26bb5634bd
web-platform-ci / Standalone decision service (no cloud credentials) (push) Waiting to run
web-platform-ci / TypeScript, lint, unit, build (push) Waiting to run
web-platform-ci / Playwright E2E (push) Waiting to run
lekiwi-compatibility / cpu-compatibility (push) Waiting to run
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
import { Download, Square } from 'lucide-react';
|
|
import { Badge, Button, ProgressBar, PropertyRow } from '../components/ui';
|
|
import type { MobileDeployment } from '../mobile/training';
|
|
import { PretrainedIdentity } from './PretrainedSourceSelect';
|
|
import { TrainingMetricsPanel } from './TrainingMetricsPanel';
|
|
import { trainingLosses } from './trainingLosses';
|
|
import { ACTIVE_STATES, stateLabel } from './trainingPresentation';
|
|
import type { TrainingJob } from './types';
|
|
|
|
export function TrainingJobSection({
|
|
job,
|
|
mobileDeployment,
|
|
busy,
|
|
onContinue,
|
|
onCancel,
|
|
onImport,
|
|
onNew,
|
|
}: {
|
|
job: TrainingJob;
|
|
mobileDeployment?: MobileDeployment;
|
|
busy: boolean;
|
|
onContinue(): void;
|
|
onCancel(): void | Promise<void>;
|
|
onImport(): void | Promise<void>;
|
|
onNew(): void;
|
|
}) {
|
|
const active = ACTIVE_STATES.has(job.state);
|
|
return (
|
|
<div className="mt-3 border-t border-border pt-3">
|
|
<div className="mb-2 flex items-center justify-between gap-2">
|
|
<span className="truncate text-xs font-medium text-text-primary" title={job.id}>
|
|
{job.taskId}
|
|
</span>
|
|
<Badge
|
|
tone={
|
|
job.state === 'succeeded'
|
|
? 'success'
|
|
: job.state === 'failed' || job.state === 'cancelled'
|
|
? 'warning'
|
|
: 'accent'
|
|
}
|
|
>
|
|
{stateLabel(job.state)}
|
|
</Badge>
|
|
</div>
|
|
<PretrainedIdentity source={job.pretrained} />
|
|
{mobileDeployment?.evaluation && (
|
|
<div className="mt-2 text-xs">
|
|
<PropertyRow label="阶段" value={mobileDeployment.trainingStage} />
|
|
<PropertyRow
|
|
label="独立评估成功率"
|
|
value={`${(mobileDeployment.evaluation.successRate * 100).toFixed(1)}% / ${mobileDeployment.evaluation.episodes} 回合`}
|
|
/>
|
|
<PropertyRow
|
|
label="实测关节峰值"
|
|
value={`${mobileDeployment.evaluation.maxJointVelocity.toFixed(3)} rad/s`}
|
|
/>
|
|
<PropertyRow label="安全终止次数" value={mobileDeployment.evaluation.safetyStops} />
|
|
<p className="text-warning">
|
|
导出成功不代表策略达标;未达标策略导入仅用于调试,请先同阶段续训。
|
|
</p>
|
|
</div>
|
|
)}
|
|
{job.taskId === 'Unitree-Go2-Rough' && (
|
|
<div>
|
|
<p className="text-xs text-warning">仅后端评测:234 维 Rough 策略无法在浏览器加载。</p>
|
|
</div>
|
|
)}
|
|
{job.deployment?.terrain && (
|
|
<div>
|
|
<p className="text-xs text-warning">
|
|
导入会替换当前物理地图并启动配套策略,请先保存场景。
|
|
</p>
|
|
<Badge tone="warning">
|
|
{job.deployment.terrain.approximation ? '近似碰撞布局' : '配套碰撞布局'}
|
|
</Badge>
|
|
</div>
|
|
)}
|
|
<ProgressBar value={job.progress} label="训练进度" />
|
|
<div className="mt-2">
|
|
<PropertyRow label="迭代" value={`${job.iteration} / ${job.maxIterations}`} />
|
|
<p role="status" className="break-words text-xs text-text-secondary">
|
|
{job.message}
|
|
</p>
|
|
{trainingLosses(job.logs).map(({ label, value }) => (
|
|
<PropertyRow key={label} label={label} value={value} />
|
|
))}
|
|
</div>
|
|
<TrainingMetricsPanel key={job.id} jobId={job.id} logs={job.logs} />
|
|
{job.logs.length > 0 && (
|
|
<details className="mt-2">
|
|
<summary className="cursor-pointer text-xs text-text-secondary">最近日志</summary>
|
|
<pre className="mt-1 max-h-36 overflow-auto whitespace-pre-wrap break-all rounded bg-app p-2 text-xs leading-4 text-text-tertiary">
|
|
{job.logs.slice(-40).join('\n')}
|
|
</pre>
|
|
</details>
|
|
)}
|
|
{mobileDeployment && job.state === 'succeeded' && (
|
|
<Button disabled={busy} onClick={onContinue}>
|
|
接续此作业(保留权重)
|
|
</Button>
|
|
)}
|
|
<div className="mt-3 grid grid-cols-2 gap-2">
|
|
{active ? (
|
|
<Button
|
|
variant="danger"
|
|
className="col-span-2"
|
|
icon={<Square className="h-3.5 w-3.5" />}
|
|
disabled={busy}
|
|
onClick={() => void onCancel()}
|
|
>
|
|
停止训练
|
|
</Button>
|
|
) : (
|
|
<>
|
|
<Button
|
|
disabled={
|
|
busy ||
|
|
!job.artifactReady ||
|
|
job.taskId === 'Unitree-Go2-Rough' ||
|
|
(job.deployment && !job.deployment.browserCompatible)
|
|
}
|
|
icon={<Download className="h-3.5 w-3.5" />}
|
|
onClick={() => void onImport()}
|
|
>
|
|
导入策略
|
|
</Button>
|
|
<Button disabled={busy} onClick={onNew}>
|
|
新建任务
|
|
</Button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|