Переглянути джерело

feat(speaking): add turnId/recovery to types; plumb turnId through llmService

PreviewChatMessage gains turnId (set on creation) and recovery (set on
error: 'retry' | 'rerecord' | 'restart'). unrecoverable field removed
(subsumed by recovery === 'restart').

llmService.speak() and generateGreeting() now require turnId in their
signatures and forward it to the backend (Form field for /speak, JSON
body for /greeting).

Engine call sites are intentionally broken at this commit — Tasks
11–14 wire them up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jimmylee 2 місяців тому
батько
коміт
8af1769874

+ 4 - 4
src/types/englishSpeaking.ts

@@ -201,8 +201,8 @@ export interface PreviewChatMessage {
   timestamp: Date
   status?: MessageStatus
   error?: string
-  /** true 表示这个错误无法重试(如 session 404/409),UI 应提示用户重开 */
-  unrecoverable?: boolean
+  turnId?: string                                     // set when message is created via begin/commit
+  recovery?: 'retry' | 'rerecord' | 'restart'        // set on error; drives the button matrix
   audioBlob?: Blob
   evaluation?: {
     dimensions: {
@@ -292,9 +292,9 @@ export interface DialogueAPI {
   createSession(config: SessionConfig): Promise<SessionInfo>
   completeSession(sessionId: string): Promise<void>
   /** Throws DOMException('AbortError') on signal abort; throws DialogueApiError on non-OK HTTP. */
-  generateGreeting(sessionId: string, signal?: AbortSignal): Promise<GreetingInfo>
+  generateGreeting(sessionId: string, turnId: string, signal?: AbortSignal): Promise<GreetingInfo>
   generateTaskHint(sessionId: string): Promise<TaskHint>
-  speak(sessionId: string, audioBlob: Blob, signal: AbortSignal): AsyncGenerator<SSEEvent>
+  speak(sessionId: string, audioBlob: Blob, signal: AbortSignal, turnId: string): AsyncGenerator<SSEEvent>
   getReport(sessionId: string): Promise<DialogueReport>
 }
 

+ 5 - 2
src/views/Editor/EnglishSpeaking/services/llmService.ts

@@ -232,11 +232,13 @@ export class RealDialogueAPI implements DialogueAPI {
     }
   }
 
-  async generateGreeting(sessionId: string, signal?: AbortSignal): Promise<GreetingInfo> {
+  async generateGreeting(sessionId: string, turnId: string, signal?: AbortSignal): Promise<GreetingInfo> {
     const res = await fetch(`${API_BASE}/session/${sessionId}/greeting`, {
       method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
       credentials: 'include',
       signal,
+      body: JSON.stringify({ turnId }),
     })
     if (!res.ok) {
       const text = await res.text().catch(() => '')
@@ -260,10 +262,11 @@ export class RealDialogueAPI implements DialogueAPI {
     return await res.json()
   }
 
-  async *speak(sessionId: string, audioBlob: Blob, signal: AbortSignal): AsyncGenerator<SSEEvent> {
+  async *speak(sessionId: string, audioBlob: Blob, signal: AbortSignal, turnId: string): AsyncGenerator<SSEEvent> {
     const formData = new FormData()
     formData.append('sessionId', sessionId)
     formData.append('audio', audioBlob, 'recording.webm')
+    formData.append('turnId', turnId)
 
     const res = await fetch(`${API_BASE}/speak`, {
       method: 'POST',