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`,