Update frontend debug drawer

This commit is contained in:
Xin Wang
2026-02-26 02:23:23 +08:00
parent 72ed7d0512
commit 6744646390

View File

@@ -1329,6 +1329,7 @@ export const DebugDrawer: React.FC<{
onProtocolEvent, onProtocolEvent,
}) => { }) => {
const TARGET_SAMPLE_RATE = 16000; const TARGET_SAMPLE_RATE = 16000;
const PCM_FRAME_BYTES = 640; // WS v1 fixed 20ms frame for 16k mono pcm_s16le
const downsampleTo16k = (input: Float32Array, inputSampleRate: number): Float32Array => { const downsampleTo16k = (input: Float32Array, inputSampleRate: number): Float32Array => {
if (inputSampleRate === TARGET_SAMPLE_RATE) return input; if (inputSampleRate === TARGET_SAMPLE_RATE) return input;
if (inputSampleRate < TARGET_SAMPLE_RATE) return input; if (inputSampleRate < TARGET_SAMPLE_RATE) return input;
@@ -1367,6 +1368,7 @@ export const DebugDrawer: React.FC<{
const [textSessionStarted, setTextSessionStarted] = useState(false); const [textSessionStarted, setTextSessionStarted] = useState(false);
const [wsStatus, setWsStatus] = useState<'disconnected' | 'connecting' | 'ready' | 'error'>('disconnected'); const [wsStatus, setWsStatus] = useState<'disconnected' | 'connecting' | 'ready' | 'error'>('disconnected');
const [wsError, setWsError] = useState(''); const [wsError, setWsError] = useState('');
const wsStatusRef = useRef<'disconnected' | 'connecting' | 'ready' | 'error'>('disconnected');
const [resolvedConfigOpen, setResolvedConfigOpen] = useState(false); const [resolvedConfigOpen, setResolvedConfigOpen] = useState(false);
const [resolvedConfigView, setResolvedConfigView] = useState<string>(''); const [resolvedConfigView, setResolvedConfigView] = useState<string>('');
const [captureConfigOpen, setCaptureConfigOpen] = useState(false); const [captureConfigOpen, setCaptureConfigOpen] = useState(false);
@@ -1410,6 +1412,7 @@ export const DebugDrawer: React.FC<{
const micSourceRef = useRef<MediaStreamAudioSourceNode | null>(null); const micSourceRef = useRef<MediaStreamAudioSourceNode | null>(null);
const micProcessorRef = useRef<ScriptProcessorNode | null>(null); const micProcessorRef = useRef<ScriptProcessorNode | null>(null);
const micGainRef = useRef<GainNode | null>(null); const micGainRef = useRef<GainNode | null>(null);
const micFrameBufferRef = useRef<Uint8Array>(new Uint8Array(0));
const userDraftIndexRef = useRef<number | null>(null); const userDraftIndexRef = useRef<number | null>(null);
const lastUserFinalRef = useRef<string>(''); const lastUserFinalRef = useRef<string>('');
const selectedToolSchemas = useMemo(() => { const selectedToolSchemas = useMemo(() => {
@@ -1461,6 +1464,10 @@ export const DebugDrawer: React.FC<{
localStorage.setItem('debug_ws_url', wsUrl); localStorage.setItem('debug_ws_url', wsUrl);
}, [wsUrl]); }, [wsUrl]);
useEffect(() => {
wsStatusRef.current = wsStatus;
}, [wsStatus]);
useEffect(() => { useEffect(() => {
localStorage.setItem('debug_audio_aec', aecEnabled ? '1' : '0'); localStorage.setItem('debug_audio_aec', aecEnabled ? '1' : '0');
}, [aecEnabled]); }, [aecEnabled]);
@@ -1540,10 +1547,31 @@ export const DebugDrawer: React.FC<{
void micAudioCtxRef.current.close(); void micAudioCtxRef.current.close();
micAudioCtxRef.current = null; micAudioCtxRef.current = null;
} }
micFrameBufferRef.current = new Uint8Array(0);
setCaptureConfigView(''); setCaptureConfigView('');
stopMedia(); stopMedia();
}; };
const sendFramedMicAudio = (pcm16: Int16Array) => {
const ws = wsRef.current;
if (!ws || ws.readyState !== WebSocket.OPEN || !wsReadyRef.current) return;
if (pcm16.byteLength <= 0) return;
const chunkBytes = new Uint8Array(pcm16.buffer, pcm16.byteOffset, pcm16.byteLength);
const pending = micFrameBufferRef.current;
const merged = new Uint8Array(pending.length + chunkBytes.length);
merged.set(pending, 0);
merged.set(chunkBytes, pending.length);
let offset = 0;
while (merged.length - offset >= PCM_FRAME_BYTES) {
ws.send(merged.subarray(offset, offset + PCM_FRAME_BYTES));
offset += PCM_FRAME_BYTES;
}
micFrameBufferRef.current = offset >= merged.length ? new Uint8Array(0) : merged.slice(offset);
};
const buildMicConstraints = (): MediaTrackConstraints => ({ const buildMicConstraints = (): MediaTrackConstraints => ({
deviceId: selectedMic ? { exact: selectedMic } : undefined, deviceId: selectedMic ? { exact: selectedMic } : undefined,
echoCancellation: aecEnabled, echoCancellation: aecEnabled,
@@ -1596,7 +1624,7 @@ export const DebugDrawer: React.FC<{
const inChannel = event.inputBuffer.getChannelData(0); const inChannel = event.inputBuffer.getChannelData(0);
const downsampled = downsampleTo16k(inChannel, event.inputBuffer.sampleRate); const downsampled = downsampleTo16k(inChannel, event.inputBuffer.sampleRate);
const pcm16 = float32ToPcm16(downsampled); const pcm16 = float32ToPcm16(downsampled);
wsRef.current.send(pcm16.buffer); sendFramedMicAudio(pcm16);
}; };
micSourceRef.current = source; micSourceRef.current = source;
@@ -1949,6 +1977,7 @@ export const DebugDrawer: React.FC<{
assistantDraftIndexRef.current = null; assistantDraftIndexRef.current = null;
userDraftIndexRef.current = null; userDraftIndexRef.current = null;
lastUserFinalRef.current = ''; lastUserFinalRef.current = '';
micFrameBufferRef.current = new Uint8Array(0);
setTextSessionStarted(false); setTextSessionStarted(false);
stopPlaybackImmediately(); stopPlaybackImmediately();
if (isOpen) setWsStatus('disconnected'); if (isOpen) setWsStatus('disconnected');
@@ -2096,6 +2125,14 @@ export const DebugDrawer: React.FC<{
return; return;
} }
if (type === 'config.resolved') {
const resolved = payload?.config || payload?.data?.config;
if (resolved) {
setResolvedConfigView(JSON.stringify({ config: resolved }, null, 2));
}
return;
}
if (type === 'input.speech_started') { if (type === 'input.speech_started') {
setIsLoading(true); setIsLoading(true);
return; return;
@@ -2225,7 +2262,7 @@ export const DebugDrawer: React.FC<{
} }
if (type === 'error') { if (type === 'error') {
const message = String(payload.message || 'Unknown error'); const message = String(payload?.message || payload?.data?.error?.message || 'Unknown error');
setWsStatus('error'); setWsStatus('error');
setWsError(message); setWsError(message);
setIsLoading(false); setIsLoading(false);
@@ -2251,7 +2288,7 @@ export const DebugDrawer: React.FC<{
setTextSessionStarted(false); setTextSessionStarted(false);
userDraftIndexRef.current = null; userDraftIndexRef.current = null;
stopPlaybackImmediately(); stopPlaybackImmediately();
if (wsStatus !== 'error') setWsStatus('disconnected'); if (wsStatusRef.current !== 'error') setWsStatus('disconnected');
}; };
}); });
}; };