diff --git a/src/frontend/src/features/rooms/livekit/components/RecordingStateToast.tsx b/src/frontend/src/features/rooms/livekit/components/RecordingStateToast.tsx
deleted file mode 100644
index 1f3bdc3f..00000000
--- a/src/frontend/src/features/rooms/livekit/components/RecordingStateToast.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { css } from '@/styled-system/css'
-import { RiRecordCircleLine } from '@remixicon/react'
-import { Text } from '@/primitives'
-import { useTranslation } from 'react-i18next'
-import { useRoomContext } from '@livekit/components-react'
-
-export const RecordingStateToast = () => {
- const { t } = useTranslation('rooms', { keyPrefix: 'recording' })
-
- const room = useRoomContext()
-
- if (!room?.isRecording) return
-
- return (
-
-
- {t('label')}
-
- )
-}
diff --git a/src/frontend/src/features/rooms/livekit/components/TranscriptStateToast.tsx b/src/frontend/src/features/rooms/livekit/components/TranscriptStateToast.tsx
new file mode 100644
index 00000000..3c1b94d8
--- /dev/null
+++ b/src/frontend/src/features/rooms/livekit/components/TranscriptStateToast.tsx
@@ -0,0 +1,103 @@
+import { css } from '@/styled-system/css'
+import { useTranslation } from 'react-i18next'
+import { useSnapshot } from 'valtio/index'
+import { useRoomContext } from '@livekit/components-react'
+import { Spinner } from '@/primitives/Spinner'
+import { useEffect, useMemo } from 'react'
+import { Text } from '@/primitives'
+import { RemoteParticipant, RoomEvent } from 'livekit-client'
+import { decodeNotificationDataReceived } from '@/features/notifications/utils'
+import { NotificationType } from '@/features/notifications/NotificationType'
+import { TranscriptionStatus, transcriptionStore } from '@/stores/transcription'
+
+export const TranscriptStateToast = () => {
+ const { t } = useTranslation('rooms', { keyPrefix: 'recording.transcript' })
+ const room = useRoomContext()
+
+ const transcriptionSnap = useSnapshot(transcriptionStore)
+
+ useEffect(() => {
+ if (room.isRecording) {
+ transcriptionStore.status = TranscriptionStatus.STARTED
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ useEffect(() => {
+ const handleDataReceived = (
+ payload: Uint8Array,
+ participant?: RemoteParticipant
+ ) => {
+ const notification = decodeNotificationDataReceived(payload)
+
+ if (!participant || !notification) return
+
+ switch (notification.type) {
+ case NotificationType.TranscriptionStarted:
+ transcriptionStore.status = TranscriptionStatus.STARTING
+ break
+ case NotificationType.TranscriptionStopped:
+ transcriptionStore.status = TranscriptionStatus.STOPPING
+ break
+ default:
+ return
+ }
+ }
+
+ const handleRecordingStatusChanged = (status: boolean) => {
+ transcriptionStore.status = status
+ ? TranscriptionStatus.STARTED
+ : TranscriptionStatus.STOPPED
+ }
+
+ room.on(RoomEvent.DataReceived, handleDataReceived)
+ room.on(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
+
+ return () => {
+ room.off(RoomEvent.DataReceived, handleDataReceived)
+ room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
+ }
+ }, [room])
+
+ const key = useMemo(() => {
+ switch (transcriptionSnap.status) {
+ case TranscriptionStatus.STOPPING:
+ return 'stopping'
+ case TranscriptionStatus.STARTING:
+ return 'starting'
+ default:
+ return 'started'
+ }
+ }, [transcriptionSnap])
+
+ if (transcriptionSnap.status == TranscriptionStatus.STOPPED) return
+
+ return (
+
+
+
+ {t(key)}
+
+
+ )
+}
diff --git a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
index e1e0d853..af94bce4 100644
--- a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
+++ b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
@@ -28,7 +28,7 @@ import { FocusLayout } from '../components/FocusLayout'
import { ParticipantTile } from '../components/ParticipantTile'
import { SidePanel } from '../components/SidePanel'
import { useSidePanel } from '../hooks/useSidePanel'
-import { RecordingStateToast } from '../components/RecordingStateToast'
+import { TranscriptStateToast } from '../components/TranscriptStateToast'
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
const LayoutWrapper = styled(
@@ -231,7 +231,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
)}
-
+
)
}
diff --git a/src/frontend/src/locales/de/rooms.json b/src/frontend/src/locales/de/rooms.json
index 824a8d2b..db8190db 100644
--- a/src/frontend/src/locales/de/rooms.json
+++ b/src/frontend/src/locales/de/rooms.json
@@ -282,7 +282,11 @@
}
},
"recording": {
- "label": ""
+ "transcript": {
+ "started": "",
+ "starting": "",
+ "stopping": ""
+ }
},
"participantTileFocus": {
"pin": {
diff --git a/src/frontend/src/locales/en/rooms.json b/src/frontend/src/locales/en/rooms.json
index 14de0bd4..08434627 100644
--- a/src/frontend/src/locales/en/rooms.json
+++ b/src/frontend/src/locales/en/rooms.json
@@ -281,7 +281,11 @@
}
},
"recording": {
- "label": "Recording"
+ "transcript": {
+ "started": "Transcribing",
+ "starting": "Transcription starting",
+ "stopping": "Transcription stopping"
+ }
},
"participantTileFocus": {
"pin": {
diff --git a/src/frontend/src/locales/fr/rooms.json b/src/frontend/src/locales/fr/rooms.json
index 48718596..7a61959f 100644
--- a/src/frontend/src/locales/fr/rooms.json
+++ b/src/frontend/src/locales/fr/rooms.json
@@ -281,7 +281,11 @@
}
},
"recording": {
- "label": "Enregistrement"
+ "transcript": {
+ "started": "Transcription en cours",
+ "starting": "Démarrage de la transcription",
+ "stopping": "ArrĂȘt de la transcription"
+ }
},
"participantTileFocus": {
"pin": {
diff --git a/src/frontend/src/locales/nl/rooms.json b/src/frontend/src/locales/nl/rooms.json
index 8b300512..a4fb56dc 100644
--- a/src/frontend/src/locales/nl/rooms.json
+++ b/src/frontend/src/locales/nl/rooms.json
@@ -281,7 +281,11 @@
}
},
"recording": {
- "label": "Opnemen"
+ "transcript": {
+ "started": "Transcriptie bezig",
+ "starting": "Transcriptie begint",
+ "stopping": "Transcriptie stopt"
+ }
},
"participantTileFocus": {
"pin": {
diff --git a/src/frontend/src/primitives/Spinner.tsx b/src/frontend/src/primitives/Spinner.tsx
index d3eb26d7..0c98a271 100644
--- a/src/frontend/src/primitives/Spinner.tsx
+++ b/src/frontend/src/primitives/Spinner.tsx
@@ -1,7 +1,13 @@
import { ProgressBar } from 'react-aria-components'
import { css } from '@/styled-system/css'
-export const Spinner = ({ size = 56 }: { size?: number }) => {
+export const Spinner = ({
+ size = 56,
+ variant = 'light',
+}: {
+ size?: number
+ variant?: 'light' | 'dark'
+}) => {
const center = 14
const strokeWidth = 3
const r = 14 - strokeWidth
@@ -25,7 +31,7 @@ export const Spinner = ({ size = 56 }: { size?: number }) => {
strokeDashoffset={0}
strokeLinecap="round"
className={css({
- stroke: 'primary.100',
+ stroke: variant == 'light' ? 'primary.100' : 'primaryDark.100',
})}
style={{}}
/>
@@ -37,7 +43,7 @@ export const Spinner = ({ size = 56 }: { size?: number }) => {
strokeDashoffset={percentage && c - (percentage / 100) * c}
strokeLinecap="round"
className={css({
- stroke: 'primary.800',
+ stroke: variant == 'light' ? 'primary.800' : 'white',
})}
style={{
animation: `rotate 1s ease-in-out infinite`,