Skip to content

Commit 1ff62ce

Browse files
committed
feat: enhance voice chat functionality and add internationalization support
- Introduced new voice chat prompts and toggle functionality across multiple languages (English, Spanish, French, Japanese, Korean, Chinese). - Updated the AppState to include auto-save conversation feature for voice chat. - Improved keyboard shortcuts for toggling voice chat. - Refactored ChatBotVoice component to handle voice chat state more effectively.
1 parent c4b6516 commit 1ff62ce

11 files changed

Lines changed: 121 additions & 27 deletions

File tree

messages/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@
195195
"lastMessageCopy": "Copy Last Message",
196196
"openChatPreferences": "Open Chat Preferences",
197197
"deleteThread": "Delete Chat",
198-
"openShortcutsPopup": "Open Shortcuts Popup"
198+
"openShortcutsPopup": "Open Shortcuts Popup",
199+
"toggleVoiceChat": "Toggle Voice Chat"
199200
},
200201
"MCP": {
201202
"marketplace": "Marketplace",

messages/es.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@
9191
"closeMic": "Cerrar micrófono",
9292
"openMic": "Abrir micrófono",
9393
"endConversation": "Terminar conversación",
94-
"toggleVoiceChat": "Alternar Chat por Voz"
94+
"toggleVoiceChat": "Alternar Chat por Voz",
95+
"readyWhenYouAreJustStartTalking": "Listo cuando lo estés—solo comienza a hablar.",
96+
"yourMicIsOff": "Tu micrófono está apagado.",
97+
"preparing": "Preparando...",
98+
"startVoiceChat": "¿Iniciar chat por voz?"
9599
},
96100
"Thread": {
97101
"chat": "Chat",
@@ -191,7 +195,8 @@
191195
"lastMessageCopy": "Copiar Último Mensaje",
192196
"openChatPreferences": "Abrir Preferencias de Chat",
193197
"deleteThread": "Eliminar Chat",
194-
"openShortcutsPopup": "Abrir Ventana de Atajos"
198+
"openShortcutsPopup": "Abrir Ventana de Atajos",
199+
"toggleVoiceChat": "Alternar Chat por Voz"
195200
},
196201
"MCP": {
197202
"marketplace": "Mercado",

messages/fr.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@
9191
"closeMic": "Fermer le micro",
9292
"openMic": "Ouvrir le micro",
9393
"endConversation": "Terminer la conversation",
94-
"toggleVoiceChat": "Basculer le Chat Vocal"
94+
"toggleVoiceChat": "Basculer le Chat Vocal",
95+
"readyWhenYouAreJustStartTalking": "Prêt quand vous l'êtes—commencez simplement à parler.",
96+
"yourMicIsOff": "Votre micro est éteint.",
97+
"preparing": "Préparation...",
98+
"startVoiceChat": "Démarrer le chat vocal ?"
9599
},
96100
"Thread": {
97101
"chat": "Chat",
@@ -191,7 +195,8 @@
191195
"lastMessageCopy": "Copier le Dernier Message",
192196
"openChatPreferences": "Ouvrir Préférences Chat",
193197
"deleteThread": "Supprimer Chat",
194-
"openShortcutsPopup": "Ouvrir Popup Raccourcis"
198+
"openShortcutsPopup": "Ouvrir Popup Raccourcis",
199+
"toggleVoiceChat": "Basculer Chat Vocal"
195200
},
196201
"MCP": {
197202
"marketplace": "Marché",

messages/ja.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@
9191
"closeMic": "マイクを閉じる",
9292
"openMic": "マイクを開く",
9393
"endConversation": "会話を終了",
94-
"toggleVoiceChat": "音声チャットの切り替え"
94+
"toggleVoiceChat": "音声チャットの切り替え",
95+
"readyWhenYouAreJustStartTalking": "準備ができました。話しかけてください。",
96+
"yourMicIsOff": "マイクがオフになっています。",
97+
"preparing": "準備中...",
98+
"startVoiceChat": "音声チャットを開始しますか?"
9599
},
96100
"Thread": {
97101
"chat": "チャット",
@@ -191,7 +195,8 @@
191195
"lastMessageCopy": "最後のメッセージをコピー",
192196
"openChatPreferences": "チャット設定を開く",
193197
"deleteThread": "チャットを削除",
194-
"openShortcutsPopup": "ショートカットポップアップを開く"
198+
"openShortcutsPopup": "ショートカットポップアップを開く",
199+
"toggleVoiceChat": "音声チャットの切り替え"
195200
},
196201
"MCP": {
197202
"marketplace": "マーケットプレイス",

messages/ko.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@
195195
"lastMessageCopy": "마지막 메시지 복사",
196196
"openChatPreferences": "채팅 환경설정 열기",
197197
"deleteThread": "채팅 삭제",
198-
"openShortcutsPopup": "단축키 팝업 열기"
198+
"openShortcutsPopup": "단축키 팝업 열기",
199+
"toggleVoiceChat": "음성 채팅 토글"
199200
},
200201
"MCP": {
201202
"marketplace": "마켓플레이스",

messages/zh.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@
9191
"closeMic": "关闭麦克风",
9292
"openMic": "打开麦克风",
9393
"endConversation": "结束对话",
94-
"toggleVoiceChat": "切换语音聊天"
94+
"toggleVoiceChat": "切换语音聊天",
95+
"readyWhenYouAreJustStartTalking": "准备就绪—请开始说话。",
96+
"yourMicIsOff": "您的麦克风已关闭。",
97+
"preparing": "准备中...",
98+
"startVoiceChat": "开始语音聊天?"
9599
},
96100
"Thread": {
97101
"chat": "聊天",
@@ -191,7 +195,8 @@
191195
"lastMessageCopy": "复制最后一条消息",
192196
"openChatPreferences": "打开聊天偏好",
193197
"deleteThread": "删除聊天",
194-
"openShortcutsPopup": "打开快捷键弹窗"
198+
"openShortcutsPopup": "打开快捷键弹窗",
199+
"toggleVoiceChat": "切换语音聊天"
195200
},
196201
"MCP": {
197202
"marketplace": "市场",

src/app/store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface AppState {
2424
};
2525
voiceChat: {
2626
isOpen: boolean;
27+
autoSaveConversation: boolean;
2728
options: {
2829
provider: string;
2930
providerOptions?: Record<string, any>;
@@ -54,6 +55,7 @@ const initialState: AppState = {
5455
},
5556
voiceChat: {
5657
isOpen: false,
58+
autoSaveConversation: false,
5759
options: {
5860
provider: "openai",
5961
providerOptions: {

src/components/chat-bot-voice.tsx

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,7 @@ import { safe } from "ts-safe";
2828
import { Alert, AlertDescription, AlertTitle } from "ui/alert";
2929
import { Button } from "ui/button";
3030

31-
import {
32-
Drawer,
33-
DrawerContent,
34-
DrawerOverlay,
35-
DrawerPortal,
36-
DrawerTitle,
37-
} from "ui/drawer";
31+
import { Drawer, DrawerContent, DrawerPortal, DrawerTitle } from "ui/drawer";
3832
import {
3933
DropdownMenu,
4034
DropdownMenuContent,
@@ -61,6 +55,7 @@ import { useTranslations } from "next-intl";
6155
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "ui/dialog";
6256
import JsonView from "ui/json-view";
6357
import { useRouter } from "next/navigation";
58+
import { isShortcutEvent, Shortcuts } from "lib/keyboard-shortcuts";
6459

6560
const isNotEmptyUIMessage = (message: UIMessage) => {
6661
return message.parts.some((v) => {
@@ -149,30 +144,30 @@ export function ChatBotVoice() {
149144
start().then(() => {
150145
startAudio.current?.play().catch(() => {});
151146
});
152-
startAudio.current?.play().catch(() => {});
153147
}, [start]);
154148

155149
const endVoiceChat = useCallback(async () => {
156150
setIsClosing(true);
157151
await safe(() => stop());
158-
await safe(() => {
159-
if (!currentThreadId) return;
152+
await safe(async () => {
153+
if (!currentThreadId || !voiceChat.autoSaveConversation) return;
160154
const saveMessages = messages.filter(
161155
(v) => v.completed && isNotEmptyUIMessage(v),
162156
);
163157
if (saveMessages.length === 0) {
164158
return;
165159
}
166-
return fetch(`/api/chat/${currentThreadId}`, {
160+
await fetch(`/api/chat/${currentThreadId}`, {
167161
method: "POST",
168162
body: JSON.stringify({
169163
messages: mergeConsecutiveMessages(saveMessages),
170164
model,
171165
projectId: currentProjectId,
172166
}),
173167
});
174-
}).ifOk(() => {
175-
if (messages.length && currentThreadId) {
168+
return true;
169+
}).ifOk((isSaved) => {
170+
if (isSaved) {
176171
nextTick().then(() => {
177172
mutate("threads");
178173
router.push(`/chat/${currentThreadId}`);
@@ -189,7 +184,13 @@ export function ChatBotVoice() {
189184
isOpen: false,
190185
},
191186
});
192-
}, [messages, currentProjectId, model, currentThreadId]);
187+
}, [
188+
messages,
189+
currentProjectId,
190+
model,
191+
currentThreadId,
192+
voiceChat.autoSaveConversation,
193+
]);
193194

194195
const statusMessage = useMemo(() => {
195196
if (isLoading) {
@@ -261,6 +262,25 @@ export function ChatBotVoice() {
261262
}
262263
}, [error]);
263264

265+
useEffect(() => {
266+
if (voiceChat.isOpen) return;
267+
const handleKeyDown = (e: KeyboardEvent) => {
268+
const isVoiceChatEvent = isShortcutEvent(e, Shortcuts.toggleVoiceChat);
269+
if (isVoiceChatEvent) {
270+
e.preventDefault();
271+
e.stopPropagation();
272+
appStoreMutate((prev) => ({
273+
voiceChat: {
274+
...prev.voiceChat,
275+
isOpen: true,
276+
},
277+
}));
278+
}
279+
};
280+
window.addEventListener("keydown", handleKeyDown);
281+
return () => window.removeEventListener("keydown", handleKeyDown);
282+
}, [voiceChat.isOpen]);
283+
264284
return (
265285
<Drawer dismissible={false} open={voiceChat.isOpen} direction="top">
266286
<DrawerPortal>

src/components/layouts/app-header.tsx

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "ui/tooltip";
1010
import { Toggle } from "ui/toggle";
1111
import {
12+
AudioWaveformIcon,
1213
ChevronDown,
1314
ChevronRight,
1415
MessageCircleDashed,
@@ -70,7 +71,46 @@ export function AppHeader() {
7071
<div className="flex items-center gap-1">
7172
<Tooltip>
7273
<TooltipTrigger asChild>
73-
<Toggle
74+
<Button
75+
size={"icon"}
76+
variant={"ghost"}
77+
onClick={() => {
78+
appStoreMutate((state) => ({
79+
voiceChat: {
80+
...state.voiceChat,
81+
isOpen: true,
82+
autoSaveConversation: false,
83+
},
84+
}));
85+
}}
86+
>
87+
<AudioWaveformIcon className="size-4" />
88+
</Button>
89+
</TooltipTrigger>
90+
<TooltipContent align="end" side="bottom">
91+
<div className="text-xs flex items-center gap-2">
92+
{t("KeyboardShortcuts.toggleVoiceChat")}
93+
<div className="text-xs text-muted-foreground flex items-center gap-1">
94+
{getShortcutKeyList(Shortcuts.toggleVoiceChat).map((key) => (
95+
<span
96+
className="w-5 h-5 flex items-center justify-center bg-muted rounded "
97+
key={key}
98+
>
99+
{key}
100+
</span>
101+
))}
102+
</div>
103+
</div>
104+
</TooltipContent>
105+
</Tooltip>
106+
<div className="h-4">
107+
<Separator orientation="vertical" />
108+
</div>
109+
<Tooltip>
110+
<TooltipTrigger asChild>
111+
<Button
112+
size={"icon"}
113+
variant={"ghost"}
74114
onClick={() => {
75115
appStoreMutate((state) => ({
76116
temporaryChat: {
@@ -80,8 +120,8 @@ export function AppHeader() {
80120
}));
81121
}}
82122
>
83-
<MessageCircleDashed className="size-5" />
84-
</Toggle>
123+
<MessageCircleDashed className="size-4" />
124+
</Button>
85125
</TooltipTrigger>
86126
<TooltipContent align="end" side="bottom">
87127
<div className="text-xs flex items-center gap-2">

src/components/prompt-input.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ export default function PromptInput({
229229
voiceChat: {
230230
...state.voiceChat,
231231
isOpen: true,
232+
autoSaveConversation: true,
232233
},
233234
}));
234235
}}

0 commit comments

Comments
 (0)