Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions apps/cockpit-admin/src/components/CampaignControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,19 @@ export function CampaignControls({ status }: Props) {
)}

{startOpen && (
// biome-ignore lint/a11y/useSemanticElements: backdrop overlay closes the modal on outside click; a native <dialog> can't carry the click-to-dismiss backdrop without changing behavior.
<div
role="dialog"
aria-modal="true"
tabIndex={-1}
className="fixed inset-0 z-40 flex items-center justify-center bg-black/50"
onClick={() => setStartOpen(false)}
onKeyDown={(e) => e.key === 'Escape' && setStartOpen(false)}
>
<div
className="max-h-[80vh] w-full max-w-2xl overflow-y-auto rounded bg-white p-6 shadow-lg"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
<h3 className="text-lg font-bold mb-2">Start medium35 campaign</h3>
<p className="text-sm text-slate-600 mb-4">
Expand Down Expand Up @@ -162,15 +166,19 @@ export function CampaignControls({ status }: Props) {
)}

{abortOpen && (
// biome-ignore lint/a11y/useSemanticElements: backdrop overlay closes the modal on outside click; a native <dialog> can't carry the click-to-dismiss backdrop without changing behavior.
<div
role="dialog"
aria-modal="true"
tabIndex={-1}
className="fixed inset-0 z-40 flex items-center justify-center bg-black/50"
onClick={() => setAbortOpen(false)}
onKeyDown={(e) => e.key === 'Escape' && setAbortOpen(false)}
>
<div
className="w-full max-w-md rounded bg-white p-6 shadow-lg"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
<h3 className="text-lg font-bold mb-2">Abandonner la campagne ?</h3>
<p className="text-sm text-slate-600">
Expand Down Expand Up @@ -220,11 +228,7 @@ function DomainCheckboxGroup({
key={d}
className="flex items-center gap-2 text-sm cursor-pointer hover:bg-slate-50 px-1 py-0.5 rounded"
>
<input
type="checkbox"
checked={selected.has(d)}
onChange={() => toggle(d)}
/>
<input type="checkbox" checked={selected.has(d)} onChange={() => toggle(d)} />
<span className="font-mono text-xs">{d}</span>
</label>
))}
Expand Down
13 changes: 3 additions & 10 deletions apps/cockpit-admin/src/components/CampaignDomainGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ export function CampaignDomainGrid({ verdicts, currentDomain }: Props) {
// (insertion order is preserved by JSON parsing) and tag the in-flight
// domain separately if it isn't yet in `verdicts`.
const entries = Object.entries(verdicts ?? {});
if (
currentDomain &&
!entries.some(([d]) => d === currentDomain)
) {
if (currentDomain && !entries.some(([d]) => d === currentDomain)) {
entries.push([currentDomain, '']);
}

Expand Down Expand Up @@ -61,16 +58,12 @@ export function CampaignDomainGrid({ verdicts, currentDomain }: Props) {
return (
<tr
key={domain}
className={`border-b border-slate-100 ${
isCurrent ? 'bg-violet-50' : ''
}`}
className={`border-b border-slate-100 ${isCurrent ? 'bg-violet-50' : ''}`}
>
<td className="py-1 pr-2 font-mono text-slate-400">{idx + 1}</td>
<td className="py-1 pr-2 font-mono">
{domain}
{isCurrent && (
<span className="ml-2 text-xs text-violet-600">(active)</span>
)}
{isCurrent && <span className="ml-2 text-xs text-violet-600">(active)</span>}
</td>
<td className="py-1">
{verdict ? (
Expand Down
18 changes: 4 additions & 14 deletions apps/cockpit-admin/src/components/CampaignStatusCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ function statusVisuals(s: string): { Icon: typeof Activity; color: string } {
return { Icon: XCircle, color: 'text-rose-600' };
case 'ABORTED':
return { Icon: AlertCircle, color: 'text-amber-600' };
case 'IDLE':
default:
return { Icon: Pause, color: 'text-slate-400' };
}
Expand All @@ -25,10 +24,7 @@ function ProgressBar({ value, max }: { value: number; max: number }) {
const pct = max > 0 ? Math.min(100, Math.round((value / max) * 100)) : 0;
return (
<div className="h-2 rounded bg-slate-200 overflow-hidden" aria-label={`${pct}%`}>
<div
className="h-full bg-violet-500 transition-all"
style={{ width: `${pct}%` }}
/>
<div className="h-full bg-violet-500 transition-all" style={{ width: `${pct}%` }} />
</div>
);
}
Expand All @@ -45,9 +41,7 @@ export function CampaignStatusCard({ status }: Props) {
return (
<section className="rounded border border-slate-200 bg-white p-4">
<header className="flex items-center justify-between">
<h3 className="font-bold">
{status.campaign ?? 'medium35 campaign'}
</h3>
<h3 className="font-bold">{status.campaign ?? 'medium35 campaign'}</h3>
<span className={`inline-flex items-center gap-1 text-xs ${color}`}>
<Icon size={14} /> {status.status}
</span>
Expand Down Expand Up @@ -97,18 +91,14 @@ export function CampaignStatusCard({ status }: Props) {
</div>
)}

{status.error && (
<p className="mt-3 text-xs text-rose-600">Error: {status.error}</p>
)}
{status.error && <p className="mt-3 text-xs text-rose-600">Error: {status.error}</p>}
{status.reload_failed && (
<p className="mt-2 text-xs text-amber-600">
Reload failed — workers may need manual recovery.
</p>
)}
{status.abort_requested && status.status === 'TRAINING' && (
<p className="mt-2 text-xs text-amber-600">
Abort requested — current domain finishing.
</p>
<p className="mt-2 text-xs text-amber-600">Abort requested — current domain finishing.</p>
)}
</section>
);
Expand Down
3 changes: 1 addition & 2 deletions apps/cockpit-admin/src/hooks/useStartCampaign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ interface StartResponse {
export function useStartCampaign() {
const qc = useQueryClient();
return useMutation<StartResponse, Error, StartBody>({
mutationFn: (body) =>
api.post<StartResponse>('/api/admin/training/campaign/start', body),
mutationFn: (body) => api.post<StartResponse>('/api/admin/training/campaign/start', body),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['campaign-status'] });
},
Expand Down
18 changes: 5 additions & 13 deletions apps/cockpit-admin/src/routes/training.campaign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { CampaignControls } from '@/components/CampaignControls';
import { CampaignDomainGrid } from '@/components/CampaignDomainGrid';
import { CampaignStatusCard } from '@/components/CampaignStatusCard';
import { LogTail } from '@/components/LogTail';
import type { LogEvent } from '@/hooks/useTrainingLogs';
import { useCampaignLog } from '@/hooks/useCampaignLog';
import { useCampaignStatus } from '@/hooks/useCampaignStatus';
import type { LogEvent } from '@/hooks/useTrainingLogs';
import { createFileRoute } from '@tanstack/react-router';
import { useMemo } from 'react';

Expand Down Expand Up @@ -60,24 +60,16 @@ function CampaignPage() {
</p>
)}

<CampaignDomainGrid
verdicts={status.verdicts}
currentDomain={currentDomain}
/>
<CampaignDomainGrid verdicts={status.verdicts} currentDomain={currentDomain} />

{currentDomain && (
<section className="space-y-2">
<h3 className="font-bold">
Log tail —{' '}
<span className="font-mono text-sm">{currentDomain}</span>
{logQ.isFetching && (
<span className="ml-2 text-xs text-slate-400">refreshing…</span>
)}
Log tail — <span className="font-mono text-sm">{currentDomain}</span>
{logQ.isFetching && <span className="ml-2 text-xs text-slate-400">refreshing…</span>}
</h3>
{logQ.error ? (
<p className="text-sm text-rose-600">
Failed to load log: {logQ.error.message}
</p>
<p className="text-sm text-rose-600">Failed to load log: {logQ.error.message}</p>
) : (
<LogTail events={logEvents} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface Props {
// only a Playground UX default. Power users can override via ParamsPanel.
const REASONING_ALIASES = new Set([
'ailiance-reasoning-r1',
'ailiance-gemma2',
'ailiance-ministral-reasoning',
'ailiance-qwen-235b',
'ailiance-qwen36',
]);
Expand Down
Loading