完善韩泰 EOT 标注编辑器并更新标注规范与项目资料

This commit is contained in:
Xin Wang
2026-10-09 15:55:46 +08:00
parent 138b08a8e4
commit 0204f1288a
29 changed files with 1340 additions and 1271 deletions
+7 -39
View File
@@ -10,7 +10,6 @@ import {
type Transcript,
} from "@/lib/annotations";
import { isSameOriginRequest } from "@/lib/request-origin";
import type { CallReference } from "@/lib/call-reference";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
@@ -20,7 +19,7 @@ const dataRoot = path.join(projectRoot, "data");
const labelsRoot = path.join(projectRoot, "labels", "EOT标注");
const savedRoot = path.join(labelsRoot, "工作数据");
const exampleRoot = path.join(labelsRoot, "示例");
const callReferencesPath = path.join(process.cwd(), "data", "hantai-reference.json");
const draftRoot = path.join(labelsRoot, "韩泰初稿");
const lockPath = path.join(savedRoot, ".workspace.lock");
const extensions = new Set([".mp3", ".wav", ".m4a", ".ogg", ".flac", ".webm"]);
const maxPayloadBytes = 20 * 1024 * 1024;
@@ -119,39 +118,6 @@ async function discoverRecordings(): Promise<Recording[]> {
return result;
}
async function readCallReferences(): Promise<CallReference[]> {
let source: string;
try {
source = await fs.readFile(callReferencesPath, "utf8");
} catch (error) {
if (hasCode(error, "ENOENT")) return [];
throw error;
}
try {
const references: unknown = JSON.parse(source);
if (!Array.isArray(references)) throw new Error("顶层必须为数组。");
return references.map((value, index) => {
const message = `第 ${index + 1} 条原表记录格式无效。`;
if (!value || typeof value !== "object") throw new Error(message);
const row = value as Partial<CallReference>;
if (typeof row.call_id !== "string" || !row.call_id.trim()
|| !row.source || typeof row.source.file !== "string" || typeof row.source.sheet !== "string"
|| !Number.isSafeInteger(row.source.row) || row.source.row < 1
|| !Array.isArray(row.fields)
|| row.fields.some((field) => !field || typeof field.label !== "string" || typeof field.value !== "string")) {
throw new Error(message);
}
return {
call_id: row.call_id,
source: { file: row.source.file, sheet: row.source.sheet, row: row.source.row },
fields: row.fields.map((field) => ({ label: field.label, value: field.value })),
};
});
} catch (error) {
throw new WorkspaceError(`原表参考数据格式有误:${error instanceof Error ? error.message : "JSON 格式错误。"}`);
}
}
async function readCsvPair(directory: string): Promise<{ transcripts: Transcript[]; annotations: Annotation[] }> {
const [transcriptsCsv, annotationsCsv] = await Promise.all([
fs.readFile(path.join(directory, "asr_history.csv"), "utf8"),
@@ -179,22 +145,24 @@ export async function GET(): Promise<Response> {
if (await exists(lockPath)) {
throw new WorkspaceError("工作数据正在保存,请稍后重试。", 409);
}
const [hasTranscripts, hasAnnotations, recordings, callReferences] = await Promise.all([
const [hasTranscripts, hasAnnotations, recordings] = await Promise.all([
exists(path.join(savedRoot, "asr_history.csv")),
exists(path.join(savedRoot, "eot_labels.csv")),
discoverRecordings(),
readCallReferences(),
]);
if (hasTranscripts && hasAnnotations) {
return await withDiskLock(async () => json({
...await readCsvPair(savedRoot),
recordings,
callReferences,
source: "saved",
savedAt: await savedAt(),
}));
}
return json({ ...await readCsvPair(exampleRoot), recordings, callReferences, source: "example" });
if (await exists(path.join(draftRoot, "asr_history.csv")) && await exists(path.join(draftRoot, "eot_labels.csv"))) {
const stats = await Promise.all([fs.stat(path.join(draftRoot, "asr_history.csv")), fs.stat(path.join(draftRoot, "eot_labels.csv"))]);
return json({ ...await readCsvPair(draftRoot), recordings, source: "draft", savedAt: new Date(Math.max(...stats.map(stat => stat.mtimeMs))).toISOString() });
}
return json({ ...await readCsvPair(exampleRoot), recordings, source: "example" });
});
} catch (error) {
return errorResponse(error);