import { useState, useEffect, useCallback } from 'react'; import { toast } from './use-toast'; import type { TrainingJob } from '@/types/training'; import fs from 'fs'; import path from 'path'; export function useJobStatusPolling() { const [jobId, setJobId] = useState(null); const [jobStatus, setJobStatus] = useState(null); const [logs, setLogs] = useState([]); const [metrics, setMetrics] = useState([]); const [isPolling, setIsPolling] = useState(false); const [isLoading, setIsLoading] = useState(false); // Find most recent active job const findActiveJob = useCallback(async () => { try { const res = await fetch('/api/active-job'); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } const data = await res.json(); if (data.job_id) { setJobId(data.job_id); setIsPolling(true); } } catch (error) { console.error('Failed to find active job:', error); } }, []); // Start polling when component mounts useEffect(() => { findActiveJob(); }, [findActiveJob]); const startJob = useCallback(async (params: any) => { try { setIsLoading(true); const res = await fetch('/api/train', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } // Instead of trusting returned job_id, always fetch the latest active job from backend await new Promise(resolve => setTimeout(resolve, 300)); // slight delay to let backend register job const activeRes = await fetch('/api/active-job'); const activeData = await activeRes.json(); if (!activeData.job_id) { throw new Error('No active job found after starting'); } setJobId(activeData.job_id); setIsPolling(true); toast({ title: "Training Started", description: `Job ID: ${activeData.job_id}`, }); } catch (error) { console.error('Failed to start job:', error); toast({ title: "Error Starting Training", description: error instanceof Error ? error.message : 'Unknown error', variant: "destructive", }); } finally { setIsLoading(false); } }, []); const stopJob = useCallback(async () => { if (!jobId) return; try { const res = await fetch(`/api/stop/${jobId}`, { method: 'PUT', }); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } setIsPolling(false); toast({ title: "Training Stopped", description: `Job ${jobId} has been stopped`, }); } catch (error) { console.error('Failed to stop job:', error); toast({ title: "Error Stopping Training", description: error instanceof Error ? error.message : 'Unknown error', variant: "destructive", }); } }, [jobId]); const pollJobStatus = useCallback(async () => { if (!jobId || !isPolling) return; try { const res = await fetch(`/api/status/${jobId}`); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } const data: TrainingJob = await res.json(); setJobStatus(data.status); setLogs(data.log_messages || []); // Correctly read `metrics_history` and ensure it's an array if (data.metrics_history && Array.isArray(data.metrics_history)) { // Create a new array to guarantee a state update and trigger re-render setMetrics([...data.metrics_history]); } if (data.status === 'completed' || data.status === 'failed' || data.status === 'stopped') { setIsPolling(false); if (data.status === 'completed' && data.metrics_history && data.metrics_history.length > 0) { toast({ title: "Training Completed", description: `Final accuracy: ${data.metrics_history[data.metrics_history.length - 1]?.val_acc.toFixed(2)}%`, }); } } } catch (error) { console.error('Failed to poll job status:', error); setIsPolling(false); } }, [jobId, isPolling]); useEffect(() => { let intervalId: NodeJS.Timeout | undefined; if (isPolling && jobId) { pollJobStatus(); intervalId = setInterval(pollJobStatus, 1000); // Poll every second } return () => { if (intervalId) { clearInterval(intervalId); } }; }, [isPolling, jobId, pollJobStatus]); const clearLogs = useCallback(() => { setLogs([]); setMetrics([]); }, []); const exportLogs = useCallback(() => { const blob = new Blob([JSON.stringify(logs, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${jobId}_logs.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }, [jobId, logs]); const exportMetrics = useCallback(() => { const blob = new Blob([JSON.stringify(metrics, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${jobId}_metrics.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }, [jobId, metrics]); const freeze = useCallback(() => { setIsPolling(false); }, []); const resume = useCallback(() => { if (jobId) { setIsPolling(true); } }, [jobId]); return { jobId, jobStatus, logs, metrics, isPolling, isLoading, startJob, stopJob, clearLogs, exportLogs, exportMetrics, freeze, resume, setJobId }; }