Skip to content

Commit c070c8f

Browse files
author
shijiashuai
committed
fix: 修复缺失模块、TTS 误报错误、语音对话历史缺失等 4 项 bug
- 补充缺失的 useTouch.ts,修复测试套件整体失败 - TTSService cancel 前清空 utterance 回调,避免主动停止触发错误 toast - ASRStateAdapter 接入 chatSessionStore,语音对话消息现在正确写入聊天历史 - useConnectionHealth 中 as any 替换为 as ConnectionStatus
1 parent 402fd88 commit c070c8f

4 files changed

Lines changed: 48 additions & 5 deletions

File tree

src/core/audio/audioService.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export interface ASRStateAdapter {
4040
sessionId: string;
4141
/** Current behavior (for thinking reset check). */
4242
currentBehavior: string;
43+
/** Add a message to chat history (for voice-initiated dialogue turns). */
44+
addChatMessage?: (role: 'user' | 'assistant', text: string, isStreaming?: boolean) => number | null;
45+
/** Update an existing chat message (for voice-initiated dialogue turns). */
46+
updateChatMessage?: (id: number, updates: Partial<{ text: string; isStreaming: boolean }>) => void;
4347
}
4448

4549
type SpeechRecognitionResultLike = ArrayLike<{ transcript: string }>;
@@ -77,6 +81,7 @@ export class TTSService {
7781
private config: TTSConfig;
7882
private isInitialized: boolean = false;
7983
private callbacks: TTSCallbacks;
84+
private currentUtterance: SpeechSynthesisUtterance | null = null;
8085

8186
constructor(config: TTSConfig = {}, callbacks: TTSCallbacks = {}) {
8287
this.synth = typeof window !== 'undefined' && 'speechSynthesis' in window
@@ -142,6 +147,7 @@ export class TTSService {
142147
}
143148

144149
if (this.synth.speaking) {
150+
this.cancelCurrentUtterance();
145151
this.synth.cancel();
146152
}
147153

@@ -165,16 +171,19 @@ export class TTSService {
165171
};
166172

167173
utterance.onend = () => {
174+
this.currentUtterance = null;
168175
this.callbacks.onSpeakEnd?.();
169176
resolve();
170177
};
171178

172179
utterance.onerror = (event) => {
180+
this.currentUtterance = null;
173181
console.error('语音合成错误:', event);
174182
this.callbacks.onError?.(`语音合成失败: ${event.error}`);
175183
reject(new Error(event.error));
176184
};
177185

186+
this.currentUtterance = utterance;
178187
this.synth.speak(utterance);
179188
});
180189
}
@@ -197,6 +206,7 @@ export class TTSService {
197206
}
198207

199208
if (this.synth.speaking) {
209+
this.cancelCurrentUtterance();
200210
this.synth.cancel();
201211
}
202212

@@ -223,18 +233,32 @@ export class TTSService {
223233
};
224234

225235
utterance.onend = () => {
236+
this.currentUtterance = null;
226237
this.callbacks.onSpeakEnd?.();
227238
};
228239

229240
utterance.onerror = (event) => {
241+
this.currentUtterance = null;
230242
console.error('语音合成错误:', event);
231243
this.callbacks.onError?.('语音合成失败');
232244
};
233245

246+
this.currentUtterance = utterance;
234247
this.synth.speak(utterance);
235248
}
236249

250+
/** Nullify handlers on the current utterance so cancel() won't fire onerror. */
251+
private cancelCurrentUtterance(): void {
252+
if (this.currentUtterance) {
253+
this.currentUtterance.onstart = null;
254+
this.currentUtterance.onend = null;
255+
this.currentUtterance.onerror = null;
256+
this.currentUtterance = null;
257+
}
258+
}
259+
237260
stop(): void {
261+
this.cancelCurrentUtterance();
238262
this.synth?.cancel();
239263
this.callbacks.onSpeakEnd?.();
240264
}
@@ -538,10 +562,10 @@ export class ASRService {
538562
isMuted: this.state.isMuted,
539563
speakWith: (textToSpeak) => this.tts.speak(textToSpeak),
540564
onAddUserMessage: (t) => {
541-
// Deliberately not handled here — callers should wire this via callbacks
565+
this.state.addChatMessage?.('user', t);
542566
},
543-
onAddAssistantMessage: () => {
544-
// Same as above
567+
onAddAssistantMessage: (replyText) => {
568+
this.state.addChatMessage?.('assistant', replyText);
545569
},
546570
onResetBehavior: () => {
547571
if (this.state.currentBehavior === 'thinking') {

src/core/audio/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ function createASRStateAdapter(): ASRStateAdapter {
5353
get currentBehavior() {
5454
return useDigitalHumanStore.getState().currentBehavior;
5555
},
56+
addChatMessage: (role, text, isStreaming) => useChatSessionStore.getState().addChatMessage(role, text, isStreaming),
57+
updateChatMessage: (id, updates) => useChatSessionStore.getState().updateChatMessage(id, updates),
5658
};
5759
}
5860

src/hooks/useConnectionHealth.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useRef, useCallback } from 'react';
22
import { useSystemStore } from '../store/systemStore';
3+
import type { ConnectionStatus } from '../store/systemStore';
34
import { checkServerHealth } from '../core/dialogue/dialogueService';
45
import { resolveChatTransportMode } from '../core/dialogue/chatTransport';
56
import { toast } from 'sonner';
@@ -22,7 +23,7 @@ export function useConnectionHealth() {
2223
const nextStatus = isHealthy ? 'connected' : 'disconnected';
2324
const previousStatus = lastStatusRef.current;
2425

25-
setConnectionStatus(nextStatus as any);
26+
setConnectionStatus(nextStatus as ConnectionStatus);
2627

2728
if (!isHealthy && previousStatus !== 'disconnected') {
2829
toast.warning('服务器连接不稳定,部分功能可能受限');
@@ -45,7 +46,7 @@ export function useConnectionHealth() {
4546
const isHealthy = await checkServerHealth();
4647
const nextStatus = isHealthy ? 'connected' : 'error';
4748

48-
setConnectionStatus(nextStatus as any);
49+
setConnectionStatus(nextStatus as ConnectionStatus);
4950
lastStatusRef.current = nextStatus;
5051

5152
if (isHealthy) {

src/hooks/useTouch.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useState, useEffect } from 'react';
2+
3+
export function isTouchDevice(): boolean {
4+
if (typeof window === 'undefined') return false;
5+
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
6+
}
7+
8+
export function useTouch(): { isTouchDevice: boolean } {
9+
const [touch, setTouch] = useState(isTouchDevice);
10+
11+
useEffect(() => {
12+
setTouch(isTouchDevice());
13+
}, []);
14+
15+
return { isTouchDevice: touch };
16+
}

0 commit comments

Comments
 (0)