diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 70353fee..c5fcbe74 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -1179,6 +1179,11 @@ "Semantic search": "Semantic search", "Voice / STT": "Voice / STT", "Voice dictation": "Voice dictation", + "Realtime dictation": "Realtime dictation", + "Realtime model": "Realtime model", + "Realtime endpoint": "Realtime endpoint", + "Streams audio live and inserts text as you speak (requires an OpenAI-compatible Realtime endpoint)": "Streams audio live and inserts text as you speak (requires an OpenAI-compatible Realtime endpoint)", + "Leave empty to use the STT base URL": "Leave empty to use the STT base URL", "Voice dictation is not available yet.": "Voice dictation is not available yet.", "Test endpoint": "Test endpoint", "Save endpoints": "Save endpoints", diff --git a/apps/client/src/features/ai-chat/components/chat-input.tsx b/apps/client/src/features/ai-chat/components/chat-input.tsx index 3bb67535..2728e7cf 100644 --- a/apps/client/src/features/ai-chat/components/chat-input.tsx +++ b/apps/client/src/features/ai-chat/components/chat-input.tsx @@ -1,11 +1,19 @@ -import { KeyboardEvent } from "react"; -import { ActionIcon, Group, Textarea, Tooltip } from "@mantine/core"; +import { KeyboardEvent, useState } from "react"; +import { + ActionIcon, + Group, + Stack, + Text, + Textarea, + Tooltip, +} from "@mantine/core"; import { IconPlayerStopFilled, IconSend } from "@tabler/icons-react"; import { useTranslation } from "react-i18next"; import { useAtom, useAtomValue } from "jotai"; import { aiChatDraftAtom } from "@/features/ai-chat/atoms/ai-chat-atom.ts"; import { workspaceAtom } from "@/features/user/atoms/current-user-atom"; import { MicButton } from "@/features/dictation/components/mic-button"; +import { RealtimeMicButton } from "@/features/dictation/components/realtime-mic-button"; interface ChatInputProps { onSend: (text: string) => void; @@ -29,12 +37,17 @@ export default function ChatInput({ const [value, setValue] = useAtom(aiChatDraftAtom); const workspace = useAtomValue(workspaceAtom); const isDictationEnabled = workspace?.settings?.ai?.dictation === true; + const isRealtime = workspace?.settings?.ai?.dictationRealtime === true; + // Live interim (partial) transcript shown as a dimmed tail under the input. + const [interim, setInterim] = useState(""); const send = (): void => { const text = value.trim(); if (!text || isStreaming || disabled) return; onSend(text); setValue(""); + // Drop any leftover partial when a message is sent. + setInterim(""); }; const handleKeyDown = (e: KeyboardEvent): void => { @@ -45,7 +58,8 @@ export default function ChatInput({ }; return ( - + +