feat: complete prediction UI feedback tasks (11.2, 11.4, 11.5)

- Implement disagreement visual highlighting with distinct colors
  - Yellow highlight for 'missed_by_human' predictions
  - Orange for 'label_mismatch' disagreements
  - Warning icon on disagreement markers
- Add click-to-convert prediction feedback
  - Click disagreement predictions to create span annotations
  - Auto-fill with predicted label and times
  - Set source as 'model_confirmed' or 'model_corrected'
- Add dismiss action for false positive predictions
  - Alt+Click or Ctrl+Click to dismiss predictions
  - Saves negative annotation with label 'O'
  - Records original prediction in model_prediction field
- Filter predictions when 'Show only disagreements' is enabled
This commit is contained in:
Marko Djordjevic 2026-02-16 11:40:55 +01:00
parent a18c6d110a
commit 65f00e6ce7
13 changed files with 905 additions and 11 deletions

View file

@ -306,6 +306,83 @@ export default function Home() {
setSelectedSpanId(spanId);
};
// Handle prediction click to convert to annotation
const handlePredictionClick = useCallback(async (span: PredictionSpan, disagreementType: string | null) => {
if (!activeChartId) return;
// Find the span label type that matches the prediction label
const matchingLabelType = spanLabelTypes.find((lt) => lt.name === span.label);
if (!matchingLabelType) {
console.warn(`No span label type found for prediction label: ${span.label}`);
return;
}
// Create span annotation from prediction
try {
const response = await fetch('/api/span-annotations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chart_id: activeChartId,
start_time: span.start_time,
end_time: span.end_time,
label: span.label,
confidence: 3, // Default confidence for model-confirmed annotations
source: disagreementType === 'label_mismatch' ? 'model_corrected' : 'model_confirmed',
model_prediction: {
label: span.label,
confidence: span.avg_confidence,
},
}),
});
if (response.ok) {
await fetchSpanAnnotations(activeChartId);
// Show a brief notification (you could add a toast notification here)
console.log(`Created span annotation from prediction: ${span.label}`);
} else {
console.error('Failed to create span annotation from prediction');
}
} catch (error) {
console.error('Error creating span annotation from prediction:', error);
}
}, [activeChartId, spanLabelTypes, fetchSpanAnnotations]);
// Handle prediction dismiss (save as negative annotation with label "O")
const handlePredictionDismiss = useCallback(async (span: PredictionSpan, disagreementType: string | null) => {
if (!activeChartId) return;
// Create negative annotation with label "O"
try {
const response = await fetch('/api/span-annotations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chart_id: activeChartId,
start_time: span.start_time,
end_time: span.end_time,
label: 'O', // "O" means "not a pattern"
confidence: 5, // High confidence for explicit user correction
source: 'human_correction',
model_prediction: {
label: span.label,
confidence: span.avg_confidence,
},
}),
});
if (response.ok) {
await fetchSpanAnnotations(activeChartId);
console.log(`Dismissed prediction as "not a pattern": ${span.label}`);
} else {
console.error('Failed to save negative annotation');
}
} catch (error) {
console.error('Error saving negative annotation:', error);
}
}, [activeChartId, fetchSpanAnnotations]);
const handleDeleteSpan = async (spanId: number) => {
try {
const response = await fetch(`/api/span-annotations/${spanId}`, {
@ -715,6 +792,8 @@ export default function Home() {
modelInfo={predictionState.modelInfo}
predictionSummary={predictionSummary}
showOnlyDisagreements={showOnlyDisagreements}
onPredictionClick={handlePredictionClick}
onPredictionDismiss={handlePredictionDismiss}
/>
</main>
</div>