@@ -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
4549type 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' ) {
0 commit comments