diff --git a/apps/admin/src/app/simulate/page.tsx b/apps/admin/src/app/simulate/page.tsx index f69fe09..3913b95 100644 --- a/apps/admin/src/app/simulate/page.tsx +++ b/apps/admin/src/app/simulate/page.tsx @@ -2,14 +2,8 @@ import { useEffect, useState } from 'react'; import { AdminShell } from '@/components/AdminShell'; -import { - startSimulation, - getSimulationRuns, - getSimulationRun, - SimRun, -} from '@/lib/api'; +import { getSimulationRuns, SimRun } from '@/lib/api'; -const POLICIES = ['linucb-v1', 'egreedy-v1', 'egreedy-v2']; const mlflowBase = process.env.NEXT_PUBLIC_MLFLOW_URL ?? '/mlflow'; const airflowBase = process.env.NEXT_PUBLIC_AIRFLOW_URL ?? '/airflow'; @@ -83,15 +77,7 @@ function SummaryRow({ run }: { run: SimRun }) { export default function SimulatePage() { const [runs, setRuns] = useState([]); const [loading, setLoading] = useState(true); - const [launching, setLaunching] = useState(false); const [error, setError] = useState(''); - const [msg, setMsg] = useState(''); - - const [nUsers, setNUsers] = useState(5); - const [nRounds, setNRounds] = useState(20); - const [tasksPerRound, setTasksPerRound] = useState(8); - const [judgeMode, setJudgeMode] = useState<'rule' | 'llm'>('rule'); - const [selectedPolicies, setSelectedPolicies] = useState(['linucb-v1', 'egreedy-v1']); const refresh = () => getSimulationRuns() @@ -105,112 +91,30 @@ export default function SimulatePage() { return () => clearInterval(t); }, []); - const togglePolicy = (p: string) => - setSelectedPolicies((prev) => - prev.includes(p) ? prev.filter((x) => x !== p) : [...prev, p], - ); - - const handleLaunch = async () => { - if (selectedPolicies.length < 2) { setError('Select at least 2 policies.'); return; } - setLaunching(true); setError(''); setMsg(''); - try { - const r = await startSimulation({ nUsers, nRounds, tasksPerRound, judgeMode, policies: selectedPolicies }); - setMsg(r.airflow_dag_run_id - ? `Launched via Airflow — dag_run_id: ${r.airflow_dag_run_id}` - : `Launched locally — run id: ${r.id}`); - await refresh(); - } catch (e: unknown) { - setError((e as Error).message); - } finally { - setLaunching(false); - } - }; - return ( -
-

Simulations

- {error &&

{error}

} - {msg &&

{msg}

} - - {/* Launch form */} -
-

New simulation

- -
- - - -
- -
- Policies (select ≥ 2) -
- {POLICIES.map((p) => ( - - ))} -
-
- -
- Judge -
- {(['rule', 'llm'] as const).map((m) => ( - - ))} -
- {judgeMode === 'llm' && ( -

LLM judge requires ANTHROPIC_API_KEY in ml/serving env.

- )} -
- - -

- Runs via Airflow (mlops profile) when available; falls back to local subprocess. - Results logged to MLflow. +

+
+

Simulations

+

+ Offline policy comparisons — run via the{' '} + + Airflow bench_collect DAG + + {' '}(mlops profile). Results are logged to{' '} + MLflow ↗.

-
+
+ + {error &&

{error}

} - {/* Run history */}
-

+

Run history - {loading && loading…} + {loading && loading…}

{runs.length === 0 && !loading && ( -

No simulations yet.

+

No simulation runs yet. Trigger a run from Airflow.

)} {runs.map((r) => )}
diff --git a/apps/admin/src/lib/docs.ts b/apps/admin/src/lib/docs.ts index 5db1bc9..619aed5 100644 --- a/apps/admin/src/lib/docs.ts +++ b/apps/admin/src/lib/docs.ts @@ -13,8 +13,11 @@ import { readdir, readFile } from 'fs/promises'; import path from 'path'; import { marked } from 'marked'; -// apps/admin sits two levels below the monorepo root. -const DOCS_ROOT = path.resolve(process.cwd(), '../../docs'); +// In development: process.cwd() = apps/admin/, so ../../docs = monorepo root docs/. +// In Docker standalone: CWD = /app, so ../../docs is wrong. Set DOCS_ROOT in the +// container to the absolute path where docs/ is copied (e.g. /app/docs). +const DOCS_ROOT = + process.env.DOCS_ROOT ?? path.resolve(process.cwd(), '../../docs'); export type DocCategory = 'adr' | 'architecture'; diff --git a/infra/docker/Dockerfile.admin b/infra/docker/Dockerfile.admin index 1e0498f..0398c93 100644 --- a/infra/docker/Dockerfile.admin +++ b/infra/docker/Dockerfile.admin @@ -26,8 +26,9 @@ ENV NEXT_TELEMETRY_DISABLED=1 \ RUN pnpm --filter @oo/admin build FROM node:22-slim AS runner -ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 PORT=3080 +ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 PORT=3080 DOCS_ROOT=/app/docs WORKDIR /app COPY --from=builder /app/apps/admin/.next/standalone ./ COPY --from=builder /app/apps/admin/.next/static ./apps/admin/.next/static +COPY --from=builder /app/docs ./docs CMD ["node", "apps/admin/server.js"]