Jelajahi Sumber

修复评估字段问题以及前端显示

lxylxy123321 2 hari lalu
induk
melakukan
a6f0e808cc

+ 9 - 9
backend/app/services/eval_service.py

@@ -131,18 +131,18 @@ async def _run_remote_evaluation(eval_id: str, job_id: str) -> dict[str, Any]:
         if line.startswith("{"):
             try:
                 result = json.loads(line)
-                # 保存结果到本地数据库
+                # 保存结果到本地数据库(更新已有记录)
                 metrics = result.get("metrics", {})
                 async with async_session() as session:
-                    eval_record = EvalResultModel(
-                        id=eval_id,
-                        job_id=job_id,
-                        metrics=json.dumps(metrics),
-                        status="completed",
-                        created_at=datetime.utcnow(),
+                    res = await session.execute(
+                        select(EvalResultModel).where(EvalResultModel.id == eval_id)
                     )
-                    session.add(eval_record)
-                    await session.commit()
+                    eval_record = res.scalar_one_or_none()
+                    if eval_record:
+                        eval_record.metrics = json.dumps(metrics)
+                        eval_record.status = "completed"
+                        eval_record.progress = 100.0
+                        await session.commit()
                 return {"id": eval_id, "job_id": job_id, "metrics": metrics}
             except json.JSONDecodeError:
                 continue

+ 3 - 0
frontend/src/api/client.ts

@@ -335,7 +335,10 @@ interface EvalConfig {
 interface EvalResult {
   id: string
   job_id: string
+  status: string
+  progress: number
   metrics: Record<string, unknown>
+  error: string | null
   created_at: string
 }
 

+ 85 - 20
frontend/src/pages/Evaluation.tsx

@@ -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>
   )
 }