import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { getPsBatches } from './api/taskBatches'; import { createTimeseriesRun, getTimeseriesRunDetail, listTimeseriesRuns, } from './api/timeseriesProduction'; const card = { background: '#fff', padding: '12px', borderRadius: '8px', border: '1px solid #e2e8f0', marginBottom: '12px', }; const STATUS_COLOR = { PENDING: '#64748b', RUNNING: '#2563eb', PREPARING: '#2563eb', PREPARED: '#16a34a', STACK_PREPARING: '#d97706', STACK_PREPARED: '#0891b2', MATERIALIZING: '#7c3aed', MATERIALIZED: '#9333ea', STACK_READY: '#15803d', STACK_RUNNING: '#ea580c', STACK_COMPLETED: '#16a34a', MINTPY_RUNNING: '#2563eb', MINTPY_COMPLETED: '#0f766e', EXPORTING: '#c2410c', EXPORTED: '#0891b2', REGISTERING: '#7c3aed', FAILED: '#dc2626', PUBLISHED: '#16a34a', }; function formatDateTime(value) { if (!value) return '-'; try { return new Date(value).toLocaleString(); } catch { return String(value); } } function StatusPill({ value }) { const color = STATUS_COLOR[value] || '#64748b'; return ( {value || 'UNKNOWN'} ); } export default function TimeseriesProductionPanel({ readOnly = false, onJobQueued }) { const [batches, setBatches] = useState([]); const [runs, setRuns] = useState([]); const [selectedBatchId, setSelectedBatchId] = useState(''); const [selectedRunId, setSelectedRunId] = useState(''); const [selectedRunDetail, setSelectedRunDetail] = useState(null); const [loading, setLoading] = useState(false); const [detailLoading, setDetailLoading] = useState(false); const [submitting, setSubmitting] = useState(false); const [message, setMessage] = useState(''); const [runName, setRunName] = useState(''); const [referenceDate, setReferenceDate] = useState(''); const [waterMaskMode, setWaterMaskMode] = useState('synthetic_fallback'); const [notes, setNotes] = useState(''); const selectedBatch = useMemo( () => batches.find(item => item.batch_id === selectedBatchId) || null, [batches, selectedBatchId] ); const loadBatches = useCallback(async () => { try { const data = await getPsBatches(); const nextItems = Array.isArray(data) ? data : []; setBatches(nextItems); setSelectedBatchId(current => current || nextItems[0]?.batch_id || ''); } catch { setBatches([]); } }, []); const loadRuns = useCallback(async () => { setLoading(true); try { const data = await listTimeseriesRuns({ limit: 20, offset: 0 }); const nextItems = Array.isArray(data?.items) ? data.items : []; setRuns(nextItems); setSelectedRunId(current => current || nextItems[0]?.run_id || ''); } catch { setRuns([]); } finally { setLoading(false); } }, []); const loadRunDetail = useCallback(async runId => { if (!runId) { setSelectedRunDetail(null); return; } setDetailLoading(true); try { const detail = await getTimeseriesRunDetail(runId); setSelectedRunDetail(detail); } catch (error) { setSelectedRunDetail({ error: error?.response?.data?.detail || error.message || '运行详情加载失败', }); } finally { setDetailLoading(false); } }, []); useEffect(() => { loadBatches(); loadRuns(); }, [loadBatches, loadRuns]); useEffect(() => { const timer = setInterval(loadRuns, 10000); return () => clearInterval(timer); }, [loadRuns]); useEffect(() => { loadRunDetail(selectedRunId); }, [loadRunDetail, selectedRunId]); const handleSubmit = async () => { if (!selectedBatchId) { setMessage('请先选择一个时序批次。'); return; } setSubmitting(true); setMessage(''); try { const result = await createTimeseriesRun({ batch_id: selectedBatchId, run_name: runName.trim() || null, reference_date: referenceDate.trim() || null, water_mask_mode: waterMaskMode, notes: notes.trim() || null, }); setMessage(`运行已入队:${result.run_id} / task=${result.task_id}`); onJobQueued?.(result.task_id); await loadRuns(); } catch (error) { setMessage(error?.response?.data?.detail || error.message || '运行提交失败'); } finally { setSubmitting(false); } }; const runData = selectedRunDetail?.run || null; const linkedProduct = selectedRunDetail?.product || null; const workflowSteps = selectedRunDetail?.workflow?.steps || []; return (