Revamp EasyCom floor event review pipeline

This commit is contained in:
Xin Wang
2026-09-14 18:43:58 +08:00
parent 7d9db4af58
commit 4f88806d56
11 changed files with 1081 additions and 965 deletions

View File

@@ -1,285 +1,82 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="EasyCom wearer floor-event local review workspace" />
<title>Floor Event Review</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='14' fill='%230b1324'/%3E%3Cpath d='M14 39h8l5-17 9 29 6-22 4 10h5' fill='none' stroke='%235eead4' stroke-width='5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E" />
<link rel="stylesheet" href="/app.css" />
</head>
<body>
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true"></span>
<div>
<h1>Floor Event Review</h1>
<p>EasyCom 事件审核</p>
</div>
</div>
<div class="progress-block" aria-live="polite">
<div class="progress-copy">
<strong id="progressText">载入中</strong>
<span id="progressMeta">—</span>
</div>
<div class="progress-track"><span id="progressBar"></span></div>
</div>
<nav class="export-actions" aria-label="导出审核结果">
<a href="/api/export.jsonl" download>导出 JSONL</a>
<a href="/api/export.csv" download>导出 CSV</a>
</nav>
</header>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>EasyCom Floor Event Review</title>
<link rel="stylesheet" href="/app.css" />
</head>
<body>
<header class="topbar">
<div class="brand"><span class="brand-mark"></span><div><h1>Floor Event Review</h1><p>AI 粗标注 → 人工确认</p></div></div>
<div class="progress-block"><div class="progress-copy"><strong id="progressText">载入中</strong><span id="progressMeta">—</span></div><div class="progress-track"><span id="progressBar"></span></div></div>
<nav class="export-actions"><a href="/api/export.jsonl" download>导出 JSONL</a><a href="/api/export.csv" download>导出 CSV</a></nav>
</header>
<section class="filters" aria-label="候选筛选">
<label>
<span>Session</span>
<select id="sessionFilter"><option value="all">全部</option></select>
</label>
<label>
<span>Split</span>
<select id="splitFilter">
<option value="all">全部</option>
<option value="train">train</option>
<option value="test">test</option>
</select>
</label>
<label>
<span>事件族</span>
<select id="familyFilter">
<option value="all">全部</option>
<option value="eot">EOT-response</option>
<option value="int">INT-yield</option>
<option value="onset">wearer onset</option>
<option value="offset">旧 EOT proxy</option>
<option value="interaction">旧 interaction proxy</option>
</select>
</label>
<label>
<span>审核状态</span>
<select id="statusFilter">
<option value="unreviewed">未审核</option>
<option value="all">全部</option>
<option value="reviewed">已审核</option>
</select>
</label>
<label class="search-field">
<span>搜索</span>
<input id="searchFilter" type="search" placeholder="UID、类别、session" />
</label>
<button id="clearFilters" class="button ghost" type="button">清除筛选</button>
</section>
<section class="filters" aria-label="候选筛选">
<label><span>Session</span><select id="sessionFilter"><option value="all">全部</option></select></label>
<label><span>Split</span><select id="splitFilter"><option value="all">全部</option><option value="train">train</option><option value="test">test</option></select></label>
<label><span>任务</span><select id="familyFilter"><option value="all">全部</option><option value="eot">EOT / ENTRY</option><option value="int">INT / YIELD</option></select></label>
<label><span>审核状态</span><select id="statusFilter"><option value="unreviewed">未审核</option><option value="all">全部</option><option value="reviewed">已审核</option></select></label>
<label class="search-field"><span>搜索</span><input id="searchFilter" type="search" placeholder="事件来源、AI 标签、session" /></label>
<button id="clearFilters" class="button ghost" type="button">清除筛选</button>
</section>
<main class="workspace">
<aside class="queue-panel panel" aria-label="事件候选列表">
<div class="panel-heading">
<div>
<span class="eyebrow">QUEUE</span>
<h2 id="queueTitle">候选</h2>
</div>
<span id="queueCount" class="count-pill">0</span>
</div>
<div id="queueList" class="queue-list"></div>
<p id="queueLimit" class="queue-limit" hidden></p>
</aside>
<main class="workspace">
<aside class="queue-panel panel">
<div class="panel-heading"><div><span class="eyebrow">QUEUE</span><h2 id="queueTitle">待审核</h2></div><span id="queueCount" class="count-pill">0</span></div>
<div id="queueList" class="queue-list"></div><p id="queueLimit" class="queue-limit" hidden></p>
</aside>
<section class="evidence-panel panel" aria-label="事件证据">
<div class="event-header">
<div>
<div class="event-badges">
<span id="familyBadge" class="badge accent">—</span>
<span id="splitBadge" class="badge">—</span>
<span id="autoConfidenceBadge" class="badge">—</span>
</div>
<h2 id="eventTitle">请选择一个候选事件</h2>
<p id="eventSubtitle">—</p>
</div>
<div class="event-position"><strong id="eventPosition">0 / 0</strong></div>
</div>
<section class="evidence-panel panel">
<div class="event-header"><div><div class="event-badges"><span id="familyBadge" class="badge accent">—</span><span id="splitBadge" class="badge">—</span><span id="autoConfidenceBadge" class="badge">—</span></div><h2 id="eventTitle">请选择候选</h2><p id="eventSubtitle">—</p></div><div class="event-position"><strong id="eventPosition">0 / 0</strong></div></div>
<div id="llmSuggestion" class="llm-suggestion" hidden>
<strong>DeepSeek 预标注(仅供复核)</strong>
<span id="llmSuggestionText">—</span>
</div>
<div id="llmSuggestion" class="llm-suggestion" hidden><strong>AI 预标注(不是最终标签)</strong><span id="llmSuggestionText">—</span></div>
<div class="video-shell">
<video id="eventVideo" controls preload="metadata"></video>
<div id="mediaEmpty" class="empty-state" hidden>
<strong>当前候选未找到视频</strong>
<span>检查 dataset root、session 和 clip stem。</span>
</div>
<div class="time-marker" aria-hidden="true"><span></span></div>
</div>
<div class="transport" aria-label="视频定位">
<button type="button" data-seek="-2">−2 s</button>
<button type="button" data-seek="-0.2">−0.2 s</button>
<button id="jumpEvent" class="jump-event" type="button">回到事件</button>
<button type="button" data-seek="0.2">+0.2 s</button>
<button type="button" data-seek="2">+2 s</button>
<span id="timeReadout">—</span>
</div>
<section class="transcript-section">
<div class="section-heading">
<div>
<span class="eyebrow">TRANSCRIPT</span>
<h3>事件上下文</h3>
</div>
<span id="clipLabel">—</span>
</div>
<div id="transcriptList" class="transcript-list">
<div class="empty-copy">选择事件后显示 transcript。</div>
</div>
</section>
<details class="raw-details">
<summary>查看自动规则与原始候选字段</summary>
<pre id="rawCandidate">{}</pre>
</details>
<section class="timeline-section">
<div class="section-heading"><div><span class="eyebrow">SESSION OVERVIEW</span><h3>本 Session 候选分布</h3></div><span>点击 marker 跳转</span></div>
<div id="sessionTimeline" class="session-timeline"></div>
<div class="section-heading local-heading"><div><span class="eyebrow">LOCAL SPEAKER ACTIVITY</span><h3>事件附近 participant timeline</h3></div><span>原始标注,只读</span></div>
<div id="speakerTimeline" class="speaker-timeline"></div>
</section>
<aside class="review-panel panel" aria-label="人工审核表单">
<div class="panel-heading review-heading">
<div>
<span class="eyebrow">HUMAN REVIEW</span>
<h2>审核结论</h2>
<div class="video-shell"><video id="eventVideo" controls preload="metadata"></video><div id="mediaEmpty" class="empty-state" hidden><strong>未找到视频</strong><span>检查 dataset root 与 clip。</span></div></div>
<div class="transport"><button type="button" data-seek="-2">−2 s</button><button type="button" data-seek="-0.2">−0.2 s</button><button id="jumpEvent" class="jump-event" type="button">回到事件</button><button type="button" data-seek="0.2">+0.2 s</button><button type="button" data-seek="2">+2 s</button><span id="timeReadout">—</span></div>
<section class="transcript-section"><div class="section-heading"><div><span class="eyebrow">TRANSCRIPT</span><h3>事件上下文</h3></div><span id="clipLabel">—</span></div><div id="transcriptList" class="transcript-list"><div class="empty-copy">选择事件后显示 transcript。</div></div></section>
<details class="raw-details"><summary>查看 structured timing 与原始候选</summary><pre id="rawCandidate">{}</pre></details>
</section>
<aside class="review-panel panel">
<div class="panel-heading review-heading"><div><span class="eyebrow">HUMAN REVIEW</span><h2>只做一个主操作</h2></div><span id="saveState" class="save-state">未保存</span></div>
<form id="reviewForm">
<fieldset><legend>确认 AI 判断,或改标 <span class="required">必填</span></legend>
<div class="choice-grid two action-grid">
<label><input type="radio" name="human_action" value="accept" /><span><b>Accept</b><small>接受 AI verdict/type</small></span></label>
<label><input type="radio" name="human_action" value="relabel" /><span><b>Relabel</b><small>展开并选择正确标签</small></span></label>
<label><input type="radio" name="human_action" value="exclude" /><span><b>Exclude</b><small>数据不可可靠评分</small></span></label>
<label><input type="radio" name="human_action" value="second_review" /><span><b>Unsure / 二审</b><small>证据不足,交给二审</small></span></label>
</div>
<span id="saveState" class="save-state">未保存</span>
</div>
</fieldset>
<form id="reviewForm">
<fieldset>
<legend>1. 稿件协议结论 <span class="required">必填</span></legend>
<div class="choice-grid two compact">
<label><input type="radio" name="protocol_disposition" value="positive" /><span><b>Positive</b><small>EOT-response / INT-yield 正例</small></span></label>
<label><input type="radio" name="protocol_disposition" value="hard_negative" /><span><b>Hard negative</b><small>错误 commit 的计分区间</small></span></label>
<label><input type="radio" name="protocol_disposition" value="exclude" /><span><b>Exclude</b><small>歧义或标注损坏</small></span></label>
<label><input type="radio" name="protocol_disposition" value="uncertain" /><span><b>不确定</b><small>需要裁决</small></span></label>
</div>
</fieldset>
<section id="relabelFields" class="conditional-fields" hidden>
<label><span>一级 verdict</span><select name="human_verdict"><option value="">请选择</option><option>POSITIVE</option><option>HARD_NEGATIVE</option><option>NOT_APPLICABLE</option><option>UNSURE</option></select></label>
<label id="eventTypeField" hidden><span>Positive event</span><select name="human_event_type"><option value="">请选择</option><option>EOT_RESPONSE</option><option>INT_YIELD</option></select></label>
<label id="subtypeField" hidden><span>Hard-negative subtype</span><select name="human_subtype"><option value="">请选择</option><option>EOT_MIDTURN_PAUSE</option><option>EOT_OTHER_TO_OTHER</option><option>EOT_WEARER_BACKCHANNEL</option><option>INT_OTHER_BACKCHANNEL</option><option>INT_NON_TAKEOVER_OVERLAP</option><option>INT_WEARER_HOLDS_FLOOR</option><option>OTHER_HARD_NEGATIVE</option></select></label>
</section>
<fieldset>
<legend>2. Hard-negative 类型 <span class="required">必填</span></legend>
<div class="choice-grid two compact">
<label><input type="radio" name="hard_negative_type" value="eot_midturn_pause" /><span><b>EOT 句中停顿</b></span></label>
<label><input type="radio" name="hard_negative_type" value="eot_other_to_other" /><span><b>EOT other→other</b></span></label>
<label><input type="radio" name="hard_negative_type" value="eot_no_response" /><span><b>EOT 保持沉默</b></span></label>
<label><input type="radio" name="hard_negative_type" value="int_backchannel" /><span><b>INT backchannel</b></span></label>
<label><input type="radio" name="hard_negative_type" value="int_non_takeover_overlap" /><span><b>INT 非接管重叠</b></span></label>
<label><input type="radio" name="hard_negative_type" value="int_retain_floor" /><span><b>INT 保持话轮</b></span></label>
<label><input type="radio" name="hard_negative_type" value="other" /><span><b>其他</b></span></label>
<label><input type="radio" name="hard_negative_type" value="not_applicable" /><span><b>不适用</b></span></label>
<label><input type="radio" name="hard_negative_type" value="uncertain" /><span><b>不确定</b></span></label>
</div>
</fieldset>
<section id="excludeFields" class="conditional-fields" hidden><label><span>排除原因</span><select name="exclude_reason"><option value="">请选择</option><option>ANNOTATION_ERROR</option><option>RECORDING_BOUNDARY</option><option>MISSING_CONTEXT</option><option>NON_SPEECH_ARTIFACT</option><option>TIMESTAMP_CORRUPTION</option><option>UNRESOLVABLE</option></select></label></section>
<fieldset>
<legend>3. 活动边界是否真实? <span class="required">必填</span></legend>
<div class="choice-grid three">
<label><input type="radio" name="validity" value="valid" /><span><b>真实</b><small>有效活动边界</small></span></label>
<label><input type="radio" name="validity" value="artifact" /><span><b>伪边界</b><small>噪声、断点或错标</small></span></label>
<label><input type="radio" name="validity" value="uncertain" /><span><b>不确定</b><small>证据不足</small></span></label>
</div>
</fieldset>
<details class="boundary-details"><summary>仅在原 annotation 明显错误时校正边界</summary><label><span>可选校正(原时间始终保留)</span><select name="boundary_correction_seconds"><option value="">不校正</option><option value="-0.2">−0.2 s</option><option value="0.2">+0.2 s</option></select></label></details>
<fieldset>
<legend>4. Onset 的 floor 功能 <span class="required">必填</span></legend>
<div class="choice-grid two compact">
<label><input type="radio" name="onset_function" value="floor_taking" /><span><b>取得话轮</b></span></label>
<label><input type="radio" name="onset_function" value="backchannel" /><span><b>Backchannel</b></span></label>
<label><input type="radio" name="onset_function" value="within_turn_resume" /><span><b>句内恢复</b></span></label>
<label><input type="radio" name="onset_function" value="nonverbal" /><span><b>非语言发声</b></span></label>
<label><input type="radio" name="onset_function" value="not_applicable" /><span><b>不适用</b></span></label>
<label><input type="radio" name="onset_function" value="uncertain" /><span><b>不确定</b></span></label>
</div>
</fieldset>
<fieldset>
<legend>5. 对话指向/场景 <span class="required">必填</span></legend>
<div class="choice-grid two compact">
<label><input type="radio" name="context" value="addressed_wearer" /><span><b>对 wearer</b></span></label>
<label><input type="radio" name="context" value="third_party" /><span><b>第三方交谈</b></span></label>
<label><input type="radio" name="context" value="group" /><span><b>群体</b></span></label>
<label><input type="radio" name="context" value="unknown" /><span><b>未知</b></span></label>
</div>
</fieldset>
<fieldset>
<legend>6. Offset 的结果 <span class="required">必填</span></legend>
<div class="choice-grid two compact">
<label><input type="radio" name="offset_outcome" value="yield_to_other" /><span><b>让给 other</b></span></label>
<label><input type="radio" name="offset_outcome" value="completion_to_silence" /><span><b>结束后静音</b></span></label>
<label><input type="radio" name="offset_outcome" value="temporary_pause" /><span><b>短暂停顿</b></span></label>
<label><input type="radio" name="offset_outcome" value="backchannel_end" /><span><b>Backchannel 结束</b></span></label>
<label><input type="radio" name="offset_outcome" value="cutoff" /><span><b>截断/边界</b></span></label>
<label><input type="radio" name="offset_outcome" value="not_applicable" /><span><b>不适用</b></span></label>
<label><input type="radio" name="offset_outcome" value="uncertain" /><span><b>不确定</b></span></label>
</div>
</fieldset>
<div class="inline-fields">
<label>
<span>other 锚点校正</span>
<select name="anchor_adjustment_bins">
<option value="">无法判断</option>
<option value="-1">−1 bin(−200 ms)</option>
<option value="0" selected>原 bin</option>
<option value="1">+1 bin(+200 ms)</option>
</select>
</label>
<label>
<span>wearer 行为锚点校正</span>
<select name="behavior_adjustment_bins">
<option value="">不适用/无法判断</option>
<option value="-1">−1 bin(−200 ms)</option>
<option value="0" selected>原 bin</option>
<option value="1">+1 bin(+200 ms)</option>
</select>
</label>
<label>
<span>负区间终点校正</span>
<select name="region_end_adjustment_bins">
<option value="">不适用/无法判断</option>
<option value="-1">−1 bin(−200 ms)</option>
<option value="0" selected>原 bin</option>
<option value="1">+1 bin(+200 ms)</option>
</select>
</label>
<label>
<span>置信度</span>
<select name="confidence" required>
<option value="high">高</option>
<option value="medium" selected>中</option>
<option value="low">低</option>
</select>
</label>
</div>
<label class="text-field">
<span>审核者</span>
<input id="reviewerInput" name="reviewer" maxlength="100" placeholder="姓名或代号" required />
</label>
<label class="text-field">
<span>备注</span>
<textarea name="notes" maxlength="4000" rows="3" placeholder="记录歧义、说话对象或边界问题"></textarea>
</label>
<div id="formError" class="form-error" role="alert" hidden></div>
<div class="review-actions">
<button id="uncertainButton" class="button ghost" type="button">全部标为不确定</button>
<button id="saveButton" class="button primary" type="submit">保存并看下一个 <kbd>⌘↵</kbd></button>
</div>
</form>
<div class="shortcuts">
<span><kbd>←</kbd><kbd>→</kbd> 前后事件</span>
<span><kbd>[</kbd><kbd>]</kbd> ±200 ms</span>
<span><kbd>Space</kbd> 播放/暂停</span>
</div>
</aside>
</main>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="/app.js" defer></script>
</body>
<div class="inline-fields"><label><span>置信度</span><select name="confidence"><option value="high">高</option><option value="medium" selected>中</option><option value="low">低</option></select></label><label><span>审核者</span><input id="reviewerInput" name="reviewer" maxlength="100" placeholder="姓名或代号" required /></label></div>
<label class="text-field"><span>备注(可选)</span><textarea name="notes" maxlength="4000" rows="3" placeholder="只记录必要的歧义或证据"></textarea></label>
<div id="formError" class="form-error" role="alert" hidden></div>
<div class="review-actions"><button id="uncertainButton" class="button ghost" type="button">送二审</button><button id="saveButton" class="button primary" type="submit">保存并看下一个 <kbd>⌘↵</kbd></button></div>
</form>
<div class="shortcuts"><span><kbd>←</kbd><kbd>→</kbd> 前后事件</span><span><kbd>[</kbd><kbd>]</kbd> ±200 ms</span><span><kbd>Space</kbd> 播放/暂停</span></div>
</aside>
</main>
<div id="toast" class="toast" role="status"></div><script src="/app.js" defer></script>
</body>
</html>