Просмотр исходного кода

feat(speaking): wire audioDuration through type chain

Extends HistoricalDialogueMessage / PreviewChatMessage / SSEEvent /
BackendRound to carry the new audio_duration field. parseSSEStream
forwards it on the transcript event; adaptReport plumbs it into
SentenceEvaluation.audioDuration which already existed on the type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jimmylee 2 месяцев назад
Родитель
Сommit
0241fde5f5

+ 3 - 1
src/types/englishSpeaking.ts

@@ -205,6 +205,7 @@ export interface PreviewChatMessage {
   recovery?: 'retry' | 'rerecord' | 'restart'        // set on error; drives the button matrix
   audioBlob?: Blob
   audioUrl?: string
+  audioDuration?: number   // NEW: 秒,由后端 transcript 事件 / sessions-latest 写入
   evaluation?: {
     dimensions: {
       accuracy: 'excellent' | 'good' | 'improve'
@@ -229,7 +230,7 @@ export interface PreviewChatMessage {
 
 // SSE 事件类型
 export type SSEEvent =
-  | { type: 'transcript'; text: string }
+  | { type: 'transcript'; text: string; audioDuration?: number | null }
   | { type: 'token'; text: string }
   | { type: 'done'; isComplete: boolean }
   | { type: 'error'; message: string }
@@ -267,6 +268,7 @@ export interface HistoricalDialogueMessage {
   role: 'ai' | 'student'
   content: string
   audioUrl?: string | null
+  audioDuration?: number | null    // NEW: 秒,来自后端 sessions/latest
   clientTurnId?: string | null
 }
 

+ 7 - 1
src/views/Editor/EnglishSpeaking/services/llmService.ts

@@ -49,7 +49,11 @@ async function* parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>):
           try {
             const parsed = JSON.parse(data)
             if (eventType === 'transcript') {
-              yield { type: 'transcript', text: parsed.text }
+              yield {
+                type: 'transcript',
+                text: parsed.text,
+                audioDuration: parsed.audioDuration ?? null,
+              }
             } else if (eventType === 'token') {
               yield { type: 'token', text: parsed.content ?? parsed.text }
             } else if (eventType === 'done') {
@@ -90,6 +94,7 @@ interface BackendRound {
   role: 'ai' | 'student'
   content: string
   audioUrl: string | null
+  audioDuration: number | null   // NEW: 秒,由后端 /report 透传
   evaluation?: BackendEvaluation
 }
 
@@ -140,6 +145,7 @@ function adaptReport(raw: BackendReportResponse): DialogueReport {
       role: r.role,
       content: r.content,
       audioUrl: r.audioUrl ?? undefined,
+      audioDuration: r.audioDuration ?? undefined,
       score: pronunciation
         ? Math.round((pronunciation.accuracy + pronunciation.fluency + pronunciation.intonation + pronunciation.stress) / 4)
         : undefined,