'use client'; import { useState, useRef, useEffect, useCallback } from 'react'; import Toolbox, { Tool } from '@/components/Toolbox'; import FileUpload from '@/components/FileUpload'; import CandleChart, { CandleChartHandle } from '@/components/CandleChart'; import ChartSelector from '@/components/ChartSelector'; interface Chart { id: number; name: string; created_at: number; } interface Annotation { id: number; chart_id: number; timestamp: number; label_type: string; geometry: any; 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 [selectedColor, setSelectedColor] = useState('#3b82f6'); const [selectedLabelId, setSelectedLabelId] = useState(null); const [annotations, setAnnotations] = useState([]); const [charts, setCharts] = useState([]); 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 { const response = await fetch('/api/charts'); const data = await response.json(); setCharts(data); return data as Chart[]; } catch (error) { console.error('Failed to fetch charts:', error); return []; } }, []); // Fetch annotations for active chart const fetchAnnotations = useCallback(async (chartId: number | null) => { if (!chartId) { setAnnotations([]); return; } try { const response = await fetch(`/api/annotations?chartId=${chartId}`); const data = await response.json(); setAnnotations(data); } catch (error) { console.error('Failed to fetch annotations:', error); } }, []); // 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, fetchSpanLabelTypes]); // When activeChartId changes, refetch data useEffect(() => { if (activeChartId !== null) { chartRef.current?.refreshData(); fetchAnnotations(activeChartId); fetchSpanAnnotations(activeChartId); setSelectedLabelId(null); setSelectedSpanId(null); } }, [activeChartId, fetchAnnotations, fetchSpanAnnotations]); const handleExport = () => { if (activeChartId) { window.location.href = `/api/export?chartId=${activeChartId}`; } else { window.location.href = '/api/export'; } }; const handleUploadSuccess = (chart: { id: number; name: string }) => { // Add new chart to list and select it const newChart: Chart = { id: chart.id, name: chart.name, created_at: Math.floor(Date.now() / 1000), }; setCharts((prev) => [newChart, ...prev]); setActiveChartId(chart.id); }; const handleAnnotationChange = async () => { await chartRef.current?.refreshData(); await fetchAnnotations(activeChartId); }; const handleSpanAnnotationsChange = async () => { await fetchSpanAnnotations(activeChartId); }; const handleSelectedSpanChange = (spanId: number | null) => { setSelectedSpanId(spanId); }; const handleDeleteSpan = async (spanId: number) => { try { const response = await fetch(`/api/span-annotations/${spanId}`, { method: 'DELETE', }); if (response.ok) { await fetchSpanAnnotations(activeChartId); if (selectedSpanId === spanId) { setSelectedSpanId(null); } } } catch (error) { console.error('Failed to delete span annotation:', error); } }; const handleLabelDelete = async (id: number) => { setAnnotations(annotations.filter((a) => a.id !== id)); if (selectedLabelId === id) { setSelectedLabelId(null); } }; const handleLabelSelect = (id: number) => { setSelectedLabelId(id === -1 ? null : id); }; const handleSelectChart = (chartId: number) => { setActiveChartId(chartId); }; const handleDeleteChart = async (chartId: number) => { try { const response = await fetch(`/api/charts/${chartId}`, { method: 'DELETE' }); if (response.ok) { const remaining = charts.filter((c) => c.id !== chartId); setCharts(remaining); if (activeChartId === chartId) { setActiveChartId(remaining.length > 0 ? remaining[0].id : null); } } } catch (error) { console.error('Failed to delete chart:', error); } }; // Keyboard handler for Delete/Backspace key useEffect(() => { const handleKeyDown = async (e: KeyboardEvent) => { if ((e.key === 'Delete' || e.key === 'Backspace') && selectedLabelId !== null) { try { const response = await fetch(`/api/annotations/${selectedLabelId}`, { method: 'DELETE', }); if (response.ok) { setSelectedLabelId(null); chartRef.current?.refreshData(); } } catch (error) { console.error('Failed to delete label:', error); } } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [selectedLabelId]); return (
{/* Sidebar */} {/* Main chart area */}
); }