Quellcode durchsuchen

feat(english-speaking): 结果页透传 contentFeedback 到 SentenceCard

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jimmylee vor 2 Wochen
Ursprung
Commit
ae55e8b61b
1 geänderte Dateien mit 87 neuen und 2 gelöschten Zeilen
  1. 87 2
      src/views/Editor/EnglishSpeaking/services/llmService.ts

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

@@ -1,4 +1,4 @@
-import type { DialogueAPI, SSEEvent, SessionConfig, SessionInfo, DialogueReport } from '@/types/englishSpeaking'
+import type { DialogueAPI, SSEEvent, SessionConfig, SessionInfo, DialogueReport, SentenceEvaluation } from '@/types/englishSpeaking'
 
 const API_BASE = 'http://localhost:8000/api/speaking/dialogue'
 
@@ -48,6 +48,90 @@ async function* parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>):
   }
 }
 
+// ==================== Backend shape types ====================
+
+interface BackendEvaluation {
+  status: 'pending' | 'completed' | 'failed'
+  accuracyScore: number | null
+  fluencyScore: number | null
+  completenessScore: number | null
+  prosodyScore: number | null
+  wordAnalysis: unknown
+  contentFeedback: {
+    highlights: string[]
+    corrections: { original: string; corrected: string; explanation: string }[]
+    suggestions: string[]
+  } | null
+}
+
+interface BackendRound {
+  round: number
+  role: 'ai' | 'student'
+  content: string
+  audioUrl: string | null
+  evaluation?: BackendEvaluation
+}
+
+interface BackendReportResponse {
+  sessionId: string
+  topic: string
+  status: 'evaluating' | 'ready'
+  rounds: BackendRound[]
+  summary: string | null
+}
+
+function adaptReport(raw: BackendReportResponse): DialogueReport {
+  const sentenceEvaluations: SentenceEvaluation[] = raw.rounds.map((r, idx) => ({
+    id: `${raw.sessionId}-${idx}`,
+    round: r.round,
+    role: r.role,
+    content: r.content,
+    audioUrl: r.audioUrl ?? undefined,
+    pronunciation: r.evaluation && r.role === 'student'
+      ? {
+          accuracy: r.evaluation.accuracyScore ?? 0,
+          fluency: r.evaluation.fluencyScore ?? 0,
+          intonation: r.evaluation.prosodyScore ?? 0,
+          stress: r.evaluation.completenessScore ?? 0,
+        }
+      : undefined,
+    feedback: r.evaluation?.contentFeedback ?? undefined,
+  }))
+
+  const studentEvals = sentenceEvaluations.filter(s => s.role === 'student' && s.pronunciation)
+  const avg = studentEvals.length > 0
+    ? Math.round(
+        studentEvals.reduce(
+          (sum, s) => sum + (s.pronunciation!.accuracy + s.pronunciation!.fluency + s.pronunciation!.intonation + s.pronunciation!.stress) / 4,
+          0,
+        ) / studentEvals.length,
+      )
+    : 0
+
+  return {
+    evaluation: {
+      overallScore: avg,
+      scoreLevel: avg >= 85 ? 'excellent' : avg >= 70 ? 'good' : avg >= 60 ? 'fair' : 'needsWork',
+      percentile: 0,
+      dimensions: { fluency: 0, interaction: 0, vocabulary: 0, grammar: 0 },
+      aiComment: raw.summary ?? '',
+      highlights: [],
+      improvements: [],
+      nextChallenge: {},
+      statistics: {
+        totalRounds: Math.max(...sentenceEvaluations.map(s => s.round), 0),
+        averageScore: avg,
+        highestScore: 0,
+        highestRound: 0,
+        grammarErrors: 0,
+        excellentExpressions: 0,
+        totalDuration: 0,
+      },
+      sentenceEvaluations,
+    },
+  }
+}
+
 // ==================== Real API ====================
 
 export class RealDialogueAPI implements DialogueAPI {
@@ -88,7 +172,8 @@ export class RealDialogueAPI implements DialogueAPI {
       credentials: 'include',
     })
     if (!res.ok) throw new Error(`getReport failed: ${res.status}`)
-    return res.json()
+    const raw: BackendReportResponse = await res.json()
+    return adaptReport(raw)
   }
 }