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 && }