feat: Add FastGPT interactive voice toggle to DebugDrawer and state management

This commit is contained in:
Xin Wang
2026-03-11 13:59:34 +08:00
parent 9b9fbf432f
commit 3b9ee80c8f
2 changed files with 37 additions and 2 deletions

View File

@@ -2534,6 +2534,8 @@ export const DebugDrawer: React.FC<{
const setNsEnabled = useDebugPrefsStore((state) => state.setNsEnabled); const setNsEnabled = useDebugPrefsStore((state) => state.setNsEnabled);
const agcEnabled = useDebugPrefsStore((state) => state.agcEnabled); const agcEnabled = useDebugPrefsStore((state) => state.agcEnabled);
const setAgcEnabled = useDebugPrefsStore((state) => state.setAgcEnabled); const setAgcEnabled = useDebugPrefsStore((state) => state.setAgcEnabled);
const fastgptInteractiveVoiceEnabled = useDebugPrefsStore((state) => state.fastgptInteractiveVoiceEnabled);
const setFastgptInteractiveVoiceEnabled = useDebugPrefsStore((state) => state.setFastgptInteractiveVoiceEnabled);
const clientToolEnabledMap = useDebugPrefsStore((state) => state.clientToolEnabledMap); const clientToolEnabledMap = useDebugPrefsStore((state) => state.clientToolEnabledMap);
const setClientToolEnabled = useDebugPrefsStore((state) => state.setClientToolEnabled); const setClientToolEnabled = useDebugPrefsStore((state) => state.setClientToolEnabled);
const hydrateClientToolDefaults = useDebugPrefsStore((state) => state.hydrateClientToolDefaults); const hydrateClientToolDefaults = useDebugPrefsStore((state) => state.hydrateClientToolDefaults);
@@ -2752,6 +2754,12 @@ export const DebugDrawer: React.FC<{
fastgptInteractiveDialogRef.current = fastgptInteractiveDialog; fastgptInteractiveDialogRef.current = fastgptInteractiveDialog;
}, [fastgptInteractiveDialog]); }, [fastgptInteractiveDialog]);
useEffect(() => {
if (!fastgptInteractiveVoiceEnabled && fastgptInteractiveDialog.open) {
stopPromptVoicePlayback();
}
}, [fastgptInteractiveVoiceEnabled, fastgptInteractiveDialog.open]);
useEffect(() => { useEffect(() => {
dynamicVariableSeqRef.current = 0; dynamicVariableSeqRef.current = 0;
setDynamicVariables([]); setDynamicVariables([]);
@@ -3088,7 +3096,7 @@ export const DebugDrawer: React.FC<{
submitLabel: item.payload.submitLabel, submitLabel: item.payload.submitLabel,
cancelLabel: item.payload.cancelLabel, cancelLabel: item.payload.cancelLabel,
}); });
if (nextVoiceText) { if (nextVoiceText && fastgptInteractiveVoiceEnabled) {
void playPromptVoice(nextVoiceText); void playPromptVoice(nextVoiceText);
} }
return; return;
@@ -3163,6 +3171,10 @@ export const DebugDrawer: React.FC<{
const fieldValues = snapshot.fieldValues; const fieldValues = snapshot.fieldValues;
const interactionType = snapshot.interactionType; const interactionType = snapshot.interactionType;
stopPromptVoicePlayback(); stopPromptVoicePlayback();
// Stop only local playback so the resumed FastGPT response can take over
// without cancelling the active server-side turn.
stopPlaybackImmediately();
setAgentState('waiting');
setFastgptInteractiveDialog({ setFastgptInteractiveDialog({
open: false, open: false,
interactionType: 'userSelect', interactionType: 'userSelect',
@@ -4605,6 +4617,23 @@ export const DebugDrawer: React.FC<{
Auto Gain Control (AGC) Auto Gain Control (AGC)
</label> </label>
</div> </div>
<div className="rounded-md border border-white/10 bg-black/20 p-2 space-y-2">
<p className="text-[10px] uppercase tracking-widest text-muted-foreground">Prompt Voice</p>
<div className="flex items-center justify-between gap-3 rounded-md border border-white/10 bg-black/20 px-2 py-1.5">
<div className="min-w-0">
<div className="text-[11px] font-mono text-foreground truncate">FastGPT Interactive</div>
<div className="text-[10px] text-muted-foreground">
Play the interactive description or prompt voice when the popup opens.
</div>
</div>
<Switch
checked={fastgptInteractiveVoiceEnabled}
onCheckedChange={setFastgptInteractiveVoiceEnabled}
title={fastgptInteractiveVoiceEnabled ? 'Click to mute FastGPT interactive prompt voice' : 'Click to enable FastGPT interactive prompt voice'}
aria-label={`FastGPT interactive prompt voice ${fastgptInteractiveVoiceEnabled ? 'enabled' : 'disabled'}`}
/>
</div>
</div>
<div className="rounded-md border border-white/10 bg-black/20 p-2 space-y-2"> <div className="rounded-md border border-white/10 bg-black/20 p-2 space-y-2">
<p className="text-[10px] uppercase tracking-widest text-muted-foreground">Client Tools</p> <p className="text-[10px] uppercase tracking-widest text-muted-foreground">Client Tools</p>
<p className="text-[11px] text-muted-foreground"></p> <p className="text-[11px] text-muted-foreground"></p>
@@ -5086,7 +5115,7 @@ export const DebugDrawer: React.FC<{
)} )}
{fastgptInteractiveDialog.open && ( {fastgptInteractiveDialog.open && (
<div className="absolute inset-0 z-40 flex items-center justify-center bg-black/55 backdrop-blur-[1px]"> <div className="absolute inset-0 z-40 flex items-center justify-center bg-black/55 backdrop-blur-[1px]">
<div className="relative w-[92%] max-w-lg rounded-xl border border-white/15 bg-card/95 p-4 shadow-2xl animate-in zoom-in-95 duration-200"> <div className="relative flex max-h-[82vh] w-[92%] max-w-lg flex-col rounded-xl border border-white/15 bg-card/95 p-4 shadow-2xl animate-in zoom-in-95 duration-200">
{!fastgptInteractiveDialog.required && ( {!fastgptInteractiveDialog.required && (
<button <button
type="button" type="button"
@@ -5122,6 +5151,7 @@ export const DebugDrawer: React.FC<{
</p> </p>
)} )}
</div> </div>
<div className="min-h-0 overflow-y-auto pr-1 custom-scrollbar">
{fastgptInteractiveDialog.interactionType === 'userSelect' ? ( {fastgptInteractiveDialog.interactionType === 'userSelect' ? (
<div className="space-y-2"> <div className="space-y-2">
{fastgptInteractiveDialog.options.map((option) => { {fastgptInteractiveDialog.options.map((option) => {
@@ -5202,6 +5232,7 @@ export const DebugDrawer: React.FC<{
})} })}
</div> </div>
)} )}
</div>
<div className="mt-4 flex items-center justify-end gap-2"> <div className="mt-4 flex items-center justify-end gap-2">
<Button <Button
size="sm" size="sm"

View File

@@ -6,11 +6,13 @@ type DebugPrefsState = {
aecEnabled: boolean; aecEnabled: boolean;
nsEnabled: boolean; nsEnabled: boolean;
agcEnabled: boolean; agcEnabled: boolean;
fastgptInteractiveVoiceEnabled: boolean;
clientToolEnabledMap: Record<string, boolean>; clientToolEnabledMap: Record<string, boolean>;
setWsUrl: (value: string) => void; setWsUrl: (value: string) => void;
setAecEnabled: (value: boolean) => void; setAecEnabled: (value: boolean) => void;
setNsEnabled: (value: boolean) => void; setNsEnabled: (value: boolean) => void;
setAgcEnabled: (value: boolean) => void; setAgcEnabled: (value: boolean) => void;
setFastgptInteractiveVoiceEnabled: (value: boolean) => void;
setClientToolEnabled: (toolId: string, enabled: boolean) => void; setClientToolEnabled: (toolId: string, enabled: boolean) => void;
hydrateClientToolDefaults: (toolIds: string[]) => void; hydrateClientToolDefaults: (toolIds: string[]) => void;
}; };
@@ -30,11 +32,13 @@ export const useDebugPrefsStore = create<DebugPrefsState>()(
aecEnabled: true, aecEnabled: true,
nsEnabled: true, nsEnabled: true,
agcEnabled: true, agcEnabled: true,
fastgptInteractiveVoiceEnabled: true,
clientToolEnabledMap: {}, clientToolEnabledMap: {},
setWsUrl: (value) => set({ wsUrl: value }), setWsUrl: (value) => set({ wsUrl: value }),
setAecEnabled: (value) => set({ aecEnabled: value }), setAecEnabled: (value) => set({ aecEnabled: value }),
setNsEnabled: (value) => set({ nsEnabled: value }), setNsEnabled: (value) => set({ nsEnabled: value }),
setAgcEnabled: (value) => set({ agcEnabled: value }), setAgcEnabled: (value) => set({ agcEnabled: value }),
setFastgptInteractiveVoiceEnabled: (value) => set({ fastgptInteractiveVoiceEnabled: value }),
setClientToolEnabled: (toolId, enabled) => setClientToolEnabled: (toolId, enabled) =>
set((state) => ({ set((state) => ({
clientToolEnabledMap: { clientToolEnabledMap: {