新增 Mimi 与仅用户 VAP 对比并统一流式评测

This commit is contained in:
Xin Wang
2026-10-28 22:44:36 +08:00
parent 19d4e3c767
commit 6785e3a07b
55 changed files with 6160 additions and 31 deletions
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useMemo, useRef, useState, useId } from "react";
import { scoreAt, scorePath, type ModelId, type ScoreResponse, type ScoreSeries } from "@/lib/model-scores";
import { checkpointsForDisplay, scoreAt, scorePath, type ModelId, type ScoreResponse, type ScoreSeries } from "@/lib/model-scores";
import { createPortal } from "react-dom";
import { SCORE_MODELS, SCORE_MODEL_IDS, readScorePreferences } from "@/lib/score-models";
import type { TimelineAnnotation } from "./timeline";
@@ -169,7 +169,8 @@ export default function ModelScoreTrack({ series, width, durationMs, currentMs,
}) {
const [hoverMs, setHoverMs] = useState<number | null>(null);
const paths = useMemo(() => series.map(row => ({ ...row,
path: scorePath(row.points, durationMs, width, SCORE_HEIGHT, row.sample_interval_ms) })), [series, durationMs, width]);
checkpoints: checkpointsForDisplay(row, annotations.map(annotation => annotation.decision_ms)),
path: scorePath(row.points, durationMs, width, SCORE_HEIGHT, row.sample_interval_ms) })), [series, annotations, durationMs, width]);
const y = (p: number) => 12 + (1 - p) * (SCORE_HEIGHT - 24);
const x = (ms: number) => ms / durationMs * width;
const inspect = hoverMs ?? currentMs;
@@ -180,7 +181,7 @@ export default function ModelScoreTrack({ series, width, durationMs, currentMs,
};
return <div className={styles.scoreTrack} onPointerLeave={() => setHoverMs(null)}>
<svg width={width} height={SCORE_HEIGHT} viewBox={`0 0 ${width} ${SCORE_HEIGHT}`}
role="group" aria-label="VAP、Smart Turn、Silero VAD、LiveKit v1-mini 与 TurnSense 分数对比,纵轴 0 到 1,横轴与录音时间一致"
role="group" aria-label="VAP CPC 双声道与仅用户版本、Mimi 标准与降噪双声道、Smart Turn、Silero VAD、LiveKit v1-mini 与 TurnSense 分数对比,纵轴 0 到 1,横轴与录音时间一致"
onPointerMove={event => {
const bounds = event.currentTarget.getBoundingClientRect();
setHoverMs(Math.max(0, Math.min(durationMs, (event.clientX - bounds.left) / bounds.width * durationMs)));
@@ -194,12 +195,12 @@ export default function ModelScoreTrack({ series, width, durationMs, currentMs,
<path d={row.path} fill="none" stroke={COLORS[row.model]} strokeWidth={focusedModel === row.model ? 2.8 : 1.6} strokeLinejoin="round" pointerEvents="none" />
{row.checkpoints.map(([ms, score]) => <circle key={ms} cx={x(ms)} cy={y(score)} r={3.8}
fill="#fffefb" stroke={COLORS[row.model]} strokeWidth={1.8} tabIndex={0} role="button"
aria-label={`${row.name} 原标注点评分 ${time(ms)},${score.toFixed(3)},点击定位`}
aria-label={`${row.name} ${row.checkpoint_mode === "streaming" ? "流式判断点" : "原标注点评分"} ${time(ms)},${score.toFixed(3)},点击定位`}
onPointerDown={event => event.stopPropagation()}
onClick={event => { event.stopPropagation(); selectPoint(ms); }}
onKeyDown={event => {
if (event.key === "Enter" || event.key === " ") { event.preventDefault(); event.stopPropagation(); selectPoint(ms); }
}}><title>{row.name} · 原标注点评分 {score.toFixed(3)} · {time(ms)}</title></circle>)}
}}><title>{row.name} · {row.checkpoint_mode === "streaming" ? "流式判断点" : "原标注点评分"} {score.toFixed(3)} · {time(ms)}{row.checkpoint_mode === "streaming" ? ` · 最近更新 ${time(scoreAt(row.points, ms, row.sample_interval_ms * 2)![0])}` : ""}</title></circle>)}
</g>)}
{hoverMs !== null && <line x1={x(hoverMs)} x2={x(hoverMs)} y1={0} y2={SCORE_HEIGHT} stroke="#77816c" opacity=".5" pointerEvents="none" />}
</svg>
+2 -2
View File
@@ -345,7 +345,7 @@ export default function Timeline({
</div>
{scoresOpen && <div className={styles.scoreDescription} role="status">
{scores.status === "loading" ? "正在读取模型分数…" : scores.status === "error" ? <>{scores.messages.join(" ")} <button type="button" onClick={scores.retry}>重新读取</button></>
: <>实线:连续回放评分 · 空心点:原标注点评分 · 虚线:参考阈值。Smart Turn、LiveKit 表示完成倾向,TurnSense 为 P(complete);VAP 使用 1 − 用户近期发声分数,Silero 使用 1 − 当前发声分数,仅为非发声基线,不是语义 EOT 概率。{scores.messages.join(" ")}</>}
: <>实线:连续回放评分 · 空心点:判断时评分(VAP 为流式取点) · 虚线:参考阈值。Smart Turn、LiveKit 表示完成倾向,TurnSense 为 P(complete);VAP 使用 1 − 用户近期发声分数,Silero 使用 1 − 当前发声分数,仅为非发声基线,不是语义 EOT 概率。{scores.messages.join(" ")}</>}
</div>}
<div className={styles.trackLayout}>
@@ -474,7 +474,7 @@ export default function Timeline({
</div>
</div>
{scoresOpen && scores.series.length > 0 && <p className={styles.scoreFootnote}>点击曲线定位录音;悬停查看分数。VAP 约每 100 ms、Smart Turn 每 200 ms、Silero 每 32 ms、LiveKit、TurnSense 每 200 ms 更新。TurnSense 使用过去 8 秒用户音频;LiveKit 使用过去 1.2 秒用户音频,中文参考阈值 0.355;Silero 空心点取判断前最后完整帧;VAP 和 Smart Turn 原标注点评分使用独立历史窗口,与连续曲线可能不同;TurnSense 的 0.5 参考线不等同于原生三分类取最大值规则;编辑标注或转写不会重算已有分数。</p>}
{scoresOpen && scores.series.length > 0 && <p className={styles.scoreFootnote}>点击曲线定位录音;悬停查看分数。VAP CPC 约每 100 ms,Mimi 标准与降噪版约每 80 ms;Smart Turn 每 200 ms、Silero 每 32 ms、LiveKit、TurnSense 每 200 ms 更新。原 VAP 为 CPC 降噪双声道,“仅用户”版将机器人输入全程置为静音;Mimi 两版均输入用户和机器人双声道,20 秒上下文。回放仍为原录音。TurnSense 使用过去 8 秒用户音频;LiveKit 使用过去 1.2 秒用户音频,中文参考阈值 0.355;Silero 空心点取判断前最后完整帧;VAP 各版本圆点与悬停分数均取判断前最后可用的连续流评分,圆点应落在对应阶梯曲线上;Smart Turn 的原判断点仍为独立窗口结果;TurnSense 的 0.5 参考线不等同于原生三分类取最大值规则;编辑标注或转写不会重跑模型;VAP 圆点随当前判断位置从连续曲线重新取点。</p>}
<div className={styles.bottomBar}>
<div className={styles.timeReadout}><span className={styles.playheadDot} /> 播放 <strong>{timeLabel(current, true)}</strong></div>