first commit
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { useState } from "react"
|
||||
import { useApi, apiFetch } from "@/hooks/useApi"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
type ExperimentSummary = {
|
||||
id: string
|
||||
name: string
|
||||
status: string
|
||||
run_count: number
|
||||
}
|
||||
|
||||
type RunSummary = {
|
||||
id: string
|
||||
run_id: string
|
||||
model_id: string
|
||||
level: string
|
||||
sample_id: string
|
||||
repeat_index: number
|
||||
status: string
|
||||
retry_count: number
|
||||
}
|
||||
|
||||
type RunDetail = {
|
||||
id: string
|
||||
run_id: string
|
||||
model_id: string
|
||||
level: string
|
||||
sample_id: string
|
||||
status: string
|
||||
raw_output: string | null
|
||||
latency_ms: number | null
|
||||
}
|
||||
|
||||
export default function MonitorPage() {
|
||||
const experimentsState = useApi<ExperimentSummary[]>("/experiments")
|
||||
const [selectedExperimentId, setSelectedExperimentId] = useState<string | null>(null)
|
||||
const runsState = useApi<RunSummary[]>(
|
||||
selectedExperimentId ? `/experiments/${selectedExperimentId}/runs` : null,
|
||||
[selectedExperimentId]
|
||||
)
|
||||
const [selectedRunId, setSelectedRunId] = useState<string | null>(null)
|
||||
const runDetailState = useApi<RunDetail>(
|
||||
selectedRunId ? `/experiments/${selectedExperimentId}/runs/${selectedRunId}` : null,
|
||||
[selectedRunId, selectedExperimentId]
|
||||
)
|
||||
|
||||
async function handleResume() {
|
||||
if (!selectedExperimentId) return
|
||||
await apiFetch(`/experiments/${selectedExperimentId}/run`, { method: "POST" })
|
||||
alert("已触发断点续跑")
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-2xl font-semibold">实验监控</h2>
|
||||
|
||||
{experimentsState.status === "success" && (
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{experimentsState.data.map((e) => (
|
||||
<Button
|
||||
key={e.id}
|
||||
variant={selectedExperimentId === e.id ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedExperimentId(e.id)
|
||||
setSelectedRunId(null)
|
||||
}}
|
||||
>
|
||||
{e.name} ({e.run_count})
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedExperimentId && (
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleResume}>断点续跑</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{runsState.status === "success" && (
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted">
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left">模型</th>
|
||||
<th className="px-4 py-2 text-left">级别</th>
|
||||
<th className="px-4 py-2 text-left">重复</th>
|
||||
<th className="px-4 py-2 text-left">状态</th>
|
||||
<th className="px-4 py-2 text-left">重试</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{runsState.data.map((r) => (
|
||||
<tr
|
||||
key={r.id}
|
||||
className={`cursor-pointer hover:bg-muted/50 ${selectedRunId === r.id ? "bg-muted" : ""}`}
|
||||
onClick={() => setSelectedRunId(r.id)}
|
||||
>
|
||||
<td className="px-4 py-2">{r.model_id}</td>
|
||||
<td className="px-4 py-2">{r.level}</td>
|
||||
<td className="px-4 py-2">{r.repeat_index}</td>
|
||||
<td className="px-4 py-2">{r.status}</td>
|
||||
<td className="px-4 py-2">{r.retry_count}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{runDetailState.status === "success" && (
|
||||
<div className="border rounded-lg p-4 space-y-2">
|
||||
<h3 className="font-semibold">原始输出</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Latency: {runDetailState.data.latency_ms ?? "-"} ms
|
||||
</p>
|
||||
<pre className="bg-muted p-2 rounded text-xs overflow-auto max-h-96">
|
||||
{runDetailState.data.raw_output ?? "无输出"}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user