|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
TaskHint,
|
|
|
DialogueReport,
|
|
|
SentenceEvaluation,
|
|
|
+ WordAnalysisItem,
|
|
|
} from '@/types/englishSpeaking'
|
|
|
import { SPEAKING_DIALOGUE_API_BASE_URL } from './speakingApiConfig'
|
|
|
|
|
|
@@ -76,13 +77,19 @@ async function* parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>):
|
|
|
|
|
|
// ==================== Backend shape types ====================
|
|
|
|
|
|
+interface BackendWordAnalysisItemRaw {
|
|
|
+ word: string
|
|
|
+ accuracy_score: number | null
|
|
|
+ error_type: string
|
|
|
+}
|
|
|
+
|
|
|
interface BackendEvaluation {
|
|
|
status: 'pending' | 'completed' | 'failed'
|
|
|
accuracyScore: number | null
|
|
|
fluencyScore: number | null
|
|
|
completenessScore: number | null
|
|
|
prosodyScore: number | null
|
|
|
- wordAnalysis: unknown
|
|
|
+ wordAnalysis: BackendWordAnalysisItemRaw[] | null
|
|
|
contentFeedback: {
|
|
|
comment: string
|
|
|
betterExpression: string
|
|
|
@@ -95,6 +102,7 @@ interface BackendRound {
|
|
|
content: string
|
|
|
audioUrl: string | null
|
|
|
audioDuration: number | null // NEW: 秒,由后端 /report 透传
|
|
|
+ imageUrl: string | null // ← NEW
|
|
|
evaluation?: BackendEvaluation
|
|
|
}
|
|
|
|
|
|
@@ -128,6 +136,17 @@ function hasCompleteScores(evaluation?: BackendEvaluation): evaluation is Backen
|
|
|
&& typeof evaluation.prosodyScore === 'number'
|
|
|
}
|
|
|
|
|
|
+function normaliseWordAnalysis(
|
|
|
+ raw: BackendWordAnalysisItemRaw[] | null | undefined,
|
|
|
+): WordAnalysisItem[] | undefined {
|
|
|
+ if (!Array.isArray(raw) || raw.length === 0) return undefined
|
|
|
+ return raw.map(w => ({
|
|
|
+ word: w.word,
|
|
|
+ accuracyScore: typeof w.accuracy_score === 'number' ? w.accuracy_score : 0,
|
|
|
+ errorType: (w.error_type ?? 'None') as WordAnalysisItem['errorType'],
|
|
|
+ }))
|
|
|
+}
|
|
|
+
|
|
|
function adaptReport(raw: BackendReportResponse): DialogueReport {
|
|
|
const sentenceEvaluations: SentenceEvaluation[] = raw.rounds.map((r, idx) => {
|
|
|
const pronunciation = r.role === 'student' && hasCompleteScores(r.evaluation)
|
|
|
@@ -146,11 +165,13 @@ function adaptReport(raw: BackendReportResponse): DialogueReport {
|
|
|
content: r.content,
|
|
|
audioUrl: r.audioUrl ?? undefined,
|
|
|
audioDuration: r.audioDuration ?? undefined,
|
|
|
+ imageUrl: r.imageUrl ?? undefined, // ← NEW
|
|
|
score: pronunciation
|
|
|
? Math.round((pronunciation.accuracy + pronunciation.fluency + pronunciation.intonation + pronunciation.stress) / 4)
|
|
|
: undefined,
|
|
|
pronunciation,
|
|
|
feedback: r.evaluation?.contentFeedback ?? undefined,
|
|
|
+ wordAnalysis: normaliseWordAnalysis(r.evaluation?.wordAnalysis), // ← NEW
|
|
|
}
|
|
|
})
|
|
|
|