| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- import { useState, useEffect } from 'react'
- import api, { EvalResult, TrainingJob } from '../api/client'
- const METRICS_PRESETS = [
- { value: 'perplexity,loss', label: '困惑度 + Loss' },
- { value: 'perplexity', label: '困惑度' },
- { value: 'loss', label: 'Loss' },
- { value: 'accuracy', label: '准确率' },
- ]
- export function Evaluation() {
- const [jobs, setJobs] = useState<TrainingJob[]>([])
- const [jobId, setJobId] = useState('')
- const [metrics, setMetrics] = useState('perplexity,loss')
- const [running, setRunning] = useState(false)
- const [result, setResult] = useState<EvalResult | null>(null)
- const [error, setError] = useState('')
- useEffect(() => {
- api.training.list()
- .then(data => setJobs(data.filter(j => j.status === 'completed')))
- .catch(() => setJobs([]))
- }, [])
- const handleRun = () => {
- if (!jobId.trim()) return
- setRunning(true)
- setError('')
- setResult(null)
- api.evaluation.run({
- 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))
- }
- return (
- <div>
- <h1 style={{ margin: 0, fontSize: 22, fontWeight: 700 }}>模型评估</h1>
- <p style={{ color: '#888', fontSize: 13, margin: '4px 0 16px' }}>对已完成的训练任务进行性能评估</p>
- <div style={{
- background: '#fff', borderRadius: 12, padding: 24,
- boxShadow: '0 1px 3px rgba(0,0,0,0.06)', border: '1px solid rgba(0,0,0,0.04)',
- }}>
- <h2 style={{ margin: '0 0 16px', fontSize: 15, fontWeight: 600 }}>运行评估</h2>
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
- <div>
- <label style={{ display: 'block', fontSize: 12, color: '#666', marginBottom: 6, fontWeight: 500 }}>训练任务</label>
- <select
- value={jobId}
- onChange={e => setJobId(e.target.value)}
- style={{
- width: '100%', padding: '10px 12px', borderRadius: 8,
- border: '1px solid #d0d0d0', boxSizing: 'border-box',
- fontSize: 14, outline: 'none', background: '#fff',
- transition: 'border-color 0.2s',
- }}
- onFocus={e => { e.currentTarget.style.borderColor = '#14b8a6' }}
- onBlur={e => { e.currentTarget.style.borderColor = '#cbd5e1' }}
- >
- <option value="" disabled>选择已完成的训练任务</option>
- {jobs.map(j => (
- <option key={j.id} value={j.id}>{j.id.slice(0, 8)}... — {j.model_id}</option>
- ))}
- </select>
- </div>
- <div>
- <label style={{ display: 'block', fontSize: 12, color: '#666', marginBottom: 6, fontWeight: 500 }}>评估指标</label>
- <select
- value={metrics}
- onChange={e => setMetrics(e.target.value)}
- style={{
- width: '100%', padding: '10px 12px', borderRadius: 8,
- border: '1px solid #d0d0d0', boxSizing: 'border-box',
- fontSize: 14, outline: 'none', background: '#fff',
- transition: 'border-color 0.2s',
- }}
- onFocus={e => { e.currentTarget.style.borderColor = '#14b8a6' }}
- onBlur={e => { e.currentTarget.style.borderColor = '#cbd5e1' }}
- >
- {METRICS_PRESETS.map(m => (
- <option key={m.value} value={m.value}>{m.label}</option>
- ))}
- </select>
- </div>
- </div>
- {error && (
- <div style={{ marginTop: 16, padding: 12, background: '#fff1f2', borderRadius: 8, fontSize: 13, color: '#e11d48', border: '1px solid #fecdd3' }}>
- {error}
- </div>
- )}
- <button
- onClick={handleRun}
- disabled={running || !jobId}
- style={{
- marginTop: 20, padding: '10px 32px', borderRadius: 8, border: 'none',
- background: '#14b8a6', color: '#fff', cursor: 'pointer',
- opacity: (running || !jobId) ? 0.5 : 1, fontSize: 14, fontWeight: 600,
- transition: 'all 0.2s ease',
- }}
- >
- {running ? '评估中...' : '启动评估'}
- </button>
- </div>
- {result && (
- <div style={{
- marginTop: 24, background: '#fff', borderRadius: 12, padding: 24,
- boxShadow: '0 1px 3px rgba(0,0,0,0.06)', border: '1px solid rgba(0,0,0,0.04)',
- }}>
- <h3 style={{ margin: '0 0 16px', fontSize: 15, fontWeight: 600 }}>评估结果</h3>
- <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>
- }
- </div>
- )}
- </div>
- )
- }
|