@hugpy/video-intelligence-ui 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -3
- package/src/config.ts +36 -0
- package/src/demo/assets/generate/cinema-cavern/movie.mp4 +0 -0
- package/src/demo/assets/generate/cinema-cavern/seg_00.mp4 +0 -0
- package/src/demo/assets/generate/clips/cat-lounge.mp4 +0 -0
- package/src/demo/assets/generate/clips/drone-city.mp4 +0 -0
- package/src/demo/assets/generate/clips/paper-sails.mp4 +0 -0
- package/src/demo/assets/generate/movie-galaxy/movie.mp4 +0 -0
- package/src/demo/assets/generate/movie-galaxy/seg_00.mp4 +0 -0
- package/src/demo/assets/generate/movie-galaxy/seg_01.mp4 +0 -0
- package/src/demo/assets/generate/movie-galaxy/seg_02.mp4 +0 -0
- package/src/demo/assets/generate/movie-galaxy/seg_03.mp4 +0 -0
- package/src/demo/assets/generate/movie-medow/movie.mp4 +0 -0
- package/src/demo/assets/generate/movie-medow/seg_00.mp4 +0 -0
- package/src/demo/assets/generate/movie-medow/seg_01.mp4 +0 -0
- package/src/demo/assets/generate/scene-desert/frame_00000.png +0 -0
- package/src/demo/assets/generate/scene-desert/frame_00001.png +0 -0
- package/src/demo/assets/generate/scene-desert/frame_00002.png +0 -0
- package/src/demo/assets/generate/scene-desert/frame_00003.png +0 -0
- package/src/demo/assets/generate/scene-desert/frame_00004.png +0 -0
- package/src/demo/assets/generate/scene-desert/frame_00005.png +0 -0
- package/src/demo/assets/generate/scene-desert/scene.mp4 +0 -0
- package/src/demo/assets/generate/scene-vintage/frame_00000.png +0 -0
- package/src/demo/assets/generate/scene-vintage/frame_00001.png +0 -0
- package/src/demo/assets/generate/scene-vintage/frame_00002.png +0 -0
- package/src/demo/assets/generate/scene-vintage/frame_00003.png +0 -0
- package/src/demo/assets/generate/scene-vintage/frame_00004.png +0 -0
- package/src/demo/assets/generate/scene-vintage/frame_00005.png +0 -0
- package/src/demo/assets/generate/scene-vintage/scene.mp4 +0 -0
- package/src/demo/assets/generate2/s0.png +0 -0
- package/src/demo/assets/generate2/s1.png +0 -0
- package/src/demo/assets/generate2/s10.png +0 -0
- package/src/demo/assets/generate2/s11.png +0 -0
- package/src/demo/assets/generate2/s12.png +0 -0
- package/src/demo/assets/generate2/s13.png +0 -0
- package/src/demo/assets/generate2/s14.png +0 -0
- package/src/demo/assets/generate2/s15.png +0 -0
- package/src/demo/assets/generate2/s16.png +0 -0
- package/src/demo/assets/generate2/s17.png +0 -0
- package/src/demo/assets/generate2/s18.png +0 -0
- package/src/demo/assets/generate2/s19.png +0 -0
- package/src/demo/assets/generate2/s2.png +0 -0
- package/src/demo/assets/generate2/s20.png +0 -0
- package/src/demo/assets/generate2/s21.mp4 +0 -0
- package/src/demo/assets/generate2/s22.mp4 +0 -0
- package/src/demo/assets/generate2/s23.mp4 +0 -0
- package/src/demo/assets/generate2/s24.mp4 +0 -0
- package/src/demo/assets/generate2/s25.mp4 +0 -0
- package/src/demo/assets/generate2/s26.mp4 +0 -0
- package/src/demo/assets/generate2/s27.mp4 +0 -0
- package/src/demo/assets/generate2/s28.mp4 +0 -0
- package/src/demo/assets/generate2/s29.mp4 +0 -0
- package/src/demo/assets/generate2/s3.png +0 -0
- package/src/demo/assets/generate2/s30.mp4 +0 -0
- package/src/demo/assets/generate2/s4.png +0 -0
- package/src/demo/assets/generate2/s5.png +0 -0
- package/src/demo/assets/generate2/s6.png +0 -0
- package/src/demo/assets/generate2/s7.png +0 -0
- package/src/demo/assets/generate2/s8.png +0 -0
- package/src/demo/assets/generate2/s9.png +0 -0
- package/src/demo/demoFetch.ts +126 -3
- package/src/demo/fixtures.ts +72 -4
- package/src/demo/promptAssist.ts +25 -0
- package/src/demo/sampleData.ts +960 -0
- package/src/demo/sampleSets.ts +172 -0
- package/src/stations/ActiveProcessesStation.tsx +18 -3
- package/src/stations/GenerateStation.tsx +1031 -364
- package/src/stations/GoalComposer.tsx +9 -1
- package/src/stations/ScriptFirstStation.tsx +1081 -0
- package/src/stations/ToksPanel.tsx +188 -0
- package/src/stations/WorkbenchDrawer.tsx +4 -3
- package/src/stations/WorkbenchSidebar.tsx +60 -12
- package/src/stations/oracle/DagPanel.tsx +391 -0
- package/src/stations/oracle/OracleStation.tsx +304 -0
- package/src/stations/oracle/SpatialTab.tsx +185 -0
- package/src/stations/oracle/StewardPanel.tsx +165 -0
- package/src/stations/oracle/oracle.css +467 -0
- package/src/stations/oracle/oracleShared.tsx +160 -0
- package/src/stations/oracle/oracleTypes.ts +304 -0
- package/src/stations/oracle/useOracle.ts +560 -0
- package/src/stations/registry.ts +37 -0
- package/src/stations/scriptFirst.css +350 -0
- package/src/stations/studio/IdentitiesStation.tsx +219 -292
- package/src/stations/studio/IdentityCreatePanel.tsx +390 -0
- package/src/stations/studio/StudioGenerateTab.tsx +162 -6
- package/src/stations/studio/StudioMovieComposer.tsx +763 -318
- package/src/stations/studio/{StudioMovieComposer.tsx.bak-sessions-20260812 → StudioMovieComposer.tsx.bak-declutter-20260813} +350 -48
- package/src/stations/studio/StudioPlane.tsx +35 -3
- package/src/stations/studio/StudioViewer.tsx +116 -14
- package/src/stations/studio/coordinationBadge.tsx +206 -0
- package/src/stations/studio/identityUpload.ts +66 -0
- package/src/stations/studio/promptCard.tsx +362 -59
- package/src/stations/studio/useIdentityCreateJob.ts +184 -0
- package/src/stations/studio/useIdentityProfiles.ts +66 -0
- package/src/stations/useScriptFirstRun.ts +596 -0
- package/src/style/app.css +176 -0
- package/src/video/LengthRow.tsx +189 -0
- package/src/video/LibraryGroups.tsx +81 -0
- package/src/video/UnnamedWarn.tsx +146 -0
- package/src/video/coordinationReport.ts +266 -0
- package/src/video/jobClock.ts +104 -0
- package/src/video/spreadAssist.ts +26 -0
- package/src/video/useMediaJobs.ts +26 -0
- package/src/video/useOperatorHistory.ts +105 -0
- package/src/video/useToks.ts +193 -0
- package/src/stations/ActiveProcessesStation.tsx.bak-sessions-20260812 +0 -228
- package/src/stations/GenerateStation.tsx.bak-tester-removal +0 -5170
- package/src/stations/studio/StudioMovieSessions.tsx.bak-sessions-20260812 +0 -417
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* or-k18 / k114 — the Oracle station: the in-between of generation, made
|
|
3
|
+
* visible.
|
|
4
|
+
*
|
|
5
|
+
* 1. Steward — the selector's self-audit (findings by severity, matrix_stale
|
|
6
|
+
* banner, last run time, re-run → POST).
|
|
7
|
+
* 2. Run picker — the performance jobs on the media bus (the same
|
|
8
|
+
* GET /video/jobs feed Active Processes reads), and the picked run's
|
|
9
|
+
* manifest from GET /video/jobs/<id> → result.movie.
|
|
10
|
+
* 3. Run header — run id, ok/gap, digests, registry version, receipts.
|
|
11
|
+
* 4. Tabs: DAG (node states + per-node drawer) · Spatial overlays (honest
|
|
12
|
+
* placeholder) · Ledger (central reliability ledger summary + catalog).
|
|
13
|
+
*
|
|
14
|
+
* Every object on this screen is shown with where it came from and a raw JSON
|
|
15
|
+
* fold-out; every absence is named (route missing, selector disabled, no dag
|
|
16
|
+
* block, no spatial conditioning) rather than left blank.
|
|
17
|
+
*/
|
|
18
|
+
import { useState } from "react";
|
|
19
|
+
|
|
20
|
+
import type { StationSpec } from "../types";
|
|
21
|
+
import { DagPanel } from "./DagPanel";
|
|
22
|
+
import { SpatialTab } from "./SpatialTab";
|
|
23
|
+
import { StewardPanel } from "./StewardPanel";
|
|
24
|
+
import { GapNote, RawJson, StateChip, Tag, short, when } from "./oracleShared";
|
|
25
|
+
import {
|
|
26
|
+
useCapabilities,
|
|
27
|
+
useLedgerSummary,
|
|
28
|
+
usePerformanceJobs,
|
|
29
|
+
useRunManifest,
|
|
30
|
+
useSteward,
|
|
31
|
+
type RunManifestApi,
|
|
32
|
+
} from "./useOracle";
|
|
33
|
+
import "./oracle.css";
|
|
34
|
+
|
|
35
|
+
type TabId = "dag" | "spatial" | "ledger";
|
|
36
|
+
|
|
37
|
+
function RunPicker({ runs }: { runs: RunManifestApi }) {
|
|
38
|
+
const jobs = usePerformanceJobs();
|
|
39
|
+
const [manual, setManual] = useState("");
|
|
40
|
+
return (
|
|
41
|
+
<section className="vi-oracle-panel" aria-label="Runs">
|
|
42
|
+
<h3>
|
|
43
|
+
Runs
|
|
44
|
+
<span className="vi-oracle-sub">performance jobs on the media bus (GET /video/jobs?all=1) — pick one to read its manifest</span>
|
|
45
|
+
</h3>
|
|
46
|
+
<div className="vi-oracle-row">
|
|
47
|
+
<button type="button" className="vi-btn vi-btn-sm" onClick={jobs.refresh}>
|
|
48
|
+
Refresh list
|
|
49
|
+
</button>
|
|
50
|
+
<input
|
|
51
|
+
className="vi-oracle-grow"
|
|
52
|
+
placeholder="…or paste a job id"
|
|
53
|
+
value={manual}
|
|
54
|
+
onChange={(e) => setManual(e.target.value)}
|
|
55
|
+
onKeyDown={(e) => {
|
|
56
|
+
if (e.key === "Enter" && manual.trim()) runs.select(manual.trim());
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
<button type="button" className="vi-btn vi-btn-sm" disabled={!manual.trim()} onClick={() => runs.select(manual.trim())}>
|
|
60
|
+
Open
|
|
61
|
+
</button>
|
|
62
|
+
{runs.jobId ? (
|
|
63
|
+
<button type="button" className="vi-btn vi-btn-sm vi-btn-ghost" onClick={() => runs.select(null)}>
|
|
64
|
+
Clear
|
|
65
|
+
</button>
|
|
66
|
+
) : null}
|
|
67
|
+
</div>
|
|
68
|
+
<GapNote gap={jobs.gap} />
|
|
69
|
+
<div className="vi-oracle-runs" style={{ marginTop: "0.5rem" }}>
|
|
70
|
+
{jobs.jobs.map((j) => (
|
|
71
|
+
<button
|
|
72
|
+
key={j.id}
|
|
73
|
+
type="button"
|
|
74
|
+
className={`vi-btn vi-btn-sm${runs.jobId === j.id ? " vi-btn-on" : ""}`}
|
|
75
|
+
onClick={() => runs.select(j.id)}
|
|
76
|
+
title={`${j.name} · ${j.status}${j.runId ? ` · run ${j.runId}` : ""}`}
|
|
77
|
+
>
|
|
78
|
+
{short(j.id, 10)} · {j.status}
|
|
79
|
+
{j.runId ? ` · ${short(j.runId, 10)}` : ""}
|
|
80
|
+
</button>
|
|
81
|
+
))}
|
|
82
|
+
{!jobs.jobs.length && !jobs.gap ? (
|
|
83
|
+
<span className="vi-knob-hint">
|
|
84
|
+
No performance jobs in the bus feed (live or recent terminal). Enqueue one via the oracle runner, or paste an
|
|
85
|
+
older job id.
|
|
86
|
+
</span>
|
|
87
|
+
) : null}
|
|
88
|
+
</div>
|
|
89
|
+
</section>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function RunHeader({ runs }: { runs: RunManifestApi }) {
|
|
94
|
+
const m = runs.manifest;
|
|
95
|
+
const job = runs.job;
|
|
96
|
+
if (!runs.jobId) return null;
|
|
97
|
+
return (
|
|
98
|
+
<section className="vi-oracle-panel" aria-label="Run">
|
|
99
|
+
<h3>
|
|
100
|
+
Run
|
|
101
|
+
<span className="vi-oracle-sub">GET /video/jobs/{runs.jobId} → result.movie</span>
|
|
102
|
+
<button type="button" className="vi-btn vi-btn-sm" style={{ marginLeft: "auto" }} onClick={runs.refresh} disabled={runs.loading}>
|
|
103
|
+
{runs.loading ? "Reading…" : "Re-read"}
|
|
104
|
+
</button>
|
|
105
|
+
</h3>
|
|
106
|
+
<GapNote gap={runs.gap} />
|
|
107
|
+
{job && !m ? (
|
|
108
|
+
<p className="vi-oracle-gap" role="status">
|
|
109
|
+
Job <code>{job.job_id}</code> is <StateChip state={job.status ?? "unknown"} />
|
|
110
|
+
{job.result ? (
|
|
111
|
+
<> and its result carries no manifest (<code>result.movie</code> is empty){job.result.error ? <> — {job.result.error.code}: {job.result.error.message}</> : null}.</>
|
|
112
|
+
) : (
|
|
113
|
+
<> with no result yet — the manifest is written at the terminal transition.</>
|
|
114
|
+
)}
|
|
115
|
+
{job.progress ? <RawJson label="live progress" value={job.progress} /> : null}
|
|
116
|
+
</p>
|
|
117
|
+
) : null}
|
|
118
|
+
{m ? (
|
|
119
|
+
<>
|
|
120
|
+
<div className="vi-oracle-row">
|
|
121
|
+
<Tag kind={m.ok ? "ok" : "alarm"}>{m.ok ? "ok" : "not ok"}</Tag>
|
|
122
|
+
{m.stopped_after ? <Tag kind="warn">stopped after {m.stopped_after}</Tag> : null}
|
|
123
|
+
{job?.status ? <StateChip state={job.status} /> : null}
|
|
124
|
+
{m.dag ? <Tag kind="accent">ran on the DAG</Tag> : <Tag>linear recipe</Tag>}
|
|
125
|
+
{job?.result?.error ? (
|
|
126
|
+
<Tag kind="alarm">
|
|
127
|
+
{job.result.error.code}: {job.result.error.message}
|
|
128
|
+
</Tag>
|
|
129
|
+
) : null}
|
|
130
|
+
</div>
|
|
131
|
+
<dl className="vi-oracle-kv" style={{ marginTop: "0.5rem" }}>
|
|
132
|
+
<dt>run id</dt>
|
|
133
|
+
<dd>
|
|
134
|
+
<code>{m.run_id}</code>
|
|
135
|
+
</dd>
|
|
136
|
+
<dt>registry version</dt>
|
|
137
|
+
<dd>
|
|
138
|
+
<code>{m.registry_version ?? "not recorded"}</code>
|
|
139
|
+
</dd>
|
|
140
|
+
<dt>goal / snapshot / lock</dt>
|
|
141
|
+
<dd>
|
|
142
|
+
<code>{short(m.goal_digest)}</code> · <code>{short(m.snapshot_digest)}</code> · <code>{short(m.lock_digest)}</code>
|
|
143
|
+
</dd>
|
|
144
|
+
<dt>segments</dt>
|
|
145
|
+
<dd>{m.segment_digests?.length ?? 0}</dd>
|
|
146
|
+
<dt>video</dt>
|
|
147
|
+
<dd>{m.video_ref ? <code title={m.video_ref}>{short(m.video_ref, 56)}</code> : "none accepted"}</dd>
|
|
148
|
+
<dt>receipts</dt>
|
|
149
|
+
<dd>
|
|
150
|
+
{(m.receipts ?? []).length ? (
|
|
151
|
+
<ul className="vi-oracle-reasons" style={{ marginTop: 0 }}>
|
|
152
|
+
{(m.receipts ?? []).map((r, i) => (
|
|
153
|
+
<li key={i}>
|
|
154
|
+
<code>{r.capability}</code> → <code>{r.model_id}</code>
|
|
155
|
+
{r.worker ? <> on {r.worker}</> : null} · {when(r.started_at)}
|
|
156
|
+
{typeof r.duration_s === "number" ? <> · {r.duration_s.toFixed(1)}s</> : null}
|
|
157
|
+
{r.failure ? <Tag kind="alarm">{r.failure}</Tag> : null}
|
|
158
|
+
</li>
|
|
159
|
+
))}
|
|
160
|
+
</ul>
|
|
161
|
+
) : (
|
|
162
|
+
<span className="vi-knob-hint">none in the manifest</span>
|
|
163
|
+
)}
|
|
164
|
+
</dd>
|
|
165
|
+
<dt>state path</dt>
|
|
166
|
+
<dd>
|
|
167
|
+
<code>{m.state_path ?? "—"}</code>
|
|
168
|
+
</dd>
|
|
169
|
+
</dl>
|
|
170
|
+
{m.gap ? (
|
|
171
|
+
<p className="vi-oracle-gap">
|
|
172
|
+
gap at stage <code>{String(m.gap.stage)}</code>: {String(m.gap.diagnosis ?? "")}
|
|
173
|
+
{m.gap.requirement ? <> — required: {String(m.gap.requirement)}</> : null}
|
|
174
|
+
</p>
|
|
175
|
+
) : null}
|
|
176
|
+
{m.limitations?.length ? (
|
|
177
|
+
<ul className="vi-oracle-limits">
|
|
178
|
+
{m.limitations.map((l, i) => (
|
|
179
|
+
<li key={i}>{l}</li>
|
|
180
|
+
))}
|
|
181
|
+
</ul>
|
|
182
|
+
) : null}
|
|
183
|
+
{m.warnings?.length ? <RawJson label={`warnings (${m.warnings.length})`} value={m.warnings} /> : null}
|
|
184
|
+
<RawJson label="manifest json" value={m} />
|
|
185
|
+
</>
|
|
186
|
+
) : null}
|
|
187
|
+
</section>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function LedgerTab() {
|
|
192
|
+
const ledger = useLedgerSummary();
|
|
193
|
+
const caps = useCapabilities();
|
|
194
|
+
return (
|
|
195
|
+
<>
|
|
196
|
+
<section className="vi-oracle-panel" aria-label="Ledger">
|
|
197
|
+
<h3>
|
|
198
|
+
Reliability ledger
|
|
199
|
+
<span className="vi-oracle-sub">GET /api/oracle/ledger/summary — the ONE record workers share</span>
|
|
200
|
+
<button type="button" className="vi-btn vi-btn-sm" style={{ marginLeft: "auto" }} onClick={ledger.refresh}>
|
|
201
|
+
Refresh
|
|
202
|
+
</button>
|
|
203
|
+
</h3>
|
|
204
|
+
<GapNote gap={ledger.gap} />
|
|
205
|
+
{ledger.summary ? (
|
|
206
|
+
<>
|
|
207
|
+
<div className="vi-oracle-row">
|
|
208
|
+
{ledger.summary.remote ? <Tag kind="accent">remote: {String(ledger.summary.remote)}</Tag> : <Tag>local ledger</Tag>}
|
|
209
|
+
{Object.entries(ledger.summary)
|
|
210
|
+
.filter(([k, v]) => typeof v === "number" && k !== "ok")
|
|
211
|
+
.map(([k, v]) => (
|
|
212
|
+
<span key={k}>
|
|
213
|
+
<Tag>
|
|
214
|
+
{k}: {String(v)}
|
|
215
|
+
</Tag>
|
|
216
|
+
</span>
|
|
217
|
+
))}
|
|
218
|
+
</div>
|
|
219
|
+
<RawJson label="ledger summary json" value={ledger.summary} open />
|
|
220
|
+
</>
|
|
221
|
+
) : !ledger.gap ? (
|
|
222
|
+
<p className="vi-oracle-note">Reading the ledger…</p>
|
|
223
|
+
) : null}
|
|
224
|
+
</section>
|
|
225
|
+
<section className="vi-oracle-panel" aria-label="Capabilities">
|
|
226
|
+
<h3>
|
|
227
|
+
Capability catalog
|
|
228
|
+
<span className="vi-oracle-sub">GET /api/oracle/capabilities — registry {caps.caps?.registry_version ? <code>{short(caps.caps.registry_version)}</code> : "unknown"}</span>
|
|
229
|
+
</h3>
|
|
230
|
+
<GapNote gap={caps.gap} />
|
|
231
|
+
{caps.caps?.capabilities?.length ? (
|
|
232
|
+
<table className="vi-oracle-table">
|
|
233
|
+
<thead>
|
|
234
|
+
<tr>
|
|
235
|
+
<th>capability</th>
|
|
236
|
+
<th>eligible models</th>
|
|
237
|
+
</tr>
|
|
238
|
+
</thead>
|
|
239
|
+
<tbody>
|
|
240
|
+
{caps.caps.capabilities.map((c) => (
|
|
241
|
+
<tr key={c.name}>
|
|
242
|
+
<td>
|
|
243
|
+
<code>{c.name}</code>
|
|
244
|
+
</td>
|
|
245
|
+
<td>{c.model_ids?.length ? c.model_ids.map((m) => <code key={m} style={{ marginRight: "0.5rem" }}>{m}</code>) : <span className="vi-knob-hint">none eligible</span>}</td>
|
|
246
|
+
</tr>
|
|
247
|
+
))}
|
|
248
|
+
</tbody>
|
|
249
|
+
</table>
|
|
250
|
+
) : caps.caps && !caps.gap ? (
|
|
251
|
+
<p className="vi-oracle-note">The catalog answered with zero capabilities.</p>
|
|
252
|
+
) : null}
|
|
253
|
+
{caps.caps ? <RawJson label="capabilities json" value={caps.caps} /> : null}
|
|
254
|
+
</section>
|
|
255
|
+
</>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function OracleStation({ spec }: { spec: StationSpec }) {
|
|
260
|
+
const steward = useSteward();
|
|
261
|
+
const runs = useRunManifest();
|
|
262
|
+
const [tab, setTab] = useState<TabId>("dag");
|
|
263
|
+
|
|
264
|
+
const tabs: Array<{ id: TabId; label: string }> = [
|
|
265
|
+
{ id: "dag", label: "DAG" },
|
|
266
|
+
{ id: "spatial", label: "Spatial overlays" },
|
|
267
|
+
{ id: "ledger", label: "Ledger + catalog" },
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<section className="station-card station-wide vi-oracle" aria-label={spec.title}>
|
|
272
|
+
<p className="station-phase">{spec.phase}</p>
|
|
273
|
+
<p className="station-blurb">{spec.blurb}</p>
|
|
274
|
+
|
|
275
|
+
<StewardPanel api={steward} embedded={runs.manifest?.dag?.steward ?? null} />
|
|
276
|
+
<RunPicker runs={runs} />
|
|
277
|
+
<RunHeader runs={runs} />
|
|
278
|
+
|
|
279
|
+
<div>
|
|
280
|
+
<div className="vi-oracle-tabs" role="tablist" aria-label="Oracle views">
|
|
281
|
+
{tabs.map((t) => (
|
|
282
|
+
<button
|
|
283
|
+
key={t.id}
|
|
284
|
+
type="button"
|
|
285
|
+
role="tab"
|
|
286
|
+
id={`vi-oracle-tab-${t.id}`}
|
|
287
|
+
aria-selected={tab === t.id}
|
|
288
|
+
aria-controls={`vi-oracle-panel-${t.id}`}
|
|
289
|
+
className="vi-oracle-tab"
|
|
290
|
+
onClick={() => setTab(t.id)}
|
|
291
|
+
>
|
|
292
|
+
{t.label}
|
|
293
|
+
</button>
|
|
294
|
+
))}
|
|
295
|
+
</div>
|
|
296
|
+
<div role="tabpanel" id={`vi-oracle-panel-${tab}`} aria-labelledby={`vi-oracle-tab-${tab}`}>
|
|
297
|
+
{tab === "dag" ? <DagPanel runs={runs} /> : null}
|
|
298
|
+
{tab === "spatial" ? <SpatialTab manifest={runs.manifest} /> : null}
|
|
299
|
+
{tab === "ledger" ? <LedgerTab /> : null}
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</section>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* or-k18 — "spatial overlays", the honest placeholder.
|
|
3
|
+
*
|
|
4
|
+
* The spatial layer (oracle/spatial.py SpatialSceneManifest, oracle/spatial_eval.py
|
|
5
|
+
* SpatialEvalReport) exists, and the DAG grows a `spatial:<seg>` node when a
|
|
6
|
+
* segment carries a manifest — but the evaluator is NOT yet folded into the run
|
|
7
|
+
* manifest, and most runs carry `SegmentSpec.spatial_ref = None`. So this tab
|
|
8
|
+
* does not draw overlays it cannot source. It walks the manifest for anything
|
|
9
|
+
* shaped like a spatial manifest / eval report / spatial_manifest ref and prints
|
|
10
|
+
* what it finds with the JSON path it found it at; otherwise it says "no spatial
|
|
11
|
+
* conditioning" and quotes the run's own limitation line when there is one.
|
|
12
|
+
*/
|
|
13
|
+
import { useMemo } from "react";
|
|
14
|
+
|
|
15
|
+
import type { RunManifest } from "./oracleTypes";
|
|
16
|
+
import { findSpatial } from "./useOracle";
|
|
17
|
+
import { RawJson, Tag } from "./oracleShared";
|
|
18
|
+
|
|
19
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
20
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function SpatialTab({ manifest }: { manifest: RunManifest | null }) {
|
|
24
|
+
const finds = useMemo(() => findSpatial(manifest), [manifest]);
|
|
25
|
+
const spatialNodes = useMemo(
|
|
26
|
+
() => Object.entries(manifest?.dag?.nodes ?? {}).filter(([id]) => id.startsWith("spatial:")),
|
|
27
|
+
[manifest],
|
|
28
|
+
);
|
|
29
|
+
const limitation = (manifest?.limitations ?? []).find((l) => /spatial/i.test(l)) ?? null;
|
|
30
|
+
|
|
31
|
+
if (!manifest) {
|
|
32
|
+
return (
|
|
33
|
+
<section className="vi-oracle-panel" aria-label="Spatial overlays">
|
|
34
|
+
<h3>
|
|
35
|
+
Spatial overlays
|
|
36
|
+
<span className="vi-oracle-sub">placeholder — renders spatial manifests + evaluator metrics when a run carries them</span>
|
|
37
|
+
</h3>
|
|
38
|
+
<p className="vi-oracle-note">Pick a performance run to look for spatial conditioning.</p>
|
|
39
|
+
</section>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const manifests = finds.filter((f) => f.kind === "manifest");
|
|
44
|
+
const evals = finds.filter((f) => f.kind === "eval");
|
|
45
|
+
const refs = finds.filter((f) => f.kind === "ref");
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<section className="vi-oracle-panel" aria-label="Spatial overlays">
|
|
49
|
+
<h3>
|
|
50
|
+
Spatial overlays
|
|
51
|
+
<span className="vi-oracle-sub">placeholder — no geometry is drawn here yet; the data is shown as it is</span>
|
|
52
|
+
</h3>
|
|
53
|
+
|
|
54
|
+
<div className="vi-oracle-row">
|
|
55
|
+
<Tag kind={manifests.length ? "accent" : undefined}>{manifests.length} spatial manifest(s)</Tag>
|
|
56
|
+
<Tag kind={evals.length ? "accent" : undefined}>{evals.length} evaluator report(s)</Tag>
|
|
57
|
+
<Tag>{refs.length} spatial_manifest ref(s) on segments</Tag>
|
|
58
|
+
<Tag>{spatialNodes.length} spatial:* DAG node(s)</Tag>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{!manifests.length && !evals.length && !refs.length && !spatialNodes.length ? (
|
|
62
|
+
<>
|
|
63
|
+
<p className="vi-oracle-gap" role="status">
|
|
64
|
+
No spatial conditioning on this run. {limitation ? <>The run says so itself: “{limitation}”</> : <>No SegmentSpec carried a <code>spatial_ref</code>, no <code>spatial:<seg></code> node was built, and no SpatialSceneManifest or SpatialEvalReport appears anywhere in the manifest.</>}
|
|
65
|
+
</p>
|
|
66
|
+
<p className="vi-oracle-note">
|
|
67
|
+
When a run is conditioned, this tab will list each segment's manifest (coordinate system, camera, entities,
|
|
68
|
+
timebase, provenance) and the evaluator's per-rubric metrics with their codes and thresholds. Overlay
|
|
69
|
+
rendering on frames is not built; nothing here will pretend it is.
|
|
70
|
+
</p>
|
|
71
|
+
</>
|
|
72
|
+
) : null}
|
|
73
|
+
|
|
74
|
+
{spatialNodes.length ? (
|
|
75
|
+
<>
|
|
76
|
+
<h4>spatial DAG nodes</h4>
|
|
77
|
+
<table className="vi-oracle-table">
|
|
78
|
+
<thead>
|
|
79
|
+
<tr>
|
|
80
|
+
<th>node</th>
|
|
81
|
+
<th>state</th>
|
|
82
|
+
</tr>
|
|
83
|
+
</thead>
|
|
84
|
+
<tbody>
|
|
85
|
+
{spatialNodes.map(([id, st]) => (
|
|
86
|
+
<tr key={id}>
|
|
87
|
+
<td>
|
|
88
|
+
<code>{id}</code>
|
|
89
|
+
</td>
|
|
90
|
+
<td>{String(st)}</td>
|
|
91
|
+
</tr>
|
|
92
|
+
))}
|
|
93
|
+
</tbody>
|
|
94
|
+
</table>
|
|
95
|
+
<p className="vi-oracle-note">
|
|
96
|
+
A spatial node validates the segment's manifest against the lock and emits the conditioning request; its
|
|
97
|
+
outputs (conditioning, manifest_digest, validation) live in the journal and are not exported in the manifest.
|
|
98
|
+
</p>
|
|
99
|
+
</>
|
|
100
|
+
) : null}
|
|
101
|
+
|
|
102
|
+
{refs.length ? (
|
|
103
|
+
<>
|
|
104
|
+
<h4>spatial_manifest refs</h4>
|
|
105
|
+
<ul className="vi-oracle-reasons">
|
|
106
|
+
{refs.map((f) => (
|
|
107
|
+
<li key={f.path}>
|
|
108
|
+
<code>{f.path}</code> → <code>{String(f.value)}</code>
|
|
109
|
+
</li>
|
|
110
|
+
))}
|
|
111
|
+
</ul>
|
|
112
|
+
</>
|
|
113
|
+
) : null}
|
|
114
|
+
|
|
115
|
+
{manifests.map((f) => {
|
|
116
|
+
const m = isRecord(f.value) ? f.value : {};
|
|
117
|
+
return (
|
|
118
|
+
<div key={f.path}>
|
|
119
|
+
<h4>
|
|
120
|
+
spatial manifest <span className="vi-knob-hint">at {f.path || "(root)"}</span>
|
|
121
|
+
</h4>
|
|
122
|
+
<dl className="vi-oracle-kv">
|
|
123
|
+
<dt>segment</dt>
|
|
124
|
+
<dd>{String(m.segment_id ?? "—")}</dd>
|
|
125
|
+
<dt>schema</dt>
|
|
126
|
+
<dd>{String(m.schema_version ?? "—")}</dd>
|
|
127
|
+
<dt>revision</dt>
|
|
128
|
+
<dd>{String(m.artifact_revision ?? "—")}</dd>
|
|
129
|
+
<dt>entities</dt>
|
|
130
|
+
<dd>{Array.isArray(m.entities) ? m.entities.length : "—"}</dd>
|
|
131
|
+
<dt>fallbacks</dt>
|
|
132
|
+
<dd>{Array.isArray(m.fallbacks) ? m.fallbacks.length : "—"}</dd>
|
|
133
|
+
</dl>
|
|
134
|
+
<RawJson label="manifest json" value={m} open />
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
|
|
139
|
+
{evals.map((f) => {
|
|
140
|
+
const r = isRecord(f.value) ? f.value : {};
|
|
141
|
+
const metrics = Array.isArray(r.metrics) ? (r.metrics as Array<Record<string, unknown>>) : [];
|
|
142
|
+
return (
|
|
143
|
+
<div key={f.path}>
|
|
144
|
+
<h4>
|
|
145
|
+
evaluator report <span className="vi-knob-hint">at {f.path || "(root)"}</span>
|
|
146
|
+
</h4>
|
|
147
|
+
<div className="vi-oracle-row">
|
|
148
|
+
<Tag kind={r.ok ? "ok" : "alarm"}>{r.ok ? "ok" : "drift"}</Tag>
|
|
149
|
+
<Tag>segment {String(r.segment_id ?? "—")}</Tag>
|
|
150
|
+
<Tag>{Array.isArray(r.measured) ? r.measured.length : 0} measured</Tag>
|
|
151
|
+
<Tag>{Array.isArray(r.skipped) ? r.skipped.length : 0} skipped</Tag>
|
|
152
|
+
</div>
|
|
153
|
+
{metrics.length ? (
|
|
154
|
+
<table className="vi-oracle-table" style={{ marginTop: "0.5rem" }}>
|
|
155
|
+
<thead>
|
|
156
|
+
<tr>
|
|
157
|
+
<th>metric</th>
|
|
158
|
+
<th>value</th>
|
|
159
|
+
<th>threshold</th>
|
|
160
|
+
<th>code</th>
|
|
161
|
+
</tr>
|
|
162
|
+
</thead>
|
|
163
|
+
<tbody>
|
|
164
|
+
{metrics.map((m, i) => (
|
|
165
|
+
<tr key={i}>
|
|
166
|
+
<td>{String(m.name ?? m.rubric ?? i)}</td>
|
|
167
|
+
<td>
|
|
168
|
+
<code>{String(m.value ?? m.measured ?? "—")}</code>
|
|
169
|
+
</td>
|
|
170
|
+
<td>
|
|
171
|
+
<code>{String(m.threshold ?? "—")}</code>
|
|
172
|
+
</td>
|
|
173
|
+
<td>{m.code ? <Tag kind="warn">{String(m.code)}</Tag> : <span className="vi-knob-hint">—</span>}</td>
|
|
174
|
+
</tr>
|
|
175
|
+
))}
|
|
176
|
+
</tbody>
|
|
177
|
+
</table>
|
|
178
|
+
) : null}
|
|
179
|
+
<RawJson label="evaluator json" value={r} />
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
})}
|
|
183
|
+
</section>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* or-k18 — the steward report: the system checking itself (k113c).
|
|
3
|
+
*
|
|
4
|
+
* GET /api/oracle/steward is report-only. The "Re-run" button POSTs, which
|
|
5
|
+
* re-audits AND applies bounded rebalancing to the live selector — the button
|
|
6
|
+
* says so, because "re-run" that silently changes policy would be a lie.
|
|
7
|
+
*
|
|
8
|
+
* Findings are grouped by severity (alarm → warn → info). A `matrix_stale`
|
|
9
|
+
* finding is promoted to its own banner regardless of severity: a stale
|
|
10
|
+
* routing matrix means every selection below is reasoning from old evidence.
|
|
11
|
+
*/
|
|
12
|
+
import { useMemo } from "react";
|
|
13
|
+
|
|
14
|
+
import type { StewardFinding, StewardReport } from "./oracleTypes";
|
|
15
|
+
import type { StewardApi } from "./useOracle";
|
|
16
|
+
import { GapNote, RawJson, Tag, ago, when } from "./oracleShared";
|
|
17
|
+
|
|
18
|
+
const ORDER: Array<"alarm" | "warn" | "info"> = ["alarm", "warn", "info"];
|
|
19
|
+
|
|
20
|
+
function bucket(findings: StewardFinding[]): Record<string, StewardFinding[]> {
|
|
21
|
+
const out: Record<string, StewardFinding[]> = { alarm: [], warn: [], info: [], other: [] };
|
|
22
|
+
for (const f of findings) {
|
|
23
|
+
const k = ORDER.includes(f.severity as "alarm") ? f.severity : "other";
|
|
24
|
+
out[k].push(f);
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sevOf(f: StewardFinding): "alarm" | "warn" | "info" {
|
|
30
|
+
return (ORDER.includes(f.severity as "alarm") ? f.severity : "info") as "alarm" | "warn" | "info";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The BODY of one finding row. The `<li key>` wrapper stays at the call site —
|
|
34
|
+
* this project's scoped tsconfig rejects a `key` prop on a locally-declared
|
|
35
|
+
* component (the JobRow idiom in ActiveProcessesStation). */
|
|
36
|
+
function FindingBody({ f }: { f: StewardFinding }) {
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<div className="vi-oracle-finding-head">
|
|
40
|
+
<Tag kind={sevOf(f)}>{f.severity}</Tag>
|
|
41
|
+
<Tag>{f.kind}</Tag>
|
|
42
|
+
{f.capability ? <code>{f.capability}</code> : <span className="vi-knob-hint">fleet-wide</span>}
|
|
43
|
+
</div>
|
|
44
|
+
<div style={{ marginTop: "0.25rem" }}>{f.message}</div>
|
|
45
|
+
{f.action ? <p className="vi-oracle-finding-action">action: {f.action}</p> : null}
|
|
46
|
+
{f.evidence && Object.keys(f.evidence).length ? <RawJson label="evidence" value={f.evidence} /> : null}
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function StewardPanel({ api, embedded }: { api: StewardApi; embedded?: StewardReport | null }) {
|
|
52
|
+
const report = api.report;
|
|
53
|
+
const findings = useMemo(() => report?.findings ?? [], [report]);
|
|
54
|
+
const groups = useMemo(() => bucket(findings), [findings]);
|
|
55
|
+
const stale = findings.filter((f) => f.kind === "matrix_stale");
|
|
56
|
+
const busy = api.busy != null;
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<section className="vi-oracle-panel" aria-label="Steward report">
|
|
60
|
+
<h3>
|
|
61
|
+
Steward
|
|
62
|
+
<span className="vi-oracle-sub">the selector audited from its own ledger — GET /api/oracle/steward</span>
|
|
63
|
+
</h3>
|
|
64
|
+
|
|
65
|
+
<div className="vi-oracle-row">
|
|
66
|
+
<button type="button" className="vi-btn vi-btn-sm" disabled={busy} onClick={api.refresh}>
|
|
67
|
+
{api.busy === "get" ? "Reading…" : "Refresh report"}
|
|
68
|
+
</button>
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
className="vi-btn vi-btn-sm vi-btn-accent"
|
|
72
|
+
disabled={busy}
|
|
73
|
+
onClick={api.rerun}
|
|
74
|
+
title="POST /api/oracle/steward — re-audits AND applies bounded rebalancing to the live selection policy"
|
|
75
|
+
>
|
|
76
|
+
{api.busy === "post" ? "Re-running…" : "Re-run + apply rebalancing"}
|
|
77
|
+
</button>
|
|
78
|
+
<span className="vi-knob-hint">
|
|
79
|
+
last run (server): <code>{when(report?.at)}</code>
|
|
80
|
+
{api.fetchedAt != null ? <> · fetched {ago(api.fetchedAt)} (browser clock)</> : null}
|
|
81
|
+
</span>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<GapNote gap={api.gap} />
|
|
85
|
+
|
|
86
|
+
{report ? (
|
|
87
|
+
<>
|
|
88
|
+
<div className="vi-oracle-row" style={{ marginTop: "0.6rem" }}>
|
|
89
|
+
<Tag kind={report.ok ? "ok" : "alarm"}>{report.ok ? "no alarms" : "alarms present"}</Tag>
|
|
90
|
+
{report.applied ? <Tag kind="accent">policy applied this call</Tag> : <Tag>report only</Tag>}
|
|
91
|
+
{report.policy_changed ? <Tag kind="warn">policy changed</Tag> : null}
|
|
92
|
+
{typeof report.ledger_rows === "number" ? <Tag>ledger rows: {report.ledger_rows}</Tag> : null}
|
|
93
|
+
<Tag>
|
|
94
|
+
{groups.alarm.length} alarm · {groups.warn.length} warn · {groups.info.length} info
|
|
95
|
+
</Tag>
|
|
96
|
+
</div>
|
|
97
|
+
{report.summary ? <p className="vi-oracle-note">{report.summary}</p> : null}
|
|
98
|
+
|
|
99
|
+
{stale.length ? (
|
|
100
|
+
<div className="vi-oracle-stale" role="alert">
|
|
101
|
+
<strong>Routing matrix is stale.</strong> Selections below reason from old evidence.
|
|
102
|
+
<ul className="vi-oracle-reasons">
|
|
103
|
+
{stale.map((f, i) => (
|
|
104
|
+
<li key={i}>
|
|
105
|
+
{f.message}
|
|
106
|
+
{f.action ? <> — {f.action}</> : null}
|
|
107
|
+
</li>
|
|
108
|
+
))}
|
|
109
|
+
</ul>
|
|
110
|
+
</div>
|
|
111
|
+
) : null}
|
|
112
|
+
|
|
113
|
+
{ORDER.map((sev) =>
|
|
114
|
+
groups[sev].length ? (
|
|
115
|
+
<div key={sev}>
|
|
116
|
+
<h4>
|
|
117
|
+
{sev} ({groups[sev].length})
|
|
118
|
+
</h4>
|
|
119
|
+
<ul className="vi-oracle-findings">
|
|
120
|
+
{groups[sev].map((f, i) => (
|
|
121
|
+
<li key={`${sev}-${f.kind}-${f.capability ?? ""}-${i}`} className={`vi-oracle-finding vi-oracle-finding-${sevOf(f)}`}>
|
|
122
|
+
<FindingBody f={f} />
|
|
123
|
+
</li>
|
|
124
|
+
))}
|
|
125
|
+
</ul>
|
|
126
|
+
</div>
|
|
127
|
+
) : null,
|
|
128
|
+
)}
|
|
129
|
+
{groups.other.length ? (
|
|
130
|
+
<div>
|
|
131
|
+
<h4>unclassified severity ({groups.other.length})</h4>
|
|
132
|
+
<ul className="vi-oracle-findings">
|
|
133
|
+
{groups.other.map((f, i) => (
|
|
134
|
+
<li key={`o-${i}`} className="vi-oracle-finding vi-oracle-finding-info">
|
|
135
|
+
<FindingBody f={f} />
|
|
136
|
+
</li>
|
|
137
|
+
))}
|
|
138
|
+
</ul>
|
|
139
|
+
</div>
|
|
140
|
+
) : null}
|
|
141
|
+
{!findings.length ? (
|
|
142
|
+
<p className="vi-oracle-note">The steward returned zero findings — a clean fleet normally still says so with numbers; treat an empty list as "nothing audited".</p>
|
|
143
|
+
) : null}
|
|
144
|
+
|
|
145
|
+
{report.selection_policy ? <RawJson label="selection policy (live)" value={report.selection_policy} /> : null}
|
|
146
|
+
{report.policy_after ? <RawJson label="policy after this call" value={report.policy_after} /> : null}
|
|
147
|
+
<RawJson label="steward report json" value={report} />
|
|
148
|
+
</>
|
|
149
|
+
) : !api.gap ? (
|
|
150
|
+
<p className="vi-oracle-note">Reading the steward…</p>
|
|
151
|
+
) : null}
|
|
152
|
+
|
|
153
|
+
{embedded ? (
|
|
154
|
+
<>
|
|
155
|
+
<h4>steward snapshot embedded in the selected run</h4>
|
|
156
|
+
<p className="vi-oracle-note">
|
|
157
|
+
This is the report the run itself recorded at the time (manifest.dag.steward), not the live one above.
|
|
158
|
+
{embedded.summary ? <> Summary: {embedded.summary}</> : null}
|
|
159
|
+
</p>
|
|
160
|
+
<RawJson label="manifest.dag.steward" value={embedded} />
|
|
161
|
+
</>
|
|
162
|
+
) : null}
|
|
163
|
+
</section>
|
|
164
|
+
);
|
|
165
|
+
}
|