cvpfus Codex commited on
Commit
e898b4a
·
1 Parent(s): 79c2c78

Expose runtime setup evidence

Browse files

Co-authored-by: Codex <codex@openai.com>

Files changed (7) hide show
  1. FIELD_NOTES.md +2 -0
  2. README.md +5 -1
  3. app.py +66 -0
  4. scripts/verify.py +20 -0
  5. static/app.css +31 -5
  6. static/app.js +32 -0
  7. static/index.html +5 -0
FIELD_NOTES.md CHANGED
@@ -26,6 +26,8 @@ Tiny Narrator is a small-model accessibility article reader. It is not a generic
26
 
27
  The app exposes the same budget through `/api/model-budget`, including numeric `params_billion` values and a boolean `all_models_within_limit` check. The session panel renders that payload so the Tiny Titan claim is visible during the demo.
28
 
 
 
29
  ## Reader Mode Behavior
30
 
31
  The frontend creates a reading queue from semantic nodes: headings, paragraphs, quotes, figures, captions, and controls. It speaks one item at a time, keeps keyboard focus synchronized with the active node, and exposes the current narration through an `aria-live` region.
 
26
 
27
  The app exposes the same budget through `/api/model-budget`, including numeric `params_billion` values and a boolean `all_models_within_limit` check. The session panel renders that payload so the Tiny Titan claim is visible during the demo.
28
 
29
+ Runtime setup is also data-backed. `/api/runtime-setup` lists the app command, llama.cpp launch command, model-specific runtime paths, environment values, and fallbacks. The session panel renders a compact version so the demo can distinguish real model wiring from deterministic safety nets.
30
+
31
  ## Reader Mode Behavior
32
 
33
  The frontend creates a reading queue from semantic nodes: headings, paragraphs, quotes, figures, captions, and controls. It speaks one item at a time, keeps keyboard focus synchronized with the active node, and exposes the current narration through an `aria-live` region.
README.md CHANGED
@@ -50,7 +50,7 @@ Start the app:
50
  python app.py
51
  ```
52
 
53
- Open the local URL printed by Gradio. The custom frontend calls `/api/reader-brain`, `/api/image-descriptions`, `/api/describe-image`, `/api/speak`, and `/api/generate-image`.
54
 
55
  Useful environment variables:
56
 
@@ -71,6 +71,8 @@ The verifier checks syntax, static assets, deterministic fallback model paths, a
71
 
72
  `/api/model-budget` exposes numeric parameter counts for every model role and reports whether the full stack stays within the 4B Tiny Titan limit.
73
 
 
 
74
  ## Screen Reader Mode
75
 
76
  The frontend builds a reading queue from semantic article nodes. When screen-reader mode is on:
@@ -102,4 +104,6 @@ Reader-brain, image-description, speech, and image-generation responses include
102
 
103
  The session panel also renders a manifest-backed demo checklist for Tiny Titan, Llama Champion, Off-Brand, and Field Notes evidence. A model-budget panel reads `/api/model-budget` so judges can see each role's parameter count in the live app.
104
 
 
 
105
  `/api/runtime-status` performs a short readiness check for llama.cpp and local speech dependencies, then reports which fallback paths are ready for a live demo.
 
50
  python app.py
51
  ```
52
 
53
+ Open the local URL printed by Gradio. The custom frontend calls `/api/reader-brain`, `/api/image-descriptions`, `/api/describe-image`, `/api/speak`, `/api/generate-image`, `/api/model-budget`, and `/api/runtime-setup`.
54
 
55
  Useful environment variables:
56
 
 
71
 
72
  `/api/model-budget` exposes numeric parameter counts for every model role and reports whether the full stack stays within the 4B Tiny Titan limit.
73
 
74
+ `/api/runtime-setup` exposes the commands, environment values, and fallback paths used for the model stack so the demo can be reproduced from the same data the UI displays.
75
+
76
  ## Screen Reader Mode
77
 
78
  The frontend builds a reading queue from semantic article nodes. When screen-reader mode is on:
 
104
 
105
  The session panel also renders a manifest-backed demo checklist for Tiny Titan, Llama Champion, Off-Brand, and Field Notes evidence. A model-budget panel reads `/api/model-budget` so judges can see each role's parameter count in the live app.
106
 
107
+ A runtime-plan panel reads `/api/runtime-setup` and summarizes each model path's runtime plus fallback, keeping the live demo honest about what is online and what is deterministic.
108
+
109
  `/api/runtime-status` performs a short readiness check for llama.cpp and local speech dependencies, then reports which fallback paths are ready for a live demo.
app.py CHANGED
@@ -146,6 +146,61 @@ def model_budget_core() -> dict[str, Any]:
146
  }
147
 
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  ARTICLE_MANIFEST: dict[str, Any] = {
150
  "title": "A tiny model reader that turns articles into guided narration",
151
  "reader_controls": [
@@ -162,6 +217,7 @@ ARTICLE_MANIFEST: dict[str, Any] = {
162
  "images": ARTICLE_IMAGES,
163
  "models": MODEL_MANIFEST,
164
  "model_budget": model_budget_core(),
 
165
  "reader_settings": READER_SETTINGS,
166
  "award_evidence": AWARD_EVIDENCE,
167
  }
@@ -477,6 +533,11 @@ async def model_budget() -> JSONResponse:
477
  return _json(model_budget_core())
478
 
479
 
 
 
 
 
 
480
  @app.get("/api/runtime-status")
481
  async def runtime_status() -> JSONResponse:
482
  return _json(_runtime_status_core())
@@ -527,6 +588,11 @@ def model_budget_api() -> str:
527
  return json.dumps(model_budget_core())
528
 
529
 
 
 
 
 
 
530
  @app.api(name="speak")
531
  def speak_api(text: str, voice: str = "af_heart", speed: float = 1.0) -> str:
532
  return json.dumps(speak_core(text, voice, speed))
 
146
  }
147
 
148
 
149
+ def runtime_setup_core() -> dict[str, Any]:
150
+ return {
151
+ "ok": True,
152
+ "app": {
153
+ "runtime": "Gradio Server",
154
+ "command": "python app.py",
155
+ "env": {
156
+ "GRADIO_SERVER_NAME": GRADIO_SERVER_NAME,
157
+ "GRADIO_SERVER_PORT": str(GRADIO_SERVER_PORT),
158
+ },
159
+ },
160
+ "steps": [
161
+ {
162
+ "role": "reader_brain",
163
+ "label": "Reader brain",
164
+ "model": MODEL_MANIFEST["reader_brain"]["id"],
165
+ "runtime": "llama.cpp",
166
+ "command": (
167
+ "llama-server -hf nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF:Q4_K_M "
168
+ "--alias narrator-brain --port 8080 --host 0.0.0.0"
169
+ ),
170
+ "env": {"LLAMA_CPP_BASE_URL": LLAMA_CPP_BASE_URL, "LLAMA_CPP_MODEL": LLAMA_CPP_MODEL},
171
+ "fallback": "rule-based local narration",
172
+ },
173
+ {
174
+ "role": "speech",
175
+ "label": "Speech",
176
+ "model": MODEL_MANIFEST["speech"]["id"],
177
+ "runtime": "Python Kokoro",
178
+ "command": "python -m pip install kokoro soundfile",
179
+ "env": {},
180
+ "fallback": "browser speech plus visible transcript",
181
+ },
182
+ {
183
+ "role": "vision",
184
+ "label": "Image descriptions",
185
+ "model": MODEL_MANIFEST["vision"]["id"],
186
+ "runtime": "Python integration planned",
187
+ "command": "planned MiniCPM-V adapter",
188
+ "env": {},
189
+ "fallback": "cached deterministic alt text",
190
+ },
191
+ {
192
+ "role": "image_generation",
193
+ "label": "Article images",
194
+ "model": MODEL_MANIFEST["image_generation"]["id"],
195
+ "runtime": "Python integration planned",
196
+ "command": "planned FLUX.2 klein adapter",
197
+ "env": {},
198
+ "fallback": "bundled generated article assets",
199
+ },
200
+ ],
201
+ }
202
+
203
+
204
  ARTICLE_MANIFEST: dict[str, Any] = {
205
  "title": "A tiny model reader that turns articles into guided narration",
206
  "reader_controls": [
 
217
  "images": ARTICLE_IMAGES,
218
  "models": MODEL_MANIFEST,
219
  "model_budget": model_budget_core(),
220
+ "runtime_setup": runtime_setup_core(),
221
  "reader_settings": READER_SETTINGS,
222
  "award_evidence": AWARD_EVIDENCE,
223
  }
 
533
  return _json(model_budget_core())
534
 
535
 
536
+ @app.get("/api/runtime-setup")
537
+ async def runtime_setup() -> JSONResponse:
538
+ return _json(runtime_setup_core())
539
+
540
+
541
  @app.get("/api/runtime-status")
542
  async def runtime_status() -> JSONResponse:
543
  return _json(_runtime_status_core())
 
588
  return json.dumps(model_budget_core())
589
 
590
 
591
+ @app.api(name="runtime_setup")
592
+ def runtime_setup_api() -> str:
593
+ return json.dumps(runtime_setup_core())
594
+
595
+
596
  @app.api(name="speak")
597
  def speak_api(text: str, voice: str = "af_heart", speed: float = 1.0) -> str:
598
  return json.dumps(speak_core(text, voice, speed))
scripts/verify.py CHANGED
@@ -101,6 +101,8 @@ def verify_routes() -> None:
101
  assert_true("awardEvidenceList" in home.text, "Home route should include award evidence list")
102
  assert_true("budgetStatus" in home.text, "Home route should include model budget status")
103
  assert_true("modelBudgetList" in home.text, "Home route should include model budget list")
 
 
104
 
105
  health = client.get("/api/health")
106
  assert_true(health.status_code == 200, "Health route should return 200")
@@ -143,6 +145,10 @@ def verify_routes() -> None:
143
  manifest_payload["model_budget"]["all_models_within_limit"],
144
  "Manifest should prove every model is within the Tiny Titan limit",
145
  )
 
 
 
 
146
 
147
  awards = client.get("/api/award-evidence")
148
  assert_true(awards.status_code == 200, "Award evidence route should return 200")
@@ -164,6 +170,20 @@ def verify_routes() -> None:
164
  "Every model budget item should be at or below the limit",
165
  )
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  runtime = client.get("/api/runtime-status")
168
  assert_true(runtime.status_code == 200, "Runtime status route should return 200")
169
  runtime_payload = runtime.json()
 
101
  assert_true("awardEvidenceList" in home.text, "Home route should include award evidence list")
102
  assert_true("budgetStatus" in home.text, "Home route should include model budget status")
103
  assert_true("modelBudgetList" in home.text, "Home route should include model budget list")
104
+ assert_true("runtimeSetupStatus" in home.text, "Home route should include runtime setup status")
105
+ assert_true("runtimeSetupList" in home.text, "Home route should include runtime setup list")
106
 
107
  health = client.get("/api/health")
108
  assert_true(health.status_code == 200, "Health route should return 200")
 
145
  manifest_payload["model_budget"]["all_models_within_limit"],
146
  "Manifest should prove every model is within the Tiny Titan limit",
147
  )
148
+ assert_true(
149
+ len(manifest_payload["runtime_setup"]["steps"]) == 4,
150
+ "Manifest should expose setup steps for each model role",
151
+ )
152
 
153
  awards = client.get("/api/award-evidence")
154
  assert_true(awards.status_code == 200, "Award evidence route should return 200")
 
170
  "Every model budget item should be at or below the limit",
171
  )
172
 
173
+ setup = client.get("/api/runtime-setup")
174
+ assert_true(setup.status_code == 200, "Runtime setup route should return 200")
175
+ setup_payload = setup.json()
176
+ assert_true(setup_payload["ok"], "Runtime setup payload should be ok")
177
+ assert_true(
178
+ {item["role"] for item in setup_payload["steps"]}
179
+ == {"reader_brain", "speech", "vision", "image_generation"},
180
+ "Runtime setup should cover every model path",
181
+ )
182
+ assert_true(
183
+ "llama-server" in setup_payload["steps"][0]["command"],
184
+ "Runtime setup should include the llama.cpp launch command",
185
+ )
186
+
187
  runtime = client.get("/api/runtime-status")
188
  assert_true(runtime.status_code == 200, "Runtime status route should return 200")
189
  runtime_payload = runtime.json()
static/app.css CHANGED
@@ -298,7 +298,16 @@ dd {
298
  margin-top: 22px;
299
  }
300
 
301
- .budget-header span {
 
 
 
 
 
 
 
 
 
302
  color: var(--accent-strong);
303
  font-size: 0.76rem;
304
  font-weight: 850;
@@ -369,14 +378,24 @@ dd {
369
  list-style: none;
370
  }
371
 
372
- .budget-list li {
 
 
 
 
 
 
 
 
 
373
  border: 1px solid var(--line);
374
  border-radius: 8px;
375
  padding: 10px 12px;
376
  background: var(--surface);
377
  }
378
 
379
- .budget-row {
 
380
  display: flex;
381
  align-items: center;
382
  justify-content: space-between;
@@ -386,7 +405,8 @@ dd {
386
  text-transform: uppercase;
387
  }
388
 
389
- .budget-pill {
 
390
  border-radius: 999px;
391
  background: rgba(197, 69, 44, 0.12);
392
  color: var(--focus);
@@ -394,7 +414,13 @@ dd {
394
  font-size: 0.7rem;
395
  }
396
 
397
- .budget-list p {
 
 
 
 
 
 
398
  margin: 6px 0 0;
399
  color: var(--muted);
400
  font-size: 0.78rem;
 
298
  margin-top: 22px;
299
  }
300
 
301
+ .runtime-header {
302
+ display: flex;
303
+ align-items: center;
304
+ justify-content: space-between;
305
+ gap: 10px;
306
+ margin-top: 22px;
307
+ }
308
+
309
+ .budget-header span,
310
+ .runtime-header span {
311
  color: var(--accent-strong);
312
  font-size: 0.76rem;
313
  font-weight: 850;
 
378
  list-style: none;
379
  }
380
 
381
+ .runtime-list {
382
+ display: grid;
383
+ gap: 8px;
384
+ margin: 12px 0 0;
385
+ padding: 0;
386
+ list-style: none;
387
+ }
388
+
389
+ .budget-list li,
390
+ .runtime-list li {
391
  border: 1px solid var(--line);
392
  border-radius: 8px;
393
  padding: 10px 12px;
394
  background: var(--surface);
395
  }
396
 
397
+ .budget-row,
398
+ .runtime-row {
399
  display: flex;
400
  align-items: center;
401
  justify-content: space-between;
 
405
  text-transform: uppercase;
406
  }
407
 
408
+ .budget-pill,
409
+ .runtime-pill {
410
  border-radius: 999px;
411
  background: rgba(197, 69, 44, 0.12);
412
  color: var(--focus);
 
414
  font-size: 0.7rem;
415
  }
416
 
417
+ .runtime-pill {
418
+ background: rgba(31, 111, 104, 0.12);
419
+ color: var(--accent-strong);
420
+ }
421
+
422
+ .budget-list p,
423
+ .runtime-list p {
424
  margin: 6px 0 0;
425
  color: var(--muted);
426
  font-size: 0.78rem;
static/app.js CHANGED
@@ -20,6 +20,8 @@ const clearTranscriptButton = document.querySelector("#clearTranscriptButton");
20
  const awardEvidenceList = document.querySelector("#awardEvidenceList");
21
  const budgetStatus = document.querySelector("#budgetStatus");
22
  const modelBudgetList = document.querySelector("#modelBudgetList");
 
 
23
 
24
  const controls = {
25
  prev: document.querySelector("#prevButton"),
@@ -196,6 +198,35 @@ async function loadModelBudget() {
196
  }
197
  }
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  async function loadRuntimeStatus() {
200
  try {
201
  const payload = await postJson("/api/runtime-status");
@@ -518,6 +549,7 @@ controls.play.addEventListener("click", () => {
518
  loadManifest();
519
  loadAwardEvidence();
520
  loadModelBudget();
 
521
  loadRuntimeStatus();
522
  loadImageDescriptions();
523
  updateSpeedValue();
 
20
  const awardEvidenceList = document.querySelector("#awardEvidenceList");
21
  const budgetStatus = document.querySelector("#budgetStatus");
22
  const modelBudgetList = document.querySelector("#modelBudgetList");
23
+ const runtimeSetupStatus = document.querySelector("#runtimeSetupStatus");
24
+ const runtimeSetupList = document.querySelector("#runtimeSetupList");
25
 
26
  const controls = {
27
  prev: document.querySelector("#prevButton"),
 
198
  }
199
  }
200
 
201
+ async function loadRuntimeSetup() {
202
+ try {
203
+ const payload = await postJson("/api/runtime-setup");
204
+ runtimeSetupStatus.textContent = `${payload.steps.length} paths`;
205
+ runtimeSetupList.innerHTML = payload.steps
206
+ .map((step) => `
207
+ <li>
208
+ <div class="runtime-row">
209
+ <span>${escapeHtml(step.label)}</span>
210
+ <span class="runtime-pill">${escapeHtml(step.runtime)}</span>
211
+ </div>
212
+ <p>${escapeHtml(step.fallback)}</p>
213
+ </li>
214
+ `)
215
+ .join("");
216
+ } catch {
217
+ runtimeSetupStatus.textContent = "Unavailable";
218
+ runtimeSetupList.innerHTML = `
219
+ <li>
220
+ <div class="runtime-row">
221
+ <span>Runtime plan</span>
222
+ <span class="runtime-pill">offline</span>
223
+ </div>
224
+ <p>Setup evidence is unavailable.</p>
225
+ </li>
226
+ `;
227
+ }
228
+ }
229
+
230
  async function loadRuntimeStatus() {
231
  try {
232
  const payload = await postJson("/api/runtime-status");
 
549
  loadManifest();
550
  loadAwardEvidence();
551
  loadModelBudget();
552
+ loadRuntimeSetup();
553
  loadRuntimeStatus();
554
  loadImageDescriptions();
555
  updateSpeedValue();
static/index.html CHANGED
@@ -131,6 +131,11 @@
131
  <span id="budgetStatus">Checking</span>
132
  </div>
133
  <ul id="modelBudgetList" class="budget-list" aria-label="Tiny Titan model budget"></ul>
 
 
 
 
 
134
  </aside>
135
  </main>
136
 
 
131
  <span id="budgetStatus">Checking</span>
132
  </div>
133
  <ul id="modelBudgetList" class="budget-list" aria-label="Tiny Titan model budget"></ul>
134
+ <div class="runtime-header">
135
+ <h3>Runtime Plan</h3>
136
+ <span id="runtimeSetupStatus">Loading</span>
137
+ </div>
138
+ <ul id="runtimeSetupList" class="runtime-list" aria-label="Model runtime setup plan"></ul>
139
  </aside>
140
  </main>
141