Add operator user agent and video recorder

This commit is contained in:
2026-07-01 00:44:10 +08:00
parent dcd6f7fd41
commit 1d43efd5b4
9 changed files with 502 additions and 9 deletions

View File

@@ -311,6 +311,24 @@ type ValidationAgentPayload = {
checks: AgentCheck[];
};
type UserAgentPayload = {
available?: boolean;
agent: string;
passed: boolean;
run_id?: string;
created_at?: string;
dataset?: {
name: string;
root: string;
license: string;
counts: { images: number; labels: number; masks: number; annotations: number };
pairs: { image_label: number; image_mask: number };
yaml: string;
};
checks?: AgentCheck[];
suggestions?: string[];
};
type PageId = "overview" | "datasets" | "training" | "inference" | "results" | "system" | "agents";
type ModelWeightOption = {
@@ -425,11 +443,12 @@ function useData() {
const [runtimeReadiness, setRuntimeReadiness] = useState<RuntimeReadinessPayload | null>(null);
const [capabilities, setCapabilities] = useState<CapabilityPayload | null>(null);
const [agentEvaluation, setAgentEvaluation] = useState<EvaluationAgentPayload | null>(null);
const [userAgent, setUserAgent] = useState<UserAgentPayload | null>(null);
const [error, setError] = useState<string>("");
async function refresh() {
try {
const [catalogNext, gpusNext, envsNext, readinessNext, capabilitiesNext, jobsNext, resultsNext, curvesNext, weightsNext, datasetsNext, coverageNext, acceptanceNext, realAcceptanceNext, realTrainAcceptanceNext, deepAcceptanceNext, agentEvaluationNext] = await Promise.all([
const [catalogNext, gpusNext, envsNext, readinessNext, capabilitiesNext, jobsNext, resultsNext, curvesNext, weightsNext, datasetsNext, coverageNext, acceptanceNext, realAcceptanceNext, realTrainAcceptanceNext, deepAcceptanceNext, agentEvaluationNext, userAgentNext] = await Promise.all([
api<Catalog>("/api/catalog"),
api<GpuPayload>("/api/system/gpus"),
api<CondaEnvPayload>("/api/system/envs"),
@@ -445,7 +464,8 @@ function useData() {
api<AcceptancePayload>("/api/acceptance/real/latest"),
api<AcceptancePayload>("/api/acceptance/real-train/latest"),
api<DeepAcceptancePayload>("/api/acceptance/deep/latest"),
api<EvaluationAgentPayload>("/api/agents/evaluate")
api<EvaluationAgentPayload>("/api/agents/evaluate"),
api<UserAgentPayload>("/api/agents/user/latest")
]);
setCatalog(catalogNext);
setGpus(gpusNext);
@@ -475,6 +495,7 @@ function useData() {
setRealTrainAcceptance(realTrainAcceptanceNext);
setDeepAcceptance(deepAcceptanceNext);
setAgentEvaluation(agentEvaluationNext);
setUserAgent(userAgentNext);
setError("");
} catch (err) {
setError(String(err));
@@ -487,7 +508,7 @@ function useData() {
return () => window.clearInterval(timer);
}, []);
return { catalog, gpus, condaEnvs, runtimeReadiness, capabilities, agentEvaluation, jobs, results, curves, weightManifest, datasets, datasetValidations, coverage, acceptance, realAcceptance, realTrainAcceptance, deepAcceptance, error, refresh };
return { catalog, gpus, condaEnvs, runtimeReadiness, capabilities, agentEvaluation, userAgent, jobs, results, curves, weightManifest, datasets, datasetValidations, coverage, acceptance, realAcceptance, realTrainAcceptance, deepAcceptance, error, refresh };
}
function StatusPill({ status }: { status: string }) {
@@ -510,7 +531,7 @@ function JobProgressBar({ progress }: { progress?: JobProgress }) {
}
function App() {
const { catalog, gpus, condaEnvs, runtimeReadiness, capabilities, agentEvaluation, jobs, results, curves, weightManifest, datasets, datasetValidations, coverage, acceptance, realAcceptance, realTrainAcceptance, deepAcceptance, error, refresh } = useData();
const { catalog, gpus, condaEnvs, runtimeReadiness, capabilities, agentEvaluation, userAgent, jobs, results, curves, weightManifest, datasets, datasetValidations, coverage, acceptance, realAcceptance, realTrainAcceptance, deepAcceptance, error, refresh } = useData();
const [page, setPage] = useState<PageId>(pageFromHash);
const [taskType, setTaskType] = useState("mock.echo");
const [params, setParams] = useState(JSON.stringify(defaultParams["mock.echo"], null, 2));
@@ -530,6 +551,7 @@ function App() {
const [agentValidation, setAgentValidation] = useState<ValidationAgentPayload | null>(null);
const [weightVerification, setWeightVerification] = useState<WeightVerifyPayload | null>(null);
const [agentBusy, setAgentBusy] = useState(false);
const [userAgentBusy, setUserAgentBusy] = useState(false);
const [selectedInferenceWeight, setSelectedInferenceWeight] = useState("");
const [inferenceSourcePath, setInferenceSourcePath] = useState("");
const [inferenceModelKey, setInferenceModelKey] = useState("YOLO11n-seg");
@@ -801,6 +823,16 @@ function App() {
}
}
async function runUserAgent() {
setUserAgentBusy(true);
try {
await api<UserAgentPayload>("/api/agents/user", { method: "POST" });
await refresh();
} finally {
setUserAgentBusy(false);
}
}
async function createDataset() {
setBusy(true);
try {
@@ -1489,7 +1521,7 @@ function App() {
</div>
</section>
<section className="grid two" id="agents" data-page-section="agents">
<section className="grid three" id="agents" data-page-section="agents">
<div className="panel agentPanel">
<div className="panelHead">
<div>
@@ -1526,6 +1558,36 @@ function App() {
</div>
<AgentCheckList checks={agentValidation?.checks ?? []} limit={18} />
</div>
<div className="panel agentPanel userAgentPanel">
<div className="panelHead">
<div>
<p className="eyebrow">User Agent</p>
<h2>使</h2>
</div>
<button className="primary secondary" disabled={userAgentBusy} onClick={runUserAgent}>
<Boxes size={17} />
</button>
</div>
<div className="agentScore">
<strong>{userAgent?.available === false ? "New" : userAgent?.passed ? "OK" : "Check"}</strong>
<span>{userAgent?.run_id ? `run ${userAgent.run_id}` : "生成合成开源数据并走完整数据集流程"}</span>
</div>
{userAgent?.dataset && (
<div className="userAgentDataset">
<div><span>Dataset</span><strong>{userAgent.dataset.name}</strong></div>
<div><span>Pairs</span><strong>{userAgent.dataset.pairs.image_label}/{userAgent.dataset.pairs.image_mask}</strong></div>
<div><span>Annotations</span><strong>{userAgent.dataset.counts.annotations}</strong></div>
<a href={`${API_BASE}/api/artifacts/${userAgent.dataset.yaml}`} target="_blank" rel="noreferrer">dataset.yaml</a>
</div>
)}
<div className="suggestionList">
{(userAgent?.suggestions ?? ["等待使用者 agent 运行。"]).slice(0, 3).map((item, index) => (
<div key={`${index}-${item}`}>{item}</div>
))}
</div>
<AgentCheckList checks={userAgent?.checks ?? []} limit={10} />
</div>
</section>
<section className="grid four" data-page-section="system">

View File

@@ -990,6 +990,44 @@ textarea {
margin-top: 2px;
}
.userAgentDataset {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
margin-bottom: 12px;
}
.userAgentDataset div,
.userAgentDataset a {
min-width: 0;
display: grid;
gap: 3px;
padding: 9px;
border-radius: 6px;
border: 1px solid var(--line);
background: #101310;
color: var(--ink);
text-decoration: none;
}
.userAgentDataset span,
.userAgentDataset strong,
.userAgentDataset a {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.userAgentDataset span {
color: var(--muted);
font-size: 11px;
}
.userAgentDataset a {
color: var(--green);
align-content: center;
}
.datasetCard {
width: 100%;
display: block;
@@ -1842,7 +1880,8 @@ meter {
.pipelineExample,
.pipelineSteps,
.pipelineStats,
.inferencePreview {
.inferencePreview,
.userAgentDataset {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@@ -1902,5 +1941,6 @@ meter {
.coverageGrid,
.taskCheckList { grid-template-columns: 1fr; }
.grid.three { grid-template-columns: 1fr; }
.shell[data-page="agents"] .grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid.two { grid-template-columns: 1fr; }
}