'use strict';
const $=id=>document.getElementById(id);
const COLORS=['#2563eb','#db2777','#16a34a','#ea580c','#7c3aed','#0891b2','#ca8a04','#dc2626'];
const LABELS={matched:'模型对应候选',missing_mask:'缺 mask · 待补',not_visible_in_reference:'参考帧未能定位',mask_quality_issue:'mask 质量待审',ambiguous:'身份对应含糊',requires_mask_union:'多 mask 分组待审'};
const REVIEW={unreviewed:'尚未审核',reviewed:'已审核并记录',needs_review:'需修改',reject:'不采用'};
const KEY='videoglamm-activitynet-object20-review-v1';
const DATA_SHA='6e112bf37251619cc39a6ca58897f1c2abe85c486daef1a4dd345d91f1f8e208';
const state={rows:[],filtered:[],cur:null,group:null,mask:null,frame:0,reviews:{},timer:null};
const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const src=file=>`${state.cur.asset_dir}/${file}`;
const currentGroup=()=>state.cur.groups.find(g=>g.group_id===state.group);
const groupColor=g=>state.cur.source_masks.find(o=>o.mask_id===g.mask_id)?.color||'#a16207';
function stop(){if(state.timer)clearInterval(state.timer);state.timer=null;$('play').textContent='播放采样帧';}
function reviewOf(id){return state.reviews[id]||{status:'unreviewed',note:''};}
function filterRows(keep=true){
const q=$('search').value.trim().toLowerCase(),f=$('filter').value,id=keep?state.cur?.sample_id:null;
state.filtered=state.rows.filter(r=>(f==='all'||reviewOf(r.sample_id).status===f)&&(!q||`${r.sample_id} ${r.caption} ${r.groups.map(g=>g.entity_label).join(' ')}`.toLowerCase().includes(q)));
$('list-count').textContent=`${state.filtered.length} / ${state.rows.length} samples`;
$('review-count').textContent=`本浏览器:${Object.values(state.reviews).filter(r=>r.status!=='unreviewed').length} / 20 已记录`;
renderList();
if(!state.filtered.length){stop();state.cur=null;$('entry').hidden=true;$('loading').hidden=false;$('loading').textContent='没有符合筛选的样本。';$('prev').disabled=$('next').disabled=true;return;}
if(!id||!state.filtered.some(r=>r.sample_id===id))load(state.filtered[0].sample_id);else renderList();
}
function renderList(){
$('entry-list').innerHTML=state.filtered.map(r=>``).join('');
$('entry-list').querySelectorAll('button').forEach(b=>b.onclick=()=>load(b.dataset.id));
const i=state.filtered.indexOf(state.cur);$('prev').disabled=i<=0;$('next').disabled=i<0||i>=state.filtered.length-1;
}
function load(id){
stop();state.cur=state.rows.find(r=>r.sample_id===id);const r=state.cur;state.group=null;state.frame=r.reference_frame_id;state.mask=r.source_masks[0]?.mask_id||null;
$('mode').value='all';$('group-filter').value='all';$('loading').hidden=true;$('entry').hidden=false;
$('title').textContent=`${String(r.selection_index).padStart(2,'0')} · ${r.sample_id}`;
$('meta').textContent=`10 张采样 RGB · ${r.source_masks.length} 个源 mask · ${r.groups.length} 个实体组 · 片段 ${r.source_timestamps.map(t=>t.toFixed(2)).join('–')} s`;
$('badges').innerHTML='字符 span 校验通过部分对应 · 待人工审';
$('mask').innerHTML=r.source_masks.map(o=>``).join('');
$('objects').innerHTML=r.source_masks.map((o,i)=>``).join('');
$('objects').querySelectorAll('button').forEach(b=>b.onclick=()=>inspectMask(b.dataset.mask));
$('frames').innerHTML=Array.from({length:10},(_,i)=>``).join('');
$('frames').querySelectorAll('button').forEach(b=>b.onclick=()=>{stop();state.frame=Number(b.dataset.frame);renderMedia();});
const duration=r.source_timestamps[1]-r.source_timestamps[0];$('timing').textContent=`按源采样公式推算:间隔约 ${(duration/10).toFixed(3)} s(约 ${(10/duration).toFixed(3)} 张/秒);时间标签是估计值。`;
$('issues').innerHTML=[...r.issues.map(i=>`
${esc(i.type)}${i.text?' · '+esc(i.text):''}
${esc(i.description)}
`),...r.model_review_reasons.map(t=>`${esc(t)}
`),...r.validation.warnings.map(t=>`本地检查:${esc(typeof t==='string'?t:JSON.stringify(t))}
`),...r.local_adjustments.map(t=>`确定性整理(原模型输出保留):${esc(JSON.stringify(t))}
`)].join('')||'没有额外问题记录;仍需人工复核实体完整性及对应。
';
const rev=reviewOf(id);$('review-status').value=rev.status;$('note').value=rev.note;$('saved').textContent=rev.updated_at?'本浏览器保存于 '+new Date(rev.updated_at).toLocaleString():'';
$('original-refined').textContent=r.refined_caption_original;$('original-new').textContent=r.new_caption_original||'原字段不存在';
$('raw-json').textContent=JSON.stringify(r,null,2);
$('links').innerHTML=`源完整 JSON未改动的 GPT 输出`;
$('gpt-overlay').src=src('mask_overlay.jpg');$('gpt-identities').src=src('identity_sheet.jpg');$('gpt-binary').src=src('binary_mask_sheet.png');
renderList();renderCaption();renderGroups();renderDetail();renderMedia();window.scrollTo(0,0);
}
function renderCaption(){
const r=state.cur,chars=Array.from(r.caption),spans=[];
r.groups.forEach((g,gi)=>g.mentions.forEach(m=>{if(m.span)spans.push({s:m.span[0],e:m.span[1],g,gi});}));
const pts=[...new Set([0,chars.length,...spans.flatMap(m=>[m.s,m.e])])].sort((a,b)=>a-b);$('caption').replaceChildren();
for(let i=0;im.s<=s&&e<=m.e),t=chars.slice(s,e).join('');
if(!active.length){$('caption').append(document.createTextNode(t));continue;}
const selected=active.find(m=>m.g.group_id===state.group),a=selected||active[0],wrap=document.createElement('span');
wrap.className='mention'+(selected?' selected':'');wrap.style.setProperty('--c',groupColor(a.g));wrap.title=active.map(m=>`${m.g.group_id} · ${m.g.entity_label} · ${LABELS[m.g.grounding_status]}`).join('\n');
wrap.append(document.createTextNode(t));const sup=document.createElement('sup');sup.textContent=[...new Set(active.map(m=>m.g.group_id))].join(',');wrap.append(sup);
wrap.onclick=()=>{const ids=[...new Set(active.map(m=>m.g.group_id))];selectGroup(ids[(ids.indexOf(state.group)+1)%ids.length]);};$('caption').append(wrap);
}
const g=currentGroup();$('selected-summary').textContent=g?`选中 ${g.group_id} · ${g.entity_label} · ${LABELS[g.grounding_status]};高亮包括该组全部提及。共享 / 重叠 span 可反复点击切换,或在下方直接选择组。`:'候选对应的文字与源 mask 同色;棕色为待补 / 待审。跨组 span 允许重叠,角标列出全部组。';
}
function selectGroup(id){
stop();state.group=id;const g=currentGroup();state.frame=state.cur.reference_frame_id;state.mask=g.mask_id||null;$('mode').value=g.mask_id?'selected':'rgb';
renderCaption();renderGroups();renderDetail();renderMedia();
}
function renderGroups(){
const filter=$('group-filter').value;
$('groups').innerHTML=state.cur.groups.map((g,i)=>({g,i})).filter(({g})=>filter==='all'||(filter==='matched'?g.grounding_status==='matched':g.grounding_status!=='matched')).map(({g,i})=>``).join('');
$('groups').querySelectorAll('button').forEach(b=>b.onclick=()=>selectGroup(b.dataset.group));
}
function renderDetail(){
const g=currentGroup();if(!g){$('group-detail').innerHTML='点击 caption 或下方实体组查看对应。原始 mask 清单包含全部源 mask,可逐个检查。
';$('mask-preview').replaceChildren();return;}
$('group-detail').innerHTML=`${esc(g.group_id+' · '+g.entity_label)}${LABELS[g.grounding_status]} · ${esc(g.visibility)} · 模型信心 ${esc(g.confidence)}
${esc(g.reason)}
${g.mentions.map(m=>`${esc(m.text)} · [${m.span?m.span.join(', '):'待确定'})
`).join('')}`;
$('mask-preview').innerHTML=g.mask_id?`该组的模型候选对应:${g.mask_id}(非人工认证)
`:`此实体没有已对应的源 mask。text 已保留,待补 / 待审。${g.candidate_mask_ids.length?' 候选 ID:'+g.candidate_mask_ids.join(', ')+';可在上方原始 mask 清单中独立检查。':''}
`;
}
function inspectMask(id){stop();state.mask=id;state.frame=state.cur.reference_frame_id;$('mode').value='selected';renderMedia();}
function renderMedia(){
const r=state.cur,ref=state.frame===r.reference_frame_id,mode=$('mode').value,mid=state.mask;
let file=String(state.frame).padStart(2,'0')+'.jpg',message='原始 RGB';
if(ref&&mode==='all'){file='all_overlay.jpg';message=`全部 ${r.source_masks.length} 个源 mask 叠加(颜色见清单)`;}
if(ref&&mode==='selected'){if(mid){file=mid+'_overlay.jpg';message=`独立检查源 mask ${mid} · 叠加显示`;}else message='当前实体无对应 mask · 显示参考 RGB';}
if(ref&&mode==='binary'){if(mid){file=mid+'_mask.png';message=`独立检查源 mask ${mid} · 二值图`;}else message='当前实体无对应 mask · 显示参考 RGB';}
if(!ref)message='此采样帧无源 mask / bbox · 仅显示 RGB';
$('big-image').src=src(file);$('big-image').alt=`${r.sample_id} 帧 ${state.frame} · ${message}`;
$('frame-state').textContent=`帧 ${String(state.frame).padStart(2,'0')} / 09 · 估计 ${r.estimated_frame_times_s[state.frame].toFixed(3)} s · ${ref?'MASK 参考帧 · ':''}${message}`;
$('mask').value=mid||'';
$('frames').querySelectorAll('button').forEach(b=>{b.classList.toggle('active',Number(b.dataset.frame)===state.frame);b.setAttribute('aria-pressed',String(Number(b.dataset.frame)===state.frame));});
$('objects').querySelectorAll('button').forEach(b=>b.classList.toggle('active',ref&&mode!=='all'&&mode!=='rgb'&&mid===b.dataset.mask));
}
function persistReview(){
if(!state.cur)return;state.reviews[state.cur.sample_id]={status:$('review-status').value,note:$('note').value,updated_at:new Date().toISOString()};
try{localStorage.setItem(KEY,JSON.stringify({dataset_sha256:DATA_SHA,reviews:state.reviews}));$('saved').textContent='已保存在当前浏览器。请导出备份或汇总。';}catch(e){$('saved').textContent='浏览器保存失败;请立即导出审核备份。';}
renderList();$('review-count').textContent=`本浏览器:${Object.values(state.reviews).filter(r=>r.status!=='unreviewed').length} / 20 已记录`;
}
$('search').oninput=()=>filterRows();$('filter').onchange=()=>filterRows();
$('prev').onclick=()=>load(state.filtered[Math.max(0,state.filtered.indexOf(state.cur)-1)].sample_id);
$('next').onclick=()=>load(state.filtered[Math.min(state.filtered.length-1,state.filtered.indexOf(state.cur)+1)].sample_id);
$('all-groups').onclick=()=>{state.group=null;renderCaption();renderGroups();renderDetail();};
$('group-filter').onchange=renderGroups;
$('reference').onclick=()=>{stop();state.frame=state.cur.reference_frame_id;renderMedia();};
$('mode').onchange=()=>{stop();if($('mode').value!=='rgb')state.frame=state.cur.reference_frame_id;renderMedia();};
$('mask').onchange=()=>inspectMask($('mask').value);
$('play').onclick=()=>{if(state.timer){stop();return;}state.frame=0;$('mode').value='rgb';renderMedia();$('play').textContent='暂停播放';state.timer=setInterval(()=>{if(state.frame>=9){stop();return;}state.frame++;renderMedia();},500);};
$('review-status').onchange=persistReview;$('note').oninput=persistReview;
$('export').onclick=()=>{const x={schema:'videoglamm-activitynet-object20-review-v1',dataset_sha256:DATA_SHA,exported_at:new Date().toISOString(),storage:'browser-local',source_annotations_unchanged:true,reviews:state.reviews};const u=URL.createObjectURL(new Blob([JSON.stringify(x,null,2)],{type:'application/json'})),a=document.createElement('a');a.href=u;a.download='videoglamm-activitynet-object20-review.json';a.click();setTimeout(()=>URL.revokeObjectURL(u),2000);};
$('import').onclick=()=>$('import-file').click();
$('import-file').onchange=async()=>{try{const f=$('import-file').files[0];if(!f)return;const x=JSON.parse(await f.text());if(x.dataset_sha256!==DATA_SHA||!x.reviews||typeof x.reviews!=='object')throw new Error('数据版本或文件格式不匹配');const accepted={};for(const [id,r]of Object.entries(x.reviews)){if(!state.rows.some(a=>a.sample_id===id)||!r||!Object.hasOwn(REVIEW,r.status)||typeof r.note!=='string')throw new Error('存在无效样本或审核字段');accepted[id]={status:r.status,note:r.note,updated_at:r.updated_at||new Date().toISOString()};}for(const [id,r]of Object.entries(accepted)){const old=state.reviews[id];if(!old||Date.parse(r.updated_at)>=Date.parse(old.updated_at))state.reviews[id]=r;}localStorage.setItem(KEY,JSON.stringify({dataset_sha256:DATA_SHA,reviews:state.reviews}));filterRows();if(state.cur)load(state.cur.sample_id);alert('已合并审核记录;相同样本保留更新时间较新的记录。');}catch(e){alert('导入失败:'+e.message);}finally{$('import-file').value='';}};
async function init(){
const r=await fetch('data/annotations.json');if(!r.ok)throw new Error(`标注载入失败:${r.status}`);state.rows=await r.json();
try{const saved=JSON.parse(localStorage.getItem(KEY)||'null');if(saved?.dataset_sha256===DATA_SHA)state.reviews=Object.fromEntries(Object.entries(saved.reviews||{}).filter(([id,v])=>state.rows.some(r=>r.sample_id===id)&&v&&Object.hasOwn(REVIEW,v.status)&&typeof v.note==='string'));}catch(e){$('processing').textContent='浏览器存储不可用,请导出审核。';}
filterRows(false);
const receipt=await fetch('data/processing_receipt.json').then(r=>r.json());$('processing').textContent=`处理状态:${receipt.status} · ${receipt.request_counts.completed}/20 完成 · ${receipt.request_counts.failed} 失败。`;
}
init().catch(e=>{$('loading').hidden=false;$('loading').textContent=e.message;});