From 70b4043f9bef6dbf6375b3878b7775788bb9310d Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Mon, 2 Mar 2026 15:10:03 +0800 Subject: [PATCH] Enhance DebugDrawer to support voice prompts in text prompt dialogs - Added `promptType` and `voiceText` properties to `DebugTextPromptDialogState`. - Updated state management for text prompt dialogs to handle voice prompts. - Modified dialog activation logic to play voice prompts when applicable. - Adjusted UI to reflect the type of prompt being displayed (text or voice). - Ensured proper handling of prompt closure messages based on prompt type. --- web/pages/Assistants.tsx | 56 ++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/web/pages/Assistants.tsx b/web/pages/Assistants.tsx index a58fd84..6c983a0 100644 --- a/web/pages/Assistants.tsx +++ b/web/pages/Assistants.tsx @@ -2207,6 +2207,8 @@ type DebugTextPromptDialogState = { open: boolean; message: string; pendingResult?: DebugPromptPendingResult; + promptType?: 'text' | 'voice'; + voiceText?: string; }; type DebugChoicePromptDialogState = { @@ -2341,7 +2343,11 @@ export const DebugDrawer: React.FC<{ const [inputText, setInputText] = useState(''); const [isLoading, setIsLoading] = useState(false); const [callStatus, setCallStatus] = useState<'idle' | 'calling' | 'active'>('idle'); - const [textPromptDialog, setTextPromptDialog] = useState({ open: false, message: '' }); + const [textPromptDialog, setTextPromptDialog] = useState({ + open: false, + message: '', + promptType: 'text', + }); const [choicePromptDialog, setChoicePromptDialog] = useState({ open: false, question: '', options: [] }); const textPromptDialogRef = useRef(textPromptDialog); const choicePromptDialogRef = useRef(choicePromptDialog); @@ -2538,7 +2544,7 @@ export const DebugDrawer: React.FC<{ closeWs(); stopPromptVoicePlayback(); promptDialogQueueRef.current = []; - setTextPromptDialog({ open: false, message: '' }); + setTextPromptDialog({ open: false, message: '', promptType: 'text' }); setChoicePromptDialog({ open: false, question: '', options: [] }); if (audioCtxRef.current) { void audioCtxRef.current.close(); @@ -2861,11 +2867,17 @@ export const DebugDrawer: React.FC<{ const activatePromptDialog = (item: DebugPromptQueueItem) => { if (item.kind === 'text') { + const nextVoiceText = String(item.payload.voiceText || '').trim(); setTextPromptDialog({ open: true, message: item.payload.message, pendingResult: item.payload.pendingResult, + promptType: item.payload.promptType || 'text', + voiceText: nextVoiceText || undefined, }); + if (nextVoiceText) { + void playPromptVoice(nextVoiceText); + } return; } const nextVoiceText = String(item.payload.voiceText || '').trim(); @@ -2902,16 +2914,19 @@ export const DebugDrawer: React.FC<{ if (!snapshot.open && !opts?.force) return; const pending = snapshot?.pendingResult; const message = snapshot?.message || ''; - setTextPromptDialog({ open: false, message: '' }); + const promptType = snapshot?.promptType === 'voice' ? 'voice' : 'text'; + stopPromptVoicePlayback(); + setTextPromptDialog({ open: false, message: '', promptType: 'text' }); if (pending?.waitForResponse) { emitClientToolResult( { tool_call_id: pending.toolCallId, name: pending.toolName, output: { - message: 'text_prompt_closed', + message: promptType === 'voice' ? 'voice_prompt_closed' : 'text_prompt_closed', action, msg: message, + prompt_type: promptType, }, status: { code: 200, message: 'ok' }, }, @@ -3108,7 +3123,7 @@ export const DebugDrawer: React.FC<{ setCallStatus('idle'); clearResponseTracking(); setMessages([]); - setTextPromptDialog({ open: false, message: '' }); + setTextPromptDialog({ open: false, message: '', promptType: 'text' }); setChoicePromptDialog({ open: false, question: '', options: [] }); lastUserFinalRef.current = ''; setIsLoading(false); @@ -3467,7 +3482,7 @@ export const DebugDrawer: React.FC<{ micFrameBufferRef.current = new Uint8Array(0); stopPromptVoicePlayback(); promptDialogQueueRef.current = []; - setTextPromptDialog({ open: false, message: '' }); + setTextPromptDialog({ open: false, message: '', promptType: 'text' }); setChoicePromptDialog({ open: false, question: '', options: [] }); setTextSessionStarted(false); stopPlaybackImmediately(); @@ -3729,13 +3744,26 @@ export const DebugDrawer: React.FC<{ resultPayload.output = { message: "Missing required argument 'msg'" }; resultPayload.status = { code: 422, message: 'invalid_arguments' }; } else { - void playPromptVoice(msg); - if (waitForResponse) { - // Voice prompt playback is fire-and-forget; keep previous wait behavior stable. - // Client ack is returned immediately after dispatch. + enqueuePromptDialog({ + kind: 'text', + payload: { + message: msg, + promptType: 'voice', + voiceText: msg, + pendingResult: { + toolCallId: toolCallId, + toolName, + toolDisplayName, + waitForResponse, + }, + }, + }); + if (!waitForResponse) { + resultPayload.output = { message: 'voice_prompt_shown', msg }; + resultPayload.status = { code: 200, message: 'ok' }; + } else { + return; } - resultPayload.output = { message: 'voice_prompt_sent', msg }; - resultPayload.status = { code: 200, message: 'ok' }; } } else if (toolName === 'text_msg_prompt') { const msg = String(parsedArgs?.msg || '').trim(); @@ -4524,7 +4552,9 @@ export const DebugDrawer: React.FC<{
-
文本消息提示
+
+ {textPromptDialog.promptType === 'voice' ? '语音消息提示' : '文本消息提示'} +

{textPromptDialog.message}