|
|
@@ -1,4 +1,4 @@
|
|
|
-import { useState, useEffect } from 'react'
|
|
|
+import { useState, useEffect, useRef } from 'react'
|
|
|
import api, { EvalResult, TrainingJob } from '../api/client'
|
|
|
|
|
|
const METRICS_PRESETS = [
|
|
|
@@ -22,6 +22,33 @@ export function Evaluation() {
|
|
|
.catch(() => setJobs([]))
|
|
|
}, [])
|
|
|
|
|
|
+ const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ return () => {
|
|
|
+ if (pollingRef.current) clearInterval(pollingRef.current)
|
|
|
+ }
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ const startPolling = (evalId: string) => {
|
|
|
+ pollingRef.current = setInterval(async () => {
|
|
|
+ try {
|
|
|
+ const res = await api.evaluation.results(evalId)
|
|
|
+ setResult(res)
|
|
|
+ if (res.status === 'completed' || res.status === 'failed') {
|
|
|
+ if (pollingRef.current) clearInterval(pollingRef.current)
|
|
|
+ pollingRef.current = null
|
|
|
+ setRunning(false)
|
|
|
+ if (res.status === 'failed') {
|
|
|
+ setError(res.error || '评估失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ // ignore transient errors
|
|
|
+ }
|
|
|
+ }, 3000)
|
|
|
+ }
|
|
|
+
|
|
|
const handleRun = () => {
|
|
|
if (!jobId.trim()) return
|
|
|
setRunning(true)
|
|
|
@@ -31,9 +58,14 @@ export function Evaluation() {
|
|
|
job_id: jobId,
|
|
|
metrics: metrics.split(',').map(s => s.trim()).filter(Boolean),
|
|
|
})
|
|
|
- .then(res => setResult(res))
|
|
|
- .catch(err => setError(err instanceof Error ? err.message : '评估失败'))
|
|
|
- .finally(() => setRunning(false))
|
|
|
+ .then(res => {
|
|
|
+ setResult(res)
|
|
|
+ startPolling(res.id)
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ setError(err instanceof Error ? err.message : '评估失败')
|
|
|
+ setRunning(false)
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
@@ -117,24 +149,57 @@ export function Evaluation() {
|
|
|
<p style={{ fontSize: 13, color: '#666', margin: '0 0 12px' }}>
|
|
|
评估 ID: <code style={{ background: '#f5f5f5', padding: '2px 8px', borderRadius: 4, fontSize: 12 }}>{result.id}</code>
|
|
|
</p>
|
|
|
- {Object.keys(result.metrics).length > 0
|
|
|
- ? (
|
|
|
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
|
|
|
- {Object.entries(result.metrics).map(([k, v]) => (
|
|
|
- <div key={k} style={{
|
|
|
- padding: '14px 16px', background: '#f0fdfa', borderRadius: 8,
|
|
|
- border: '1px solid #ccfbf1',
|
|
|
- }}>
|
|
|
- <div style={{ fontSize: 12, color: '#94a3b8', marginBottom: 4 }}>{k}</div>
|
|
|
- <div style={{ fontSize: 20, fontWeight: 700, fontFamily: 'monospace', color: '#134e4a' }}>{String(v)}</div>
|
|
|
- </div>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- )
|
|
|
- : <p style={{ color: '#999', fontSize: 13 }}>评估结果为空(后端尚未返回数据)</p>
|
|
|
- }
|
|
|
+
|
|
|
+ {(result.status === 'pending' || result.status === 'running') && (
|
|
|
+ <div style={{
|
|
|
+ padding: 16, background: '#f0fdfa', borderRadius: 8, border: '1px solid #ccfbf1',
|
|
|
+ display: 'flex', alignItems: 'center', gap: 12,
|
|
|
+ }}>
|
|
|
+ <div style={{
|
|
|
+ width: 20, height: 20, border: '2px solid #14b8a6',
|
|
|
+ borderTopColor: 'transparent', borderRadius: '50%',
|
|
|
+ animation: 'spin 1s linear infinite',
|
|
|
+ }} />
|
|
|
+ <span style={{ fontSize: 14, color: '#134e4a', fontWeight: 500 }}>
|
|
|
+ {result.status === 'pending' ? '评估任务排队中...' : `评估进行中 (${result.progress?.toFixed(0) || 0}%)`}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {result.status === 'failed' && (
|
|
|
+ <div style={{
|
|
|
+ padding: 12, background: '#fff1f2', borderRadius: 8, fontSize: 13,
|
|
|
+ color: '#e11d48', border: '1px solid #fecdd3',
|
|
|
+ }}>
|
|
|
+ 评估失败:{result.error || '未知错误'}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {result.status === 'completed' && (
|
|
|
+ Object.keys(result.metrics || {}).length > 0
|
|
|
+ ? (
|
|
|
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
|
|
|
+ {Object.entries(result.metrics).map(([k, v]) => (
|
|
|
+ <div key={k} style={{
|
|
|
+ padding: '14px 16px', background: '#f0fdfa', borderRadius: 8,
|
|
|
+ border: '1px solid #ccfbf1',
|
|
|
+ }}>
|
|
|
+ <div style={{ fontSize: 12, color: '#94a3b8', marginBottom: 4 }}>{k}</div>
|
|
|
+ <div style={{ fontSize: 20, fontWeight: 700, fontFamily: 'monospace', color: '#134e4a' }}>{String(v)}</div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ : <p style={{ color: '#999', fontSize: 13 }}>评估完成,但未返回指标数据</p>
|
|
|
+ )}
|
|
|
</div>
|
|
|
)}
|
|
|
+
|
|
|
+ <style>{`
|
|
|
+ @keyframes spin {
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
+ }
|
|
|
+ `}</style>
|
|
|
</div>
|
|
|
)
|
|
|
}
|