import { promises as fs } from "node:fs"; import { createHash } from "node:crypto"; import path from "node:path"; import { parseScoreExport, type ModelId, type ScoreResponse } from "@/lib/model-scores"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; const root = path.resolve(process.cwd(), "..", ".."); const sources: { model: ModelId; directory: string; name: string }[] = [ { model: "maai", directory: "maai-ch-kyoto", name: "MaAI" }, { model: "smart_turn", directory: "smart-turn-v3.2", name: "Smart Turn" }, { model: "silero_vad", directory: "silero-vad", name: "Silero VAD" }, ]; const json = (data: unknown, status = 200) => Response.json(data, { status, headers: { "Cache-Control": "no-store" } }); export async function GET(request: Request) { const params = new URL(request.url).searchParams; const callId = params.get("callId") || ""; const recordingPath = params.get("recordingPath") || ""; if (!callId || callId.length > 200 || !recordingPath || recordingPath.length > 1000) { return json({ error: "请选择已关联项目录音的对话。" }, 400); } const payload: ScoreResponse = { callId, recordingPath, series: [], messages: [] }; try { for (const source of sources) { try { // Fixed paths only: no request value is used to locate an export. const file = path.join(root, "experiments", source.directory, "results", "score-timeline.json"); const data = parseScoreExport(JSON.parse(await fs.readFile(file, "utf8")), source.model); const call = data.calls.find(item => item.call_id === callId && item.audio_path === recordingPath); if (call) payload.series.push({ ...call, model: data.model, name: data.name, score: data.score, sample_interval_ms: data.sample_interval_ms }); else payload.messages.push(`${source.name} 暂无这段录音的评分。`); } catch (error) { if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) { console.error(`Cannot read ${source.name} scores`, error); } payload.messages.push(`${source.name} 评分暂时不可用。`); } } if (payload.series.length) { const dataRoot = await fs.realpath(path.join(root, "data")); const audio = await fs.realpath(path.resolve(dataRoot, payload.series[0].audio_path)); const relative = path.relative(dataRoot, audio); if (relative.startsWith(`..${path.sep}`) || relative === ".." || path.isAbsolute(relative)) { return json({ error: "评分对应的录音不在项目目录内。" }, 422); } const hash = createHash("sha256").update(await fs.readFile(audio)).digest("hex"); payload.series = payload.series.filter(series => { if (series.audio_sha256 === hash) return true; payload.messages.push(`${series.name} 的评分对应旧录音,请重新生成评分。`); return false; }); } return json(payload); } catch (error) { console.error("Cannot verify score recording", error); return json({ error: "无法核对评分对应的录音,请检查项目文件。" }, 500); } }