cvpfus Codex commited on
Commit ·
921d7f6
1
Parent(s): 38c87e2
Report runtime readiness in demo
Browse filesCo-authored-by: Codex <codex@openai.com>
- FIELD_NOTES.md +2 -0
- README.md +2 -0
- app.py +64 -0
- scripts/verify.py +12 -0
- static/app.js +15 -0
- 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>
|