// ===== State ===== let allData=[], filteredData=[], currentIndex=0; let assignmentId=null, reviewsMap={}, currentReviewStatus=null; let editMode={emotion:false, distortions:false, empathy:false}; let editData={emotion:null, distortions:null, empathy:null}; // working copies let selectingEvidence=null; // {card, itemIdx, field} const DIST_COLORS=['#fb923c','#f87171','#fbbf24','#a78bfa','#34d399']; const EMP_COLORS=['#60a5fa','#a78bfa','#34d399','#fbbf24','#fb923c','#f87171','#818cf8','#2dd4bf','#e879f9']; const INTENSITY_OPTS=['Low','Medium','High']; const DISTORTION_OPTS=[ 'All-or-Nothing Thinking', 'Overgeneralization', 'Mental Filter', 'Disqualifying the Positive', 'Mind Reading', 'Fortune Telling', 'Catastrophizing', 'Minimization', 'Emotional Reasoning', 'Should Statements', 'Labeling', 'Personalization' ]; // ===== Helpers ===== function safeJSON(s){try{return JSON.parse(s);}catch(e){return null;}} function escA(s){return (s||'').replace(/"/g,'"').replace(/'/g,''');} function escH(s){return (s||'').replace(/&/g,'&').replace(//g,'>');} function parseAnno(row){ return { emotion:safeJSON(row['Emotion']), distortions:safeJSON(row['Distortions']), empathy:safeJSON(row['Empathy_Level']) }; } function getEffectiveAnno(row){ const review=reviewsMap[currentIndex]; if(review&&review.annotations_edit){ const ed=safeJSON(review.annotations_edit); if(ed) return ed; } return parseAnno(row); } function emotionCls(e){ if(!e)return 'emotion-default'; const l=e.toLowerCase(); if(l.includes('fear'))return 'emotion-fear'; if(l.includes('sad'))return 'emotion-sadness'; if(l.includes('anger')||l.includes('angry'))return 'emotion-anger'; if(l.includes('joy')||l.includes('happy'))return 'emotion-joy'; if(l.includes('anxi'))return 'emotion-anxiety'; if(l.includes('shame'))return 'emotion-shame'; if(l.includes('depress'))return 'emotion-depression'; return 'emotion-default'; } function displayEmotion(row){ return row?.emotion_type || 'โ€”'; } function scoreColor(s,m=7){const r=s/m;return r>=.7?'#34d399':r>=.4?'#fbbf24':'#f87171';} function deepCopy(o){return JSON.parse(JSON.stringify(o));} // ===== Conversation parsing & highlights ===== function parseConv(s){ if(!s)return []; const msgs=[]; s.split('\n').forEach(line=>{ const t=line.trim(); if(!t)return; if(t.startsWith('speaker:')||t.startsWith('listener:')){ const isL=t.startsWith('listener:'); const c=t.replace(/^(speaker|listener):\s*/,''); if(c)msgs.push({role:isL?'listener':'speaker',text:c}); } else if(msgs.length>0) msgs[msgs.length-1].text+=' '+t; }); return msgs; } function applyHighlights(msgs,quotes){ return msgs.map(msg=>{ let text=msg.text; quotes.forEach(({quote,cls})=>{ if(!quote||quote==='No evidence present')return; const q=quote.replace(/^(listener|speaker):\s*/i,'').trim(); if(!q)return; const i=text.indexOf(q); if(i!==-1)text=text.slice(0,i)+``+text.slice(i,i+q.length)+''+text.slice(i+q.length); }); return {...msg,htmlText:text}; }); } function buildQuotes(anno){ const quotes=[]; (anno.distortions?.distortions||[]).forEach((d,i)=>{ if(d.evidence_quote&&d.evidence_quote!=='No evidence present') quotes.push({quote:d.evidence_quote,cls:`dist-hl-${i%3}`,type:'dist',idx:i}); }); (anno.empathy?.empathy_dimensions||[]).forEach((dim,i)=>{ if(dim.evidence_quote&&dim.evidence_quote!=='No evidence present') quotes.push({quote:dim.evidence_quote,cls:`emp-hl-${i%9}`,type:'emp',idx:i}); }); return quotes; } function scrollToEvidence(prefix,idx){ const m=document.querySelector(`.conversation-view mark.${prefix}-${idx}`); if(!m)return; document.querySelectorAll('mark.evidence-pulse').forEach(x=>x.classList.remove('evidence-pulse')); m.scrollIntoView({behavior:'smooth',block:'center'}); m.classList.add('evidence-pulse'); setTimeout(()=>m.classList.remove('evidence-pulse'),2000); } // ===== Render: Conversation ===== function renderConversation(row){ const anno=getEffectiveAnno(row); const quotes=buildQuotes(anno); const msgs=applyHighlights(parseConv(row['conversation_cut']),quotes); const view=document.getElementById('conversationView'); view.innerHTML=msgs.map(m=>`
${m.role==='listener'?'Listener':'Speaker'}
${m.htmlText||escH(m.text)}
`).join(''); view.querySelectorAll('mark.evidence-hl').forEach(mk=>{ mk.addEventListener('mouseenter',()=>showTooltip(mk,anno)); mk.addEventListener('mouseleave',hideTooltip); }); } function renderConvHeader(row){ const anno=getEffectiveAnno(row); const e=anno.emotion; const en=displayEmotion(row); const ei=e?.intensity||''; const intCls=ei.toLowerCase().includes('high')?'intensity-high':ei.toLowerCase().includes('medium')?'intensity-medium':'intensity-low'; document.getElementById('convMeta').innerHTML=` ${en} ${ei?`${ei}`:''} ${row.problem_type?`${row.problem_type}`:''} ${row.experience_type?`${row.experience_type}`:''}`; } // ===== Tooltip ===== function showTooltip(mk,anno){ const cls=Array.from(mk.classList).find(c=>c.startsWith('dist-hl-')||c.startsWith('emp-hl-')); let html=''; if(cls?.startsWith('dist-hl-')){ const i=parseInt(cls.split('-').pop()); const d=(anno.distortions?.distortions||[])[i]; if(d){const c=DIST_COLORS[i%DIST_COLORS.length];html=`
โš ๏ธ ${d.cognitive_distortion||'Distortion'}
Reasoning
${d.reasoning||'โ€”'}
`;} } else if(cls?.startsWith('emp-hl-')){ const i=parseInt(cls.split('-').pop()); const dim=(anno.empathy?.empathy_dimensions||[])[i]; if(dim){const c=EMP_COLORS[i%EMP_COLORS.length],sc=scoreColor(dim.score||0);html=`
๐Ÿ’ก ${dim.dimension_name}
Score
${dim.score}/7
Reasoning
${dim.reasoning||'โ€”'}
`;} } if(!html)return; const tip=document.getElementById('tooltip'); document.getElementById('tooltipContent').innerHTML=html; tip.classList.add('visible'); const r=mk.getBoundingClientRect(); let top=r.bottom+8,left=r.left; if(left+320>window.innerWidth)left=window.innerWidth-330; if(top+200>window.innerHeight)top=r.top-200; tip.style.top=top+'px';tip.style.left=left+'px'; } function hideTooltip(){document.getElementById('tooltip').classList.remove('visible');} // ===== Render: Emotion (View + Edit) ===== function renderEmotion(row){ const anno=getEffectiveAnno(row); const e=anno.emotion; const goldEmotion=displayEmotion(row); const predictedEmotion=e?.primary_emotion || 'โ€”'; const emotionsMatch=predictedEmotion!=='โ€”' && predictedEmotion.trim().toLowerCase()===goldEmotion.trim().toLowerCase(); const el=document.getElementById('emotionContent'); const hasEdit=!!(reviewsMap[currentIndex]?.annotations_edit); const btn=document.getElementById('editEmotionBtn'); btn.classList.toggle('active',editMode.emotion); btn.textContent=editMode.emotion?'โœ“ Done':'โœ๏ธ Edit'; if(editMode.emotion){ const d=editData.emotion; // Support both old (reasoning) and new (standard_reasoning + appraisal_reasoning) formats const stdReason = d?.standard_reasoning || d?.reasoning || ''; const appReason = d?.appraisal_reasoning || ''; el.innerHTML=`
Gold Emotion
LLM Predicted Emotion
Intensity
Specific Term
Standard Reasoning
Appraisal Reasoning
`; ['ee-intensity','ee-term','ee-std-reason','ee-app-reason'].forEach(id=>{ document.getElementById(id)?.addEventListener('input',()=>{ editData.emotion={ // Preserve the read-only LLM prediction when saving other emotion edits. primary_emotion:editData.emotion?.primary_emotion || d?.primary_emotion || '', intensity:document.getElementById('ee-intensity').value, specific_emotion_term:document.getElementById('ee-term').value, standard_reasoning:document.getElementById('ee-std-reason').value, appraisal_reasoning:document.getElementById('ee-app-reason').value, }; }); }); } else { if(!e){ el.innerHTML=`
Gold Emotion
${escH(goldEmotion)}
LLM Prediction
โ€”

No LLM annotation details.

`; return; } const intCls=(e.intensity||'').toLowerCase().includes('high')?'intensity-high':(e.intensity||'').toLowerCase().includes('medium')?'intensity-medium':'intensity-low'; // Determine which reasoning fields to show (new format takes priority) const hasNewFormat = !!(e.standard_reasoning || e.appraisal_reasoning); const stdR = e.standard_reasoning || (!hasNewFormat ? e.reasoning : ''); const appR = e.appraisal_reasoning || ''; el.innerHTML=`
Gold Emotion
${escH(goldEmotion)}
LLM Prediction
${escH(predictedEmotion)}
${emotionsMatch?'โœ“ Match':'โ‰  Different'} ${escH(e.intensity||'โ€”')} ${hasEdit?'Edited':''}
${e.specific_emotion_term?`

Term: ${e.specific_emotion_term}

`:''} ${stdR?`
Standard Reasoning

${escH(stdR)}

`:''} ${appR?`
Appraisal Reasoning

${escH(appR)}

`:''}`; } } // ===== Render: Distortions (View + Edit) ===== function renderDistortions(row){ const anno=getEffectiveAnno(row); const el=document.getElementById('distortionsContent'); const dists=editMode.distortions ? editData.distortions : (anno.distortions?.distortions||[]); const hasEdit=!!(reviewsMap[currentIndex]?.annotations_edit); const btn=document.getElementById('editDistBtn'); btn.classList.toggle('active',editMode.distortions); btn.textContent=editMode.distortions?'โœ“ Done':'โœ๏ธ Edit'; if(editMode.distortions){ if(!dists.length){ el.innerHTML=`
No distortions
`; } else { el.innerHTML=dists.map((d,i)=>{ const c=DIST_COLORS[i%DIST_COLORS.length]; return `
Evidence Quote
Reasoning
`; }).join(''); } el.innerHTML+=``; // events el.querySelectorAll('[data-d-field]').forEach(inp=>{ inp.addEventListener('input',()=>{ const idx=parseInt(inp.dataset.dIdx); const f=inp.dataset.dField; if(f==='name') editData.distortions[idx].cognitive_distortion=inp.value; else if(f==='quote') editData.distortions[idx].evidence_quote=inp.value; else if(f==='reason') editData.distortions[idx].reasoning=inp.value; }); }); el.querySelectorAll('[data-remove-dist]').forEach(btn=>{ btn.addEventListener('click',()=>{ editData.distortions.splice(parseInt(btn.dataset.removeDist),1); renderDistortions(row); }); }); el.querySelectorAll('[data-sel-card]').forEach(btn=>{ btn.addEventListener('click',()=>startSelectingEvidence(btn.dataset.selCard,parseInt(btn.dataset.selIdx),btn)); }); document.getElementById('addDistBtn')?.addEventListener('click',()=>{ editData.distortions.push({cognitive_distortion:'',evidence_quote:'',reasoning:''}); renderDistortions(row); }); } else { if(!dists.length){el.innerHTML=`
No distortions detected
`;return;} el.innerHTML=dists.map((d,i)=>{ const c=DIST_COLORS[i%DIST_COLORS.length]; const q=(d.evidence_quote||'').replace(/^(listener|speaker):\s*/i,''); return `
${d.cognitive_distortion||'โ€”'} ${hasEdit&&i===0?'Edited':''}
${q?`
${q}
`:''} ${d.reasoning?`
${d.reasoning}
`:''}
`; }).join(''); el.querySelectorAll('.distortion-item').forEach(item=>{ item.addEventListener('click',()=>scrollToEvidence('dist-hl',parseInt(item.dataset.distIdx)%3)); }); } } // ===== Render: Empathy (View + Edit) ===== function renderEmpathy(row){ const anno=getEffectiveAnno(row); const el=document.getElementById('empathyContent'); const scoreEl=document.getElementById('overallScore'); const dims=editMode.empathy ? editData.empathy : (anno.empathy?.empathy_dimensions||[]); const hasEdit=!!(reviewsMap[currentIndex]?.annotations_edit); const btn=document.getElementById('editEmpBtn'); btn.classList.toggle('active',editMode.empathy); btn.textContent=editMode.empathy?'โœ“ Done':'โœ๏ธ Edit'; if(!anno.empathy&&!editMode.empathy){el.innerHTML='

No annotation.

';scoreEl.textContent='';return;} const total=dims.reduce((s,d)=>s+(d.score||0),0); scoreEl.textContent=dims.length?`${total} / ${dims.length*7}`:''; if(editMode.empathy){ el.innerHTML=dims.map((dim,i)=>{ const c=EMP_COLORS[i%EMP_COLORS.length]; const sc=dim.score||0; return `
Score (0โ€“7)
${sc}
Evidence Quote
Reasoning
`; }).join(''); el.innerHTML+=``; // events el.querySelectorAll('[data-e-field]').forEach(inp=>{ inp.addEventListener('input',()=>{ const idx=parseInt(inp.dataset.eIdx); const f=inp.dataset.eField; if(f==='name') editData.empathy[idx].dimension_name=inp.value; else if(f==='score'){ const v=parseInt(inp.value); editData.empathy[idx].score=v; const disp=document.getElementById(`score-disp-${idx}`); if(disp){disp.textContent=v;disp.style.color=scoreColor(v);} } else if(f==='quote') editData.empathy[idx].evidence_quote=inp.value; else if(f==='reason') editData.empathy[idx].reasoning=inp.value; }); }); el.querySelectorAll('[data-remove-emp]').forEach(btn=>{ btn.addEventListener('click',()=>{editData.empathy.splice(parseInt(btn.dataset.removeEmp),1);renderEmpathy(row);}); }); el.querySelectorAll('[data-sel-card]').forEach(btn=>{ btn.addEventListener('click',()=>startSelectingEvidence(btn.dataset.selCard,parseInt(btn.dataset.selIdx),btn)); }); document.getElementById('addEmpBtn')?.addEventListener('click',()=>{ editData.empathy.push({dimension_name:'',score:0,evidence_quote:'',reasoning:''}); renderEmpathy(row); }); } else { el.innerHTML=dims.map((dim,i)=>{ const c=EMP_COLORS[i%EMP_COLORS.length]; const sc=dim.score||0; const pct=Math.min(100,(sc/7)*100); const sC=scoreColor(sc); const q=(dim.evidence_quote||'').replace(/^(listener|speaker):\s*/i,''); return `
${dim.dimension_name||'โ€”'}
${sc}
${q&&q!=='No evidence present'?`
"${q}"
`:''} ${dim.reasoning?`
${dim.reasoning}
`:''}
`; }).join(''); el.querySelectorAll('.empathy-item').forEach(item=>{ item.addEventListener('click',()=>{ item.classList.toggle('active'); scrollToEvidence('emp-hl',parseInt(item.dataset.empIdx)%9); }); }); } } // ===== Evidence Selection Mode ===== function startSelectingEvidence(card, itemIdx, triggerBtn){ selectingEvidence={card, itemIdx}; const panel=document.querySelector('.conversation-panel'); panel.classList.add('selecting-mode'); const hint=document.getElementById('selectionHint'); hint.classList.add('visible'); // Mark button as active document.querySelectorAll('.select-evidence-btn').forEach(b=>b.classList.remove('selecting')); triggerBtn?.classList.add('selecting'); } function stopSelectingEvidence(){ selectingEvidence=null; document.querySelector('.conversation-panel')?.classList.remove('selecting-mode'); const hint=document.getElementById('selectionHint'); hint?.classList.remove('visible'); document.querySelectorAll('.select-evidence-btn').forEach(b=>b.classList.remove('selecting')); } function captureSelection(){ if(!selectingEvidence)return; const sel=window.getSelection(); if(!sel||sel.rangeCount===0||sel.isCollapsed)return; const text=sel.toString().trim(); if(!text)return; const {card,itemIdx}=selectingEvidence; // Set the value in editData if(card==='distortions'&&editData.distortions[itemIdx]!==undefined){ editData.distortions[itemIdx].evidence_quote=text; } else if(card==='empathy'&&editData.empathy[itemIdx]!==undefined){ editData.empathy[itemIdx].evidence_quote=text; } sel.removeAllRanges(); stopSelectingEvidence(); // Re-render the card to update input field const row=filteredData[currentIndex]; if(card==='distortions') renderDistortions(row); else if(card==='empathy') renderEmpathy(row); } // ===== Save Annotations ===== async function saveAnnotations(){ const row=filteredData[currentIndex]; const anno=getEffectiveAnno(row); // Merge current editData into the annotation structure const toSave={ emotion: editData.emotion || anno.emotion, distortions: anno.distortions ? {...anno.distortions, distortions: editData.distortions||anno.distortions?.distortions||[]} : {distortions: editData.distortions||[]}, empathy: anno.empathy ? {...anno.empathy, empathy_dimensions: editData.empathy||anno.empathy?.empathy_dimensions||[]} : {empathy_dimensions: editData.empathy||[]} }; try { const res=await fetch('/api/reviews/annotations',{ method:'PATCH', headers:{'Content-Type':'application/json'}, body:JSON.stringify({assignment_id:assignmentId,sample_index:currentIndex,annotations:toSave}) }); const data=await res.json(); if(res.ok){ reviewsMap[currentIndex]=data; return true; } } catch(e){} return false; } // ===== Toggle Edit Mode ===== function toggleEdit(card){ const row=filteredData[currentIndex]; const anno=getEffectiveAnno(row); if(editMode[card]){ // Exiting edit mode โ€” auto-save editMode[card]=false; stopSelectingEvidence(); saveAnnotations().then(ok=>{ if(ok){ // Re-render all to show updated highlights renderAll(); } }); } else { // Entering edit mode โ€” init editData from effective annotation if(card==='emotion') editData.emotion=deepCopy(anno.emotion||{primary_emotion:'',intensity:'',specific_emotion_term:'',reasoning:''}); else if(card==='distortions') editData.distortions=deepCopy(anno.distortions?.distortions||[]); else if(card==='empathy') editData.empathy=deepCopy(anno.empathy?.empathy_dimensions||[]); editMode[card]=true; if(card==='emotion') renderEmotion(row); else if(card==='distortions') renderDistortions(row); else if(card==='empathy') renderEmpathy(row); } } // ===== Review State ===== function renderReviewState(){ const review=reviewsMap[currentIndex]; const badge=document.getElementById('reviewStatusBadge'); const acceptBtn=document.getElementById('acceptBtn'); const rejectBtn=document.getElementById('rejectBtn'); const comment=document.getElementById('reviewComment'); const submitBtn=document.getElementById('submitReviewBtn'); acceptBtn.classList.remove('active'); rejectBtn.classList.remove('active'); currentReviewStatus=null; if(review&&review.status&&review.status!=='reject'||(review&&review.status==='accept')){ // Has a real review decision } if(review){ badge.style.display='inline-block'; badge.textContent=review.status==='accept'?'โœ“ Accepted':'โœ— Rejected'; badge.className='review-status-badge '+(review.status==='accept'?'review-status-accept':'review-status-reject'); currentReviewStatus=review.status; if(review.status==='accept') acceptBtn.classList.add('active'); else rejectBtn.classList.add('active'); comment.value=review.comment||''; submitBtn.textContent='Update Review'; } else { badge.style.display='none'; comment.value=''; submitBtn.textContent='Submit Review'; } submitBtn.disabled=false; } // ===== Sample List ===== function renderSampleList(){ const list=document.getElementById('sampleList'); list.innerHTML=''; filteredData.forEach((row,i)=>{ const en=displayEmotion(row); const review=reviewsMap[i]; const dotCls=review?(review.status==='accept'?'dot-accept':'dot-reject'):'dot-pending'; const hasEdit=!!(review?.annotations_edit); const item=document.createElement('div'); item.className='sample-item'+(i===currentIndex?' active':''); item.dataset.idx=i; item.innerHTML=`
#${i+1} ${hasEdit?'โœ':''}
${en}
${row.problem_type||''}
`; item.addEventListener('click',()=>{currentIndex=i;renderAll();}); list.appendChild(item); }); } // ===== Navigation ===== function updateNav(){ document.getElementById('prevBtn').disabled=currentIndex<=0; document.getElementById('nextBtn').disabled=currentIndex>=filteredData.length-1; document.getElementById('currentIdx').textContent=currentIndex+1; document.getElementById('totalSamples').textContent=filteredData.length; document.querySelectorAll('.sample-item').forEach(it=>it.classList.toggle('active',parseInt(it.dataset.idx)===currentIndex)); document.querySelector('.sample-item.active')?.scrollIntoView({block:'nearest'}); } // ===== Render All ===== function renderAll(){ if(!filteredData.length)return; // Exit edit modes on navigation editMode={emotion:false,distortions:false,empathy:false}; stopSelectingEvidence(); const row=filteredData[currentIndex]; renderConvHeader(row); renderConversation(row); renderEmotion(row); renderDistortions(row); renderEmpathy(row); renderReviewState(); updateNav(); } // ===== Filter ===== function applyFilter(q){ q=q.toLowerCase().trim(); filteredData=q?allData.filter(row=>{ return displayEmotion(row).toLowerCase().includes(q)|| (row.problem_type||'').toLowerCase().includes(q); }):[...allData]; currentIndex=0;renderSampleList();renderAll(); } // ===== Submit Review ===== async function submitReview(){ if(!currentReviewStatus){alert('Please select Accept or Reject');return;} const comment=document.getElementById('reviewComment').value.trim(); const btn=document.getElementById('submitReviewBtn'); btn.disabled=true;btn.textContent='Submitting...'; try{ const res=await fetch('/api/reviews',{ method:'POST',headers:{'Content-Type':'application/json'}, body:JSON.stringify({assignment_id:assignmentId,sample_index:currentIndex,status:currentReviewStatus,comment}) }); const data=await res.json(); if(res.ok){ reviewsMap[currentIndex]=data; renderReviewState();renderSampleList(); btn.textContent='โœ“ Saved!'; setTimeout(()=>{ btn.textContent='Update Review';btn.disabled=false; const next=filteredData.findIndex((_,i)=>i>currentIndex&&!reviewsMap[i]); if(next!==-1){currentIndex=next;renderAll();} },800); } else {alert(data.error||'Failed');btn.textContent='Submit Review';btn.disabled=false;} } catch(e){alert('Network error');btn.textContent='Submit Review';btn.disabled=false;} } // ===== Annotation History ===== function closeHistory(){ const modal=document.getElementById('historyModal'); modal.classList.remove('visible'); modal.setAttribute('aria-hidden','true'); } function historySnapshotHtml(entry,version,row){ const a=entry.annotations||{}; const emotion=a.emotion||{}; const distortions=a.distortions?.distortions||[]; const empathy=a.empathy?.empathy_dimensions||[]; const when=entry.created_at ? new Date(entry.created_at).toLocaleString() : 'Unknown time'; const emotionParts=[ displayEmotion(row), emotion.specific_emotion_term, emotion.intensity ].filter(Boolean).map(v=>escH(String(v))); const distortionNames=distortions .map(d=>d.cognitive_distortion).filter(Boolean).map(v=>escH(String(v))); const empathyNames=empathy.map(d=>{ const name=escH(String(d.dimension_name||'Dimension')); return d.score===undefined||d.score===null ? name : `${name} (${escH(String(d.score))}/7)`; }); const fullJson=escH(JSON.stringify(a,null,2)); return `
Version ${version} ${escH(entry.edited_by||'unknown')} ยท ${escH(when)}
Emotion
${emotionParts.join(' ยท ')||'โ€”'}
${emotion.standard_reasoning?`
Standard Reasoning
${escH(emotion.standard_reasoning)}
`:''} ${emotion.appraisal_reasoning?`
Appraisal Reasoning
${escH(emotion.appraisal_reasoning)}
`:''}
Distortions (${distortions.length})
${distortionNames.join(', ')||'None'}
Empathy dimensions (${empathy.length})
${empathyNames.join(', ')||'None'}
View full snapshot
${fullJson}
`; } async function openHistory(){ if(!assignmentId||!filteredData.length)return; const modal=document.getElementById('historyModal'); const content=document.getElementById('historyContent'); const row=filteredData[currentIndex]; document.getElementById('historySubtitle').textContent=`Sample #${currentIndex+1} ยท ${displayEmotion(row)}`; content.innerHTML='
Loading history...
'; modal.classList.add('visible'); modal.setAttribute('aria-hidden','false'); try{ const res=await fetch(`/api/assignments/${assignmentId}/samples/${currentIndex}/annotation-history`); const entries=await res.json(); if(!res.ok)throw new Error(entries.error||'Failed to load history'); if(!entries.length){ content.innerHTML='
No edit history yet. A version will appear after an annotation is changed and saved.
'; return; } content.innerHTML=entries.map((entry,i)=>historySnapshotHtml(entry,entries.length-i,row)).join(''); }catch(err){ content.innerHTML=`
${escH(err.message||'Failed to load history')}
`; } } // ===== Load Data ===== async function loadAssignmentData(){ try{ let fileId,filename; const r1=await fetch('/api/my-assignments'); const myAssign=await r1.json(); let a=myAssign.find(x=>x.id===assignmentId); if(!a){ const r2=await fetch('/api/assignments'); if(r2.ok){const all=await r2.json();a=all.find(x=>x.id===assignmentId);} } if(!a){document.getElementById('reviewSubtitle').textContent='Assignment not found';return;} fileId=a.file_id;filename=a.filename; const r3=await fetch(`/api/files/${fileId}/data`); const fd=await r3.json(); allData=fd.samples||[];filteredData=[...allData]; const r4=await fetch(`/api/assignments/${assignmentId}/reviews`); reviewsMap=await r4.json(); document.getElementById('reviewTitle').textContent='Review'; document.getElementById('reviewSubtitle').textContent=filename||'Annotation Review'; currentIndex=0;renderSampleList();renderAll(); } catch(err){console.error(err);document.getElementById('reviewSubtitle').textContent='Failed to load';} } // ===== DOMContentLoaded ===== document.addEventListener('DOMContentLoaded',async()=>{ const parts=window.location.pathname.split('/'); assignmentId=parseInt(parts[parts.length-1]); if(isNaN(assignmentId))return; try{const m=await fetch('/api/me').then(r=>r.json());document.getElementById('userBadge').textContent=m.username;}catch(e){} document.getElementById('logoutBtn').addEventListener('click',async()=>{await fetch('/api/logout',{method:'POST'});window.location.href='/login';}); document.getElementById('prevBtn').addEventListener('click',()=>{if(currentIndex>0){currentIndex--;renderAll();}}); document.getElementById('nextBtn').addEventListener('click',()=>{if(currentIndexapplyFilter(e.target.value)); document.getElementById('acceptBtn').addEventListener('click',()=>{currentReviewStatus='accept';document.getElementById('acceptBtn').classList.add('active');document.getElementById('rejectBtn').classList.remove('active');}); document.getElementById('rejectBtn').addEventListener('click',()=>{currentReviewStatus='reject';document.getElementById('rejectBtn').classList.add('active');document.getElementById('acceptBtn').classList.remove('active');}); document.getElementById('submitReviewBtn').addEventListener('click',submitReview); document.getElementById('historyBtn').addEventListener('click',openHistory); document.getElementById('historyCloseBtn').addEventListener('click',closeHistory); document.getElementById('historyModal').addEventListener('click',e=>{ if(e.target===e.currentTarget)closeHistory(); }); // Edit toggle buttons ['editEmotionBtn','editDistBtn','editEmpBtn'].forEach(id=>{ document.getElementById(id)?.addEventListener('click',()=>{ const card={'editEmotionBtn':'emotion','editDistBtn':'distortions','editEmpBtn':'empathy'}[id]; toggleEdit(card); }); }); // Capture text selection from conversation document.getElementById('conversationView').addEventListener('mouseup',()=>{ if(selectingEvidence) captureSelection(); }); // Escape key cancels selection document.addEventListener('keydown',e=>{ if(e.key==='Escape'){ if(document.getElementById('historyModal').classList.contains('visible'))closeHistory(); else stopSelectingEvidence(); return; } if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA'||e.target.tagName==='SELECT')return; if(e.key==='ArrowLeft'&¤tIndex>0){currentIndex--;renderAll();} if(e.key==='ArrowRight'&¤tIndexr.json()).then(data=>{ const blob=new Blob([JSON.stringify(data,null,2)],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=`export_assignment_${assignmentId}.json`; a.click();URL.revokeObjectURL(a.href); }).catch(e=>alert('Export failed')); } document.getElementById('exportMenu').style.display='none'; } // Wire up export buttons after DOM ready document.addEventListener('DOMContentLoaded',()=>{ const exportBtn=document.getElementById('exportBtn'); const exportMenu=document.getElementById('exportMenu'); if(exportBtn&&exportMenu){ exportBtn.addEventListener('click',(e)=>{ e.stopPropagation(); exportMenu.style.display=exportMenu.style.display==='none'?'block':'none'; }); document.getElementById('exportJSON')?.addEventListener('click',()=>doExport('json')); document.getElementById('exportCSV')?.addEventListener('click',()=>doExport('csv')); // Close menu on outside click document.addEventListener('click',()=>{exportMenu.style.display='none';}); } });