cvpfus Codex commited on
Commit
921d7f6
·
1 Parent(s): 38c87e2

Report runtime readiness in demo

Browse files

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

Files changed (6) hide show
  1. FIELD_NOTES.md +2 -0
  2. README.md +2 -0
  3. app.py +64 -0
  4. scripts/verify.py +12 -0
  5. static/app.js +15 -0
  6. static/index.html +4 -0
FIELD_NOTES.md CHANGED
@@ -54,3 +54,5 @@ Reader settings are manifest-backed: the app exposes known Kokoro voices and spe
54
  Every model-facing backend path reports `elapsed_ms`. The frontend surfaces the latest end-to-end reader latency and stores per-item timing in the transcript, giving the build report concrete evidence for the small-model responsiveness claim.
55
 
56
  The app exposes `/api/award-evidence` and renders that data in the session panel, so the live demo can point directly to the hackathon bonus targets it is designed to satisfy.
 
 
 
54
  Every model-facing backend path reports `elapsed_ms`. The frontend surfaces the latest end-to-end reader latency and stores per-item timing in the transcript, giving the build report concrete evidence for the small-model responsiveness claim.
55
 
56
  The app exposes `/api/award-evidence` and renders that data in the session panel, so the live demo can point directly to the hackathon bonus targets it is designed to satisfy.
57
+
58
+ `/api/runtime-status` keeps the demo honest: if llama.cpp or Kokoro is unavailable, the UI labels the fallback state instead of silently pretending that every model path is online.
README.md CHANGED
@@ -95,3 +95,5 @@ The reader bar exposes Kokoro voice selection and speaking speed controls. Defau
95
  Reader-brain, image-description, speech, and image-generation responses include `elapsed_ms`. The session panel and transcript show recent latency so the Field Notes can discuss responsiveness with concrete numbers.
96
 
97
  The session panel also renders a manifest-backed demo checklist for Tiny Titan, Llama Champion, Off-Brand, and Field Notes evidence.
 
 
 
95
  Reader-brain, image-description, speech, and image-generation responses include `elapsed_ms`. The session panel and transcript show recent latency so the Field Notes can discuss responsiveness with concrete numbers.
96
 
97
  The session panel also renders a manifest-backed demo checklist for Tiny Titan, Llama Champion, Off-Brand, and Field Notes evidence.
98
+
99
+ `/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
@@ -1,5 +1,6 @@
1
  from __future__ import annotations
2
 
 
3
  import json
4
  import os
5
  import time
@@ -170,6 +171,64 @@ def _elapsed_ms(start: float) -> int:
170
  return round((time.perf_counter() - start) * 1000)
171
 
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  def _compact_text(text: str, limit: int = 220) -> str:
174
  normalized = " ".join(text.split())
175
  if len(normalized) <= limit:
@@ -381,6 +440,11 @@ async def award_evidence() -> JSONResponse:
381
  return _json({"ok": True, "items": AWARD_EVIDENCE})
382
 
383
 
 
 
 
 
 
384
  @app.post("/api/reader-brain")
385
  async def reader_brain_endpoint(payload: ReaderBrainRequest) -> JSONResponse:
386
  return _json(reader_brain_core(payload.node_type, payload.text, payload.position, payload.mode))
 
1
  from __future__ import annotations
2
 
3
+ import importlib.util
4
  import json
5
  import os
6
  import time
 
171
  return round((time.perf_counter() - start) * 1000)
172
 
173
 
174
+ def _runtime_status_core() -> dict[str, Any]:
175
+ start = time.perf_counter()
176
+ llama_start = time.perf_counter()
177
+ llama_status: dict[str, Any]
178
+ try:
179
+ request = urllib.request.Request(f"{LLAMA_CPP_BASE_URL}/models", method="GET")
180
+ with urllib.request.urlopen(request, timeout=1.5) as response:
181
+ payload = json.loads(response.read().decode("utf-8"))
182
+ model_ids = [item.get("id", "") for item in payload.get("data", []) if isinstance(item, dict)]
183
+ llama_status = {
184
+ "available": True,
185
+ "status": "online",
186
+ "base_url": LLAMA_CPP_BASE_URL,
187
+ "model": LLAMA_CPP_MODEL,
188
+ "models": model_ids,
189
+ "elapsed_ms": _elapsed_ms(llama_start),
190
+ }
191
+ except (OSError, urllib.error.URLError, TimeoutError, json.JSONDecodeError) as exc:
192
+ llama_status = {
193
+ "available": False,
194
+ "status": "fallback-ready",
195
+ "base_url": LLAMA_CPP_BASE_URL,
196
+ "model": LLAMA_CPP_MODEL,
197
+ "fallback": "rule-based local narration",
198
+ "warning": exc.__class__.__name__,
199
+ "elapsed_ms": _elapsed_ms(llama_start),
200
+ }
201
+
202
+ kokoro_available = importlib.util.find_spec("kokoro") is not None
203
+ soundfile_available = importlib.util.find_spec("soundfile") is not None
204
+
205
+ return {
206
+ "ok": True,
207
+ "reader_brain": llama_status,
208
+ "vision": {
209
+ "available": False,
210
+ "status": "fallback-ready",
211
+ "model": "OpenBMB MiniCPM-V-2",
212
+ "fallback": "cached deterministic alt text",
213
+ },
214
+ "speech": {
215
+ "available": kokoro_available and soundfile_available,
216
+ "status": "online" if kokoro_available and soundfile_available else "fallback-ready",
217
+ "model": "hexgrad/Kokoro-82M",
218
+ "kokoro_installed": kokoro_available,
219
+ "soundfile_installed": soundfile_available,
220
+ "fallback": "browser speech plus transcript",
221
+ },
222
+ "image_generation": {
223
+ "available": False,
224
+ "status": "placeholder-ready",
225
+ "model": "black-forest-labs/FLUX.2-klein-4B",
226
+ "fallback": "bundled generated article assets",
227
+ },
228
+ "elapsed_ms": _elapsed_ms(start),
229
+ }
230
+
231
+
232
  def _compact_text(text: str, limit: int = 220) -> str:
233
  normalized = " ".join(text.split())
234
  if len(normalized) <= limit:
 
440
  return _json({"ok": True, "items": AWARD_EVIDENCE})
441
 
442
 
443
+ @app.get("/api/runtime-status")
444
+ async def runtime_status() -> JSONResponse:
445
+ return _json(_runtime_status_core())
446
+
447
+
448
  @app.post("/api/reader-brain")
449
  async def reader_brain_endpoint(payload: ReaderBrainRequest) -> JSONResponse:
450
  return _json(reader_brain_core(payload.node_type, payload.text, payload.position, payload.mode))
scripts/verify.py CHANGED
@@ -84,6 +84,7 @@ def verify_routes() -> None:
84
  assert_true("readerToggle" in home.text, "Home route should include reader toggle")
85
  assert_true("summaryButton" in home.text, "Home route should include summary control")
86
  assert_true("imageStatus" in home.text, "Home route should include image status")
 
87
  assert_true("voiceStatus" in home.text, "Home route should include voice status")
88
  assert_true("latencyStatus" in home.text, "Home route should include latency status")
89
  assert_true("voiceControl" in home.text, "Home route should include voice control")
@@ -134,6 +135,17 @@ def verify_routes() -> None:
134
  "Award evidence route should cover the targeted bonuses",
135
  )
136
 
 
 
 
 
 
 
 
 
 
 
 
137
  image_descriptions = client.get("/api/image-descriptions")
138
  assert_true(image_descriptions.status_code == 200, "Image descriptions route should return 200")
139
  image_payload = image_descriptions.json()
 
84
  assert_true("readerToggle" in home.text, "Home route should include reader toggle")
85
  assert_true("summaryButton" in home.text, "Home route should include summary control")
86
  assert_true("imageStatus" in home.text, "Home route should include image status")
87
+ assert_true("readinessStatus" in home.text, "Home route should include readiness status")
88
  assert_true("voiceStatus" in home.text, "Home route should include voice status")
89
  assert_true("latencyStatus" in home.text, "Home route should include latency status")
90
  assert_true("voiceControl" in home.text, "Home route should include voice control")
 
135
  "Award evidence route should cover the targeted bonuses",
136
  )
137
 
138
+ runtime = client.get("/api/runtime-status")
139
+ assert_true(runtime.status_code == 200, "Runtime status route should return 200")
140
+ runtime_payload = runtime.json()
141
+ assert_true(runtime_payload["ok"], "Runtime status payload should be ok")
142
+ assert_true("reader_brain" in runtime_payload, "Runtime status should include reader brain status")
143
+ assert_true("speech" in runtime_payload, "Runtime status should include speech status")
144
+ assert_true(
145
+ runtime_payload["reader_brain"]["status"] in {"online", "fallback-ready"},
146
+ "Reader brain status should be online or fallback-ready",
147
+ )
148
+
149
  image_descriptions = client.get("/api/image-descriptions")
150
  assert_true(image_descriptions.status_code == 200, "Image descriptions route should return 200")
151
  image_payload = image_descriptions.json()
static/app.js CHANGED
@@ -4,6 +4,7 @@ const modeStatus = document.querySelector("#modeStatus");
4
  const currentStatus = document.querySelector("#currentStatus");
5
  const runtimeStatus = document.querySelector("#runtimeStatus");
6
  const modelStatus = document.querySelector("#modelStatus");
 
7
  const imageStatus = document.querySelector("#imageStatus");
8
  const voiceStatus = document.querySelector("#voiceStatus");
9
  const latencyStatus = document.querySelector("#latencyStatus");
@@ -151,6 +152,19 @@ async function loadAwardEvidence() {
151
  }
152
  }
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  async function loadImageDescriptions() {
155
  imageStatus.textContent = "Describing";
156
  try {
@@ -432,6 +446,7 @@ controls.play.addEventListener("click", () => {
432
 
433
  loadManifest();
434
  loadAwardEvidence();
 
435
  loadImageDescriptions();
436
  updateSpeedValue();
437
 
 
4
  const currentStatus = document.querySelector("#currentStatus");
5
  const runtimeStatus = document.querySelector("#runtimeStatus");
6
  const modelStatus = document.querySelector("#modelStatus");
7
+ const readinessStatus = document.querySelector("#readinessStatus");
8
  const imageStatus = document.querySelector("#imageStatus");
9
  const voiceStatus = document.querySelector("#voiceStatus");
10
  const latencyStatus = document.querySelector("#latencyStatus");
 
152
  }
153
  }
154
 
155
+ async function loadRuntimeStatus() {
156
+ try {
157
+ const payload = await postJson("/api/runtime-status");
158
+ const brain = payload.reader_brain;
159
+ const speech = payload.speech;
160
+ const brainLabel = brain.available ? "llama.cpp online" : "llama.cpp fallback";
161
+ const speechLabel = speech.available ? "Kokoro online" : "voice fallback";
162
+ readinessStatus.textContent = `${brainLabel}, ${speechLabel}`;
163
+ } catch {
164
+ readinessStatus.textContent = "Fallback ready";
165
+ }
166
+ }
167
+
168
  async function loadImageDescriptions() {
169
  imageStatus.textContent = "Describing";
170
  try {
 
446
 
447
  loadManifest();
448
  loadAwardEvidence();
449
+ loadRuntimeStatus();
450
  loadImageDescriptions();
451
  updateSpeedValue();
452
 
static/index.html CHANGED
@@ -96,6 +96,10 @@
96
  <dt>Model Stack</dt>
97
  <dd id="modelStatus">Loading</dd>
98
  </div>
 
 
 
 
99
  <div>
100
  <dt>Image Alt</dt>
101
  <dd id="imageStatus">Waiting</dd>
 
96
  <dt>Model Stack</dt>
97
  <dd id="modelStatus">Loading</dd>
98
  </div>
99
+ <div>
100
+ <dt>Readiness</dt>
101
+ <dd id="readinessStatus">Checking</dd>
102
+ </div>
103
  <div>
104
  <dt>Image Alt</dt>
105
  <dd id="imageStatus">Waiting</dd>