diff --git a/apps/ui/src/@types/index.d.ts b/apps/ui/src/@types/index.d.ts index 766f8f8ee..7d121ea8c 100644 --- a/apps/ui/src/@types/index.d.ts +++ b/apps/ui/src/@types/index.d.ts @@ -69,6 +69,7 @@ declare module '@l3-lib/ui-core/dist/icons/Discord' declare module '@l3-lib/ui-core/dist/icons/CloseOutline' declare module '@l3-lib/ui-core/dist/icons/PlayOutline' declare module '@l3-lib/ui-core/dist/icons/PauseOutline' +declare module '@l3-lib/ui-core/dist/icons/Pause' declare module '@l3-lib/ui-core/dist/icons/Persona' declare module '@l3-lib/ui-core/dist/icons/Points' declare module '@l3-lib/ui-core/dist/icons/Properties' diff --git a/apps/ui/src/components/PlayAudioButton/PlayAudioButton.tsx b/apps/ui/src/components/PlayAudioButton/PlayAudioButton.tsx new file mode 100644 index 000000000..a7af00b8a --- /dev/null +++ b/apps/ui/src/components/PlayAudioButton/PlayAudioButton.tsx @@ -0,0 +1,80 @@ +import { useEffect, useRef, useState } from 'react' + +import Button from '@l3-lib/ui-core/dist/Button' +import Play from '@l3-lib/ui-core/dist/icons/PlayOutline' +import Pause from '@l3-lib/ui-core/dist/icons/Pause' +import styled from 'styled-components' +import { t } from 'i18next' + +const PlayAudioButton = ({ audioUrl }: { audioUrl: string }) => { + const audioRef = useRef(null as any) + + const playAudio = () => { + if (audioRef.current) { + audioRef.current.play() + } + } + + const pauseAudio = () => { + if (audioRef.current) { + audioRef.current.pause() + } + } + + const [isPlaying, setIsPlaying] = useState(false) + + useEffect(() => { + if (audioRef.current) { + audioRef.current.onplaying = () => { + setIsPlaying(true) + } + audioRef.current.onpause = () => { + setIsPlaying(false) + } + } + }, []) + + return ( + + <> + {isPlaying ? ( + + ) : ( + + )} + + {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + + + Your browser does not support the audio element. + + + ) +} + +export default PlayAudioButton + +const StyledRoot = styled.div` + min-height: 24px; + max-height: 24px; + overflow: hidden; +` + +const StyledButtonTextWrapper = styled.div` + min-width: 60px; + + display: flex; + align-items: center; + justify-content: center; +` +const StyledAudio = styled.audio` + display: none; +` diff --git a/apps/ui/src/components/PlayAudioButton/index.tsx b/apps/ui/src/components/PlayAudioButton/index.tsx new file mode 100644 index 000000000..92ca6da9e --- /dev/null +++ b/apps/ui/src/components/PlayAudioButton/index.tsx @@ -0,0 +1 @@ +export { default } from './PlayAudioButton' diff --git a/apps/ui/src/gql/chat/userChatMessages.gql b/apps/ui/src/gql/chat/userChatMessages.gql index 490e4ce1f..1e194a835 100644 --- a/apps/ui/src/gql/chat/userChatMessages.gql +++ b/apps/ui/src/gql/chat/userChatMessages.gql @@ -17,5 +17,6 @@ query chatMessages($agent_id: String!, $team_id: String!, $chat_id: String!) @ap sender_user sender_name run_id + audio_url } } diff --git a/apps/ui/src/i18n/locales/en.json b/apps/ui/src/i18n/locales/en.json index 5eca6028e..9b1fa3c4f 100644 --- a/apps/ui/src/i18n/locales/en.json +++ b/apps/ui/src/i18n/locales/en.json @@ -198,6 +198,8 @@ "no-text": "No Text", "note": "Note", "or": "OR", + "play-voice": "Voice", + "pause-voice": "Pause", "payload": "Payload", "password-must-contain": "Password must contain:", "password-successfully-updated": "Password successfully updated", diff --git a/apps/ui/src/modals/AIChatModal/components/ChatMessageList/ChatMessageListV2.tsx b/apps/ui/src/modals/AIChatModal/components/ChatMessageList/ChatMessageListV2.tsx index c393b66fb..49a238abf 100644 --- a/apps/ui/src/modals/AIChatModal/components/ChatMessageList/ChatMessageListV2.tsx +++ b/apps/ui/src/modals/AIChatModal/components/ChatMessageList/ChatMessageListV2.tsx @@ -67,6 +67,7 @@ const ChatMessageListV2 = ({ sender_user: chat?.sender_user, sender_name: chat?.sender_name, run_id: chat?.run_id, + voice: chat?.audio_url, } }) @@ -185,6 +186,7 @@ const ChatMessageListV2 = ({ messageDate={chat.date} messageText={chat.message} runId={chat.run_id} + voice={chat.voice} onReplyClick={() => { setReply({ isReply: true, @@ -219,6 +221,7 @@ const ChatMessageListV2 = ({ isNewMessage={initialChat.length - 1 === index && isNewMessage} setIsNewMessage={setIsNewMessage} runId={chat.run_id} + voice={chat.voice} onReplyClick={ chat.isGreeting ? undefined diff --git a/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/AiMessage.tsx b/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/AiMessage.tsx index 5c66fec4e..0d4e829bc 100644 --- a/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/AiMessage.tsx +++ b/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/AiMessage.tsx @@ -23,6 +23,7 @@ import TypographyPrimary from 'components/Typography/Primary' import TypographyTertiary from 'components/Typography/Tertiary' import { useModal } from 'hooks' import { RUN_LOGS_MODAL_NAME } from 'modals/RunLogsModal' +import PlayAudioButton from 'components/PlayAudioButton' type AiMessageProps = { agentName?: string @@ -36,6 +37,7 @@ type AiMessageProps = { runId: string setIsNewMessage: (state: boolean) => void onReplyClick?: () => void + voice?: string } const AiMessage = ({ @@ -49,6 +51,7 @@ const AiMessage = ({ runId, setIsNewMessage, onReplyClick, + voice, }: AiMessageProps) => { function isMarkdownTable(markdownString: string) { const tableRegex = /(?<=(\r?\n){2}|^)([^\r\n]*\|[^\r\n]*(\r?\n)?)+(?=(\r?\n){2}|$)/ @@ -106,6 +109,7 @@ const AiMessage = ({ children={thoughts?.length ? thoughts[thoughts.length - 1].result : messageText} /> )} + {voice && } diff --git a/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/HumanMessage.tsx b/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/HumanMessage.tsx index f52b37a18..b768cd6df 100644 --- a/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/HumanMessage.tsx +++ b/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/HumanMessage.tsx @@ -15,6 +15,7 @@ import TypographyTertiary from 'components/Typography/Tertiary' import AiMessageMarkdown from './AiMessageMarkdown' import { RUN_LOGS_MODAL_NAME } from 'modals/RunLogsModal' import { useModal } from 'hooks' +import PlayAudioButton from 'components/PlayAudioButton' type HumanMessageProps = { avatarImg: string @@ -24,6 +25,7 @@ type HumanMessageProps = { userName: string runId: string onReplyClick?: () => void + voice?: string } const HumanMessage = ({ @@ -34,6 +36,7 @@ const HumanMessage = ({ userName, runId, onReplyClick, + voice, }: HumanMessageProps) => { const { wordArray, handleFileClick, fileUrlMatch, fileName } = useHumanMessage({ userId, @@ -80,6 +83,7 @@ const HumanMessage = ({ {/* */} + {voice && }