Przeglądaj źródła

refactor(speaking): address Task 3 code review feedback

- Move createDialogueApi factory below class definitions (reading order /
  avoid fragile forward reference)
- generateGreeting success path: explicit { aiMessage: body.aiMessage }
  mapping, matching createSession's pattern
- DialogueAPI.generateGreeting JSDoc clarifies AbortError vs DialogueApiError
- MockDialogueAPI.generateGreeting honors AbortSignal so unmount-abort
  behavior can be tested in preview mode

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jimmylee 2 tygodni temu
rodzic
commit
25bc53ae28

+ 1 - 0
src/types/englishSpeaking.ts

@@ -266,6 +266,7 @@ export interface DialogueReport {
 // 对话 API 接口
 export interface DialogueAPI {
   createSession(config: SessionConfig): Promise<SessionInfo>
+  /** Throws DOMException('AbortError') on signal abort; throws DialogueApiError on non-OK HTTP. */
   generateGreeting(sessionId: string, signal?: AbortSignal): Promise<GreetingInfo>
   speak(sessionId: string, audioBlob: Blob, signal: AbortSignal): AsyncGenerator<SSEEvent>
   getReport(sessionId: string): Promise<DialogueReport>

+ 8 - 8
src/views/Editor/EnglishSpeaking/services/llmService.ts

@@ -19,10 +19,6 @@ export class DialogueApiError extends Error {
   }
 }
 
-export function createDialogueApi(mode: 'preview' | 'real'): DialogueAPI {
-  return mode === 'real' ? new RealDialogueAPI() : new MockDialogueAPI()
-}
-
 // ==================== SSE 解析 ====================
 
 async function* parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>): AsyncGenerator<SSEEvent> {
@@ -192,7 +188,8 @@ export class RealDialogueAPI implements DialogueAPI {
         res.status,
       )
     }
-    return res.json()
+    const body = await res.json()
+    return { aiMessage: body.aiMessage }
   }
 
   async *speak(sessionId: string, audioBlob: Blob, signal: AbortSignal): AsyncGenerator<SSEEvent> {
@@ -243,9 +240,8 @@ export class MockDialogueAPI implements DialogueAPI {
     }
   }
 
-  async generateGreeting(_sessionId: string, _signal?: AbortSignal): Promise<GreetingInfo> {
-    // 模拟 300ms 延迟
-    await new Promise(r => setTimeout(r, 300))
+  async generateGreeting(_sessionId: string, signal?: AbortSignal): Promise<GreetingInfo> {
+    await delay(300, signal)
     return { aiMessage: "Hi! What's your favorite animal?" }
   }
 
@@ -296,6 +292,10 @@ export class MockDialogueAPI implements DialogueAPI {
   }
 }
 
+export function createDialogueApi(mode: 'preview' | 'real'): DialogueAPI {
+  return mode === 'real' ? new RealDialogueAPI() : new MockDialogueAPI()
+}
+
 function delay(ms: number, signal?: AbortSignal): Promise<void> {
   return new Promise((resolve, reject) => {
     if (signal?.aborted) { reject(new DOMException('Aborted', 'AbortError')); return }