Spaces:
Runtime error
Runtime error
| "use client"; | |
| import { useState } from 'react'; | |
| const TAG_STYLES = { | |
| named: { color: '#10b981', bg: '#10b98120', label: 'Named' }, | |
| descriptive: { color: '#f59e0b', bg: '#f59e0b20', label: 'Descriptive' }, | |
| vague: { color: '#a78bfa', bg: '#a78bfa20', label: 'Vague' }, | |
| 'non-dataset': { color: '#64748b', bg: '#64748b20', label: 'Non-Dataset' }, | |
| }; | |
| const TAG_OPTIONS = ['named', 'descriptive', 'vague', 'non-dataset']; | |
| export default function AnnotationPanel({ | |
| isOpen, | |
| onClose, | |
| datasets, // ALL datasets on current page (model + human) | |
| annotatorName, // current user's name | |
| onValidate, // (datasetIdx, updates) => void | |
| onDelete, | |
| }) { | |
| const [validatingIdx, setValidatingIdx] = useState(null); | |
| const [validationNotes, setValidationNotes] = useState(''); | |
| const [editingTagIdx, setEditingTagIdx] = useState(null); | |
| const [editTag, setEditTag] = useState(''); | |
| const [confirmDelete, setConfirmDelete] = useState(null); | |
| const startValidation = (idx, prefillNotes = '') => { | |
| setValidatingIdx(idx); | |
| setValidationNotes(prefillNotes); | |
| }; | |
| const submitValidation = (ds, idx, verdict) => { | |
| onValidate(ds._rawIndex ?? idx, { | |
| human_validated: true, | |
| human_verdict: verdict, | |
| human_notes: validationNotes.trim() || null, | |
| annotator: annotatorName || 'user', | |
| validated_at: new Date().toISOString(), | |
| }); | |
| setValidatingIdx(null); | |
| setValidationNotes(''); | |
| }; | |
| const startEditTag = (idx, currentTag) => { | |
| setEditingTagIdx(idx); | |
| setEditTag(currentTag); | |
| }; | |
| const saveEditTag = (ds, idx) => { | |
| onValidate(ds._rawIndex ?? idx, { dataset_tag: editTag }); | |
| setEditingTagIdx(null); | |
| setEditTag(''); | |
| }; | |
| const handleDelete = (ds, idx) => { | |
| if (confirmDelete === idx) { | |
| onDelete(ds, idx); | |
| setConfirmDelete(null); | |
| } else { | |
| setConfirmDelete(idx); | |
| setTimeout(() => setConfirmDelete(prev => prev === idx ? null : prev), 3000); | |
| } | |
| }; | |
| return ( | |
| <> | |
| {isOpen && <div className="panel-backdrop" onClick={onClose} />} | |
| <div className={`annotation-panel ${isOpen ? 'open' : ''}`}> | |
| <div className="panel-header"> | |
| <h3>Data Mentions</h3> | |
| <span className="panel-count">{datasets.length}</span> | |
| <button className="panel-close" onClick={onClose}>×</button> | |
| </div> | |
| <div className="panel-body"> | |
| {datasets.length === 0 ? ( | |
| <div className="panel-empty"> | |
| <p>No datasets detected on this page.</p> | |
| </div> | |
| ) : ( | |
| datasets.map((ds, i) => { | |
| const text = ds.dataset_name?.text || ''; | |
| const tag = ds.dataset_tag || 'named'; | |
| const style = TAG_STYLES[tag] || TAG_STYLES.named; | |
| const isHuman = !!ds.annotator; | |
| // Per-annotator validation: look up current user's entry | |
| const myValidation = (ds.validations || []).find(v => v.annotator === annotatorName); | |
| const isValidated = myValidation?.human_validated === true; | |
| const humanVerdict = myValidation?.human_verdict; | |
| const humanNotes = myValidation?.human_notes; | |
| const judgeVerdict = ds.dataset_name?.judge_verdict; | |
| const judgeTag = ds.dataset_name?.judge_tag; | |
| const isValidating = validatingIdx === i; | |
| const isEditingTag = editingTagIdx === i; | |
| return ( | |
| <div | |
| key={`${text}-${ds.dataset_name?.start}-${i}`} | |
| className={`panel-annotation-card ${isValidated ? (humanVerdict ? 'validated-correct' : 'validated-wrong') : ''}`} | |
| > | |
| {/* Top row: tag + source */} | |
| <div className="panel-card-top"> | |
| {isEditingTag ? ( | |
| <div className="inline-edit"> | |
| <select | |
| className="form-select-small" | |
| value={editTag} | |
| onChange={(e) => setEditTag(e.target.value)} | |
| > | |
| {TAG_OPTIONS.map(t => ( | |
| <option key={t} value={t}> | |
| {TAG_STYLES[t]?.label || t} | |
| </option> | |
| ))} | |
| </select> | |
| <button className="btn-panel save" onClick={() => saveEditTag(ds, i)}>β</button> | |
| <button className="btn-panel" onClick={() => setEditingTagIdx(null)}>β</button> | |
| </div> | |
| ) : ( | |
| <span | |
| className="annotation-tag-badge clickable" | |
| style={{ color: style.color, backgroundColor: style.bg }} | |
| onClick={() => startEditTag(i, tag)} | |
| title="Click to change tag" | |
| > | |
| {style.label} | |
| </span> | |
| )} | |
| <span className="panel-card-source"> | |
| {isHuman ? `π€ ${ds.annotator}` : 'π€ model'} | |
| </span> | |
| </div> | |
| {/* Dataset text */} | |
| <p className="panel-card-text">"{text}"</p> | |
| {/* Judge info (for model extractions) */} | |
| {judgeTag && ( | |
| <div className="panel-card-judge"> | |
| <span className={`judge-verdict ${judgeVerdict ? 'correct' : 'wrong'}`}> | |
| Judge: {judgeVerdict ? 'β' : 'β'} | |
| </span> | |
| <span className="judge-tag">{judgeTag}</span> | |
| </div> | |
| )} | |
| {/* Position info */} | |
| {ds.dataset_name?.start != null && ( | |
| <span className="panel-card-position"> | |
| chars {ds.dataset_name.start}β{ds.dataset_name.end} | |
| </span> | |
| )} | |
| {/* Existing validation status (your own) */} | |
| {isValidated && ( | |
| <div className={`validation-status ${humanVerdict ? 'correct' : 'wrong'}`}> | |
| {humanVerdict ? 'β Validated correct' : 'β Marked incorrect'} | |
| <span className="validation-by"> by you</span> | |
| {humanNotes && ( | |
| <p className="validation-notes">Note: {humanNotes}</p> | |
| )} | |
| </div> | |
| )} | |
| {/* Validation UI */} | |
| {isValidating ? ( | |
| <div className="validation-form"> | |
| <textarea | |
| className="validation-notes-input" | |
| placeholder="Optional notes..." | |
| value={validationNotes} | |
| onChange={(e) => setValidationNotes(e.target.value)} | |
| rows={2} | |
| /> | |
| <div className="validation-buttons"> | |
| <button | |
| className="btn-panel correct" | |
| onClick={() => submitValidation(ds, i, true)} | |
| > | |
| β Correct | |
| </button> | |
| <button | |
| className="btn-panel wrong" | |
| onClick={() => submitValidation(ds, i, false)} | |
| > | |
| β Wrong | |
| </button> | |
| <button | |
| className="btn-panel" | |
| onClick={() => setValidatingIdx(null)} | |
| > | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| ) : ( | |
| <div className="panel-card-actions"> | |
| <button | |
| className="btn-panel validate" | |
| onClick={() => startValidation(i, humanNotes || '')} | |
| > | |
| {isValidated ? 'π Re-validate' : 'π·οΈ Validate'} | |
| </button> | |
| {isHuman && ( | |
| <button | |
| className={`btn-panel delete ${confirmDelete === i ? 'confirming' : ''}`} | |
| onClick={() => handleDelete(ds, i)} | |
| > | |
| {confirmDelete === i ? 'β Confirm?' : 'π Delete'} | |
| </button> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }) | |
| )} | |
| </div> | |
| </div> | |
| </> | |
| ); | |
| } | |