diff --git a/openspec/changes/span-annotation/tasks.md b/openspec/changes/span-annotation/tasks.md index ef6eca4..aa523ed 100644 --- a/openspec/changes/span-annotation/tasks.md +++ b/openspec/changes/span-annotation/tasks.md @@ -30,11 +30,11 @@ ## 5. Span Tool State & Integration -- [ ] 5.1 Extend `activeTool` type in `page.tsx` to include `'span'` tool mode -- [ ] 5.2 Add span-related state to `page.tsx`: `spanAnnotations[]`, `selectedSpanId`, `spanLabelTypes[]` -- [ ] 5.3 Add `fetchSpanAnnotations(chartId)` and `fetchSpanLabelTypes()` data fetching functions -- [ ] 5.4 Load span annotations and label types when chart changes (alongside existing annotation loading) -- [ ] 5.5 Add "Span" tool button to Toolbox component alongside existing tools +- [x] 5.1 Extend `activeTool` type in `page.tsx` to include `'span'` tool mode +- [x] 5.2 Add span-related state to `page.tsx`: `spanAnnotations[]`, `selectedSpanId`, `spanLabelTypes[]` +- [x] 5.3 Add `fetchSpanAnnotations(chartId)` and `fetchSpanLabelTypes()` data fetching functions +- [x] 5.4 Load span annotations and label types when chart changes (alongside existing annotation loading) +- [x] 5.5 Add "Span" tool button to Toolbox component alongside existing tools ## 6. Two-Click Span Selection & Preview diff --git a/src/app/page.tsx b/src/app/page.tsx index 22b23ce..48e1b3e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -21,8 +21,33 @@ interface Annotation { created_at: number; } +interface SpanAnnotation { + id: number; + chart_id: number; + start_time: number; + end_time: number; + label: string; + confidence: number | null; + outcome: string | null; + notes: string | null; + sub_spans: any; + color: string; + created_at: number; +} + +interface SpanLabelType { + id: number; + name: string; + display_name: string; + color: string; + hotkey: string | null; + is_active: number; + sort_order: number; + created_at: number; +} + export default function Home() { - const [activeTool, setActiveTool] = useState(null); + const [activeTool, setActiveTool] = useState(null); const [selectedColor, setSelectedColor] = useState('#3b82f6'); const [selectedLabelId, setSelectedLabelId] = useState(null); const [annotations, setAnnotations] = useState([]); @@ -30,6 +55,11 @@ export default function Home() { const [activeChartId, setActiveChartId] = useState(null); const chartRef = useRef(null); + // Span annotation state + const [spanAnnotations, setSpanAnnotations] = useState([]); + const [selectedSpanId, setSelectedSpanId] = useState(null); + const [spanLabelTypes, setSpanLabelTypes] = useState([]); + // Fetch charts list const fetchCharts = useCallback(async () => { try { @@ -58,25 +88,54 @@ export default function Home() { } }, []); - // Fetch charts on mount, auto-select the most recent + // Fetch span annotations for active chart + const fetchSpanAnnotations = useCallback(async (chartId: number | null) => { + if (!chartId) { + setSpanAnnotations([]); + return; + } + try { + const response = await fetch(`/api/span-annotations?chartId=${chartId}`); + const data = await response.json(); + setSpanAnnotations(data); + } catch (error) { + console.error('Failed to fetch span annotations:', error); + } + }, []); + + // Fetch span label types + const fetchSpanLabelTypes = useCallback(async () => { + try { + const response = await fetch('/api/span-label-types'); + const data = await response.json(); + setSpanLabelTypes(data); + } catch (error) { + console.error('Failed to fetch span label types:', error); + } + }, []); + + // Fetch charts and span label types on mount, auto-select the most recent chart useEffect(() => { const init = async () => { const chartList = await fetchCharts(); + await fetchSpanLabelTypes(); if (chartList.length > 0) { setActiveChartId(chartList[0].id); // sorted by created_at desc } }; init(); - }, [fetchCharts]); + }, [fetchCharts, fetchSpanLabelTypes]); // When activeChartId changes, refetch data useEffect(() => { if (activeChartId !== null) { chartRef.current?.refreshData(); fetchAnnotations(activeChartId); + fetchSpanAnnotations(activeChartId); setSelectedLabelId(null); + setSelectedSpanId(null); } - }, [activeChartId, fetchAnnotations]); + }, [activeChartId, fetchAnnotations, fetchSpanAnnotations]); const handleExport = () => { if (activeChartId) { diff --git a/src/components/Toolbox.tsx b/src/components/Toolbox.tsx index 283e90e..04b4e49 100644 --- a/src/components/Toolbox.tsx +++ b/src/components/Toolbox.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect } from 'react'; -import { ArrowUpCircle, ArrowDownCircle, TrendingUp, Trash2, Download, ChevronDown, ChevronUp } from 'lucide-react'; +import { ArrowUpCircle, ArrowDownCircle, TrendingUp, Trash2, Download, ChevronDown, ChevronUp, RectangleHorizontal } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { ThemeToggle } from '@/components/ThemeToggle'; @@ -168,6 +168,16 @@ export default function Toolbox({ ))} + {/* Span tool button */} + + {/* Color picker */}
{[