接入 LiveKit 与 TurnSense 评分并改进模型曲线选择
This commit is contained in:
@@ -11,8 +11,9 @@ import {
|
||||
type PointerEvent,
|
||||
} from "react";
|
||||
import styles from "./timeline.module.css";
|
||||
import type { ModelId } from "@/lib/model-scores";
|
||||
import { resizeTranscriptRange, snapTimelineTime, type SnapPoint } from "@/lib/timeline-edit";
|
||||
import ModelScoreTrack, { ScoreControls, SCORE_HEIGHT, useModelScores, type ScoreThresholds, type ScoreVisibility } from "./model-score-track";
|
||||
import ModelScoreTrack, { ScoreControls, SCORE_HEIGHT, useModelScores, useScorePreferences } from "./model-score-track";
|
||||
|
||||
export interface TimelineTranscript {
|
||||
index: number;
|
||||
@@ -90,8 +91,8 @@ export default function Timeline({
|
||||
const helpId = useId();
|
||||
const scoreRegionId = useId();
|
||||
const [scoresOpen, setScoresOpen] = useState(false);
|
||||
const [thresholds, setThresholds] = useState<ScoreThresholds>({ maai: .5, smart_turn: .5, silero_vad: .5 });
|
||||
const [scoreVisibility, setScoreVisibility] = useState<ScoreVisibility>({ maai: true, smart_turn: true, silero_vad: true });
|
||||
const { thresholds, visibility: scoreVisibility, setVisibility, setThreshold } = useScorePreferences();
|
||||
const [focusedModel, setFocusedModel] = useState<ModelId | null>(null);
|
||||
const scores = useModelScores(callId, recordingPath, scoresOpen);
|
||||
const visibleScores = useMemo(() => scores.series.filter(row => scoreVisibility[row.model]), [scores.series, scoreVisibility]);
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
@@ -336,15 +337,15 @@ export default function Timeline({
|
||||
<button type="button" className={styles.scoreToggle} aria-expanded={scoresOpen} aria-controls={scoreRegionId}
|
||||
onClick={() => setScoresOpen(value => !value)}>
|
||||
<span aria-hidden="true" className={scoresOpen ? styles.chevronOpen : styles.chevron}>›</span>
|
||||
<strong>模型分数对比</strong><span>{scoresOpen ? "收起" : "展开 VAP / Smart Turn / Silero VAD 对比"}</span>
|
||||
<strong>模型分数对比</strong><span>{scoresOpen ? "收起" : "展开评分曲线"}</span>
|
||||
</button>
|
||||
{scoresOpen && <ScoreControls series={scores.series} currentMs={current}
|
||||
visibility={scoreVisibility} onVisibility={(model, visible) => setScoreVisibility(previous => ({ ...previous, [model]: visible }))}
|
||||
thresholds={thresholds} onThreshold={(model, value) => setThresholds(previous => ({ ...previous, [model]: value }))} />}
|
||||
visibility={scoreVisibility} onVisibility={setVisibility}
|
||||
thresholds={thresholds} onThreshold={setThreshold} focusedModel={focusedModel} onFocus={setFocusedModel} loading={scores.status === "loading"} />}
|
||||
</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 表示完成倾向;VAP 使用 1 − 用户近期发声分数,Silero 使用 1 − 当前发声分数,仅为非发声基线,不是语义 EOT 概率。{scores.messages.join(" ")}</>}
|
||||
: <>实线:连续回放评分 · 空心点:原标注点评分 · 虚线:参考阈值。Smart Turn、LiveKit 表示完成倾向,TurnSense 为 P(complete);VAP 使用 1 − 用户近期发声分数,Silero 使用 1 − 当前发声分数,仅为非发声基线,不是语义 EOT 概率。{scores.messages.join(" ")}</>}
|
||||
</div>}
|
||||
|
||||
<div className={styles.trackLayout}>
|
||||
@@ -452,9 +453,9 @@ export default function Timeline({
|
||||
</div>
|
||||
|
||||
{scoresOpen && <div id={scoreRegionId}>
|
||||
{visibleScores.length ? <ModelScoreTrack series={visibleScores} width={canvasWidth} durationMs={duration}
|
||||
{visibleScores.length ? <ModelScoreTrack focusedModel={focusedModel && visibleScores.some(row => row.model === focusedModel) ? focusedModel : null} series={visibleScores} width={canvasWidth} durationMs={duration}
|
||||
currentMs={current} thresholds={thresholds} annotations={annotations} onSeek={onSeek} onSelectAnnotation={onSelectAnnotation} />
|
||||
: <div className={styles.scoreEmpty} style={{ height: SCORE_HEIGHT, width: viewportWidth }}>{scores.status === "loading" ? "正在载入评分曲线…" : scores.series.length ? "已隐藏全部模型分数,请打开上方开关查看" : "当前录音暂无可显示的模型分数"}</div>}
|
||||
: <div className={styles.scoreEmpty} style={{ height: SCORE_HEIGHT, width: viewportWidth }}>{scores.status === "loading" ? "正在载入评分曲线…" : scores.series.length ? "已隐藏全部模型分数,请在「选择模型」中添加" : "当前录音暂无可显示的模型分数"}</div>}
|
||||
</div>}
|
||||
|
||||
{activeSnap && <div className={styles.snapGuide} style={{ left: position(activeSnap.ms) }} aria-hidden="true" />}
|
||||
@@ -473,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 更新。Silero 空心点取判断前最后完整帧;其他模型原标注点评分使用独立历史窗口,与连续曲线可能不同;编辑标注或转写不会重算已有分数。</p>}
|
||||
{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>}
|
||||
|
||||
<div className={styles.bottomBar}>
|
||||
<div className={styles.timeReadout}><span className={styles.playheadDot} /> 播放 <strong>{timeLabel(current, true)}</strong></div>
|
||||
|
||||
Reference in New Issue
Block a user