cvpfus Codex commited on
Commit ·
dfd3402
1
Parent(s): fdde5de
Simplify sidebar with model stack
Browse filesCo-authored-by: Codex <codex@openai.com>
- FIELD_NOTES.md +8 -8
- README.md +10 -10
- SUBMISSION.md +5 -5
- app.py +1 -6
- scripts/verify.py +16 -45
- static/app.css +0 -45
- static/app.js +35 -31
- static/evidence.html +0 -95
- static/evidence.js +0 -341
- static/index.html +5 -6
FIELD_NOTES.md
CHANGED
|
@@ -24,17 +24,17 @@ Tiny Narrator is a small-model accessibility article reader. It is not a generic
|
|
| 24 |
| Speech | `hexgrad/Kokoro-82M` | 82M | Fast screen-reader voice with a tiny footprint. |
|
| 25 |
| Image generation | `black-forest-labs/FLUX.2-klein-4B` | 4B | Generates the article illustrations while staying Tiny Titan-safe. |
|
| 26 |
|
| 27 |
-
The app exposes the same budget through `/api/model-budget`, including numeric `params_billion` values, per-model `within_limit` values, and a boolean `all_models_within_limit` check. The
|
| 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
|
| 30 |
|
| 31 |
-
The judge runbook lives at `/api/demo-script`
|
| 32 |
|
| 33 |
Submission readiness lives at `/api/submission-readiness`. It aggregates the demo-critical checks into one payload: model budget, award evidence, custom frontend assets, runtime setup, runtime status, accessibility audit, image receipts, executable demo API checks, and command base URL checks. POST checks must include sample bodies before readiness passes.
|
| 34 |
|
| 35 |
-
The
|
| 36 |
|
| 37 |
-
Image provenance lives in `/api/image-descriptions`. Each article illustration carries the planned FLUX.2 klein model id, prompt, seed, asset URL, and fallback status
|
| 38 |
|
| 39 |
Accessibility evidence lives at `/api/accessibility-audit`. It records the reader-mode choices that matter most for this prototype: semantic reading order, keyboard navigation, reader cursor state, shortcut safety, live narration, image alt text, transcript review, user-controlled playback, and fallback resilience.
|
| 40 |
|
|
@@ -81,8 +81,8 @@ Reader navigation interrupts active playback before requesting the next narratio
|
|
| 81 |
|
| 82 |
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.
|
| 83 |
|
| 84 |
-
The app exposes `/api/award-evidence`
|
| 85 |
|
| 86 |
-
The app also
|
| 87 |
|
| 88 |
-
`/api/runtime-status` keeps the demo honest: if llama.cpp or Kokoro is unavailable, the
|
|
|
|
| 24 |
| Speech | `hexgrad/Kokoro-82M` | 82M | Fast screen-reader voice with a tiny footprint. |
|
| 25 |
| Image generation | `black-forest-labs/FLUX.2-klein-4B` | 4B | Generates the article illustrations while staying Tiny Titan-safe. |
|
| 26 |
|
| 27 |
+
The app exposes the same budget through `/api/model-budget`, including numeric `params_billion` values, per-model `within_limit` values, and a boolean `all_models_within_limit` check. The sidebar model stack renders each model id, runtime, parameter count, and Tiny Titan pass state so the 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 so the demo can distinguish real model wiring from deterministic safety nets.
|
| 30 |
|
| 31 |
+
The judge runbook lives at `/api/demo-script`. It keeps the live presentation repeatable by pairing visible actions with API checks for health, model budget, runtime setup, runtime status, image descriptions, reader narration, and speech. Each API check includes copyable curl and PowerShell-friendly `curl.exe` commands for quick reproduction. Deployed demos can set `PUBLIC_BASE_URL` so those commands point at the public Space instead of localhost.
|
| 32 |
|
| 33 |
Submission readiness lives at `/api/submission-readiness`. It aggregates the demo-critical checks into one payload: model budget, award evidence, custom frontend assets, runtime setup, runtime status, accessibility audit, image receipts, executable demo API checks, and command base URL checks. POST checks must include sample bodies before readiness passes.
|
| 34 |
|
| 35 |
+
The judge evidence bundle lives at `/api/evidence-bundle`. That endpoint returns the core receipts, including schema version, UTC generation time, `PUBLIC_BASE_URL`, runtime status, and readiness, as formatted JSON.
|
| 36 |
|
| 37 |
+
Image provenance lives in `/api/image-descriptions`. Each article illustration carries the planned FLUX.2 klein model id, prompt, seed, asset URL, and fallback status so the generated-image claim is inspectable.
|
| 38 |
|
| 39 |
Accessibility evidence lives at `/api/accessibility-audit`. It records the reader-mode choices that matter most for this prototype: semantic reading order, keyboard navigation, reader cursor state, shortcut safety, live narration, image alt text, transcript review, user-controlled playback, and fallback resilience.
|
| 40 |
|
|
|
|
| 81 |
|
| 82 |
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.
|
| 83 |
|
| 84 |
+
The app exposes `/api/award-evidence`, so the live demo can point directly to the hackathon bonus targets it is designed to satisfy.
|
| 85 |
|
| 86 |
+
The app also exposes `/api/submission-readiness`, so the Field Notes story has a single rollup of which submission claims are currently backed by app evidence.
|
| 87 |
|
| 88 |
+
`/api/runtime-status` keeps the demo honest: if llama.cpp or Kokoro is unavailable, the API labels the fallback state instead of silently pretending that every model path is online.
|
README.md
CHANGED
|
@@ -52,7 +52,7 @@ Start the app:
|
|
| 52 |
python app.py
|
| 53 |
```
|
| 54 |
|
| 55 |
-
Open the local URL printed by Gradio. The article page calls `/api/reader-brain`, `/api/image-descriptions`, `/api/describe-image`,
|
| 56 |
|
| 57 |
Useful environment variables:
|
| 58 |
|
|
@@ -76,7 +76,7 @@ The verifier checks syntax, static assets, Space metadata consistency, determini
|
|
| 76 |
|
| 77 |
`/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.
|
| 78 |
|
| 79 |
-
`/api/demo-script` exposes a compact judge runbook with the visible actions, API checks, sample bodies, curl commands, and PowerShell-friendly `curl.exe` commands that prove the submission claims. The commands use `PUBLIC_BASE_URL`
|
| 80 |
|
| 81 |
`/api/accessibility-audit` exposes structured evidence for semantic reading order, keyboard navigation, reader cursor state, shortcut safety, live narration, image alt text, transcript review, user-controlled playback, and fallback resilience.
|
| 82 |
|
|
@@ -86,7 +86,7 @@ The verifier checks syntax, static assets, Space metadata consistency, determini
|
|
| 86 |
|
| 87 |
The readiness rollup only passes the demo API check when POST entries include executable sample bodies.
|
| 88 |
|
| 89 |
-
`/api/evidence-bundle` returns a
|
| 90 |
|
| 91 |
## Screen Reader Mode
|
| 92 |
|
|
@@ -103,7 +103,7 @@ The frontend builds a reading queue from semantic article nodes. When screen-rea
|
|
| 103 |
|
| 104 |
Each readable node is sent to the reader brain for concise narration, then Kokoro generates speech. If a model is unavailable, the app uses deterministic fallbacks so the demo remains navigable.
|
| 105 |
|
| 106 |
-
The session panel stays reader-first
|
| 107 |
|
| 108 |
When screen-reader mode turns on, it selects the focused or most visible article item, assigns stable reader-node ids, and marks the active item with `aria-current`. Clicking a readable article item while the mode is on reads that item directly.
|
| 109 |
|
|
@@ -115,7 +115,7 @@ The session panel keeps a transcript of recent narration with reader position, r
|
|
| 115 |
|
| 116 |
Images start with meaningful fallback `alt` text in the HTML. Image descriptions are then preloaded into a local cache and written into the page's real `img alt` attributes. When the planned MiniCPM-V runtime is unavailable, deterministic alt-text fallbacks keep the screen-reader path usable.
|
| 117 |
|
| 118 |
-
|
| 119 |
|
| 120 |
Kokoro remains the planned tiny-model TTS path. During local demos, if the server-side Kokoro call falls back, the browser speech engine can read the same transcript so screen-reader mode still produces audible feedback.
|
| 121 |
|
|
@@ -129,12 +129,12 @@ Navigation commands interrupt current speech before starting the next request, m
|
|
| 129 |
|
| 130 |
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.
|
| 131 |
|
| 132 |
-
The
|
| 133 |
|
| 134 |
-
|
| 135 |
|
| 136 |
-
|
| 137 |
|
| 138 |
-
|
| 139 |
|
| 140 |
-
`/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.
|
|
|
|
| 52 |
python app.py
|
| 53 |
```
|
| 54 |
|
| 55 |
+
Open the local URL printed by Gradio. The article page calls `/api/reader-brain`, `/api/image-descriptions`, `/api/describe-image`, `/api/speak`, and `/api/model-budget`; the remaining evidence endpoints stay available for docs and submission checks.
|
| 56 |
|
| 57 |
Useful environment variables:
|
| 58 |
|
|
|
|
| 76 |
|
| 77 |
`/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.
|
| 78 |
|
| 79 |
+
`/api/demo-script` exposes a compact judge runbook with the visible actions, API checks, sample bodies, curl commands, and PowerShell-friendly `curl.exe` commands that prove the submission claims. The commands use `PUBLIC_BASE_URL` so they can be copied from the API response or submission notes.
|
| 80 |
|
| 81 |
`/api/accessibility-audit` exposes structured evidence for semantic reading order, keyboard navigation, reader cursor state, shortcut safety, live narration, image alt text, transcript review, user-controlled playback, and fallback resilience.
|
| 82 |
|
|
|
|
| 86 |
|
| 87 |
The readiness rollup only passes the demo API check when POST entries include executable sample bodies.
|
| 88 |
|
| 89 |
+
`/api/evidence-bundle` returns a JSON bundle of the main judging receipts, including schema version, UTC generation time, `PUBLIC_BASE_URL`, runtime status, and the submission readiness rollup.
|
| 90 |
|
| 91 |
## Screen Reader Mode
|
| 92 |
|
|
|
|
| 103 |
|
| 104 |
Each readable node is sent to the reader brain for concise narration, then Kokoro generates speech. If a model is unavailable, the app uses deterministic fallbacks so the demo remains navigable.
|
| 105 |
|
| 106 |
+
The session panel stays reader-first: it shows current reader state, live narration, transcript controls, latency, and the tiny-model stack. The semantic reader queue remains internal to playback and keyboard navigation.
|
| 107 |
|
| 108 |
When screen-reader mode turns on, it selects the focused or most visible article item, assigns stable reader-node ids, and marks the active item with `aria-current`. Clicking a readable article item while the mode is on reads that item directly.
|
| 109 |
|
|
|
|
| 115 |
|
| 116 |
Images start with meaningful fallback `alt` text in the HTML. Image descriptions are then preloaded into a local cache and written into the page's real `img alt` attributes. When the planned MiniCPM-V runtime is unavailable, deterministic alt-text fallbacks keep the screen-reader path usable.
|
| 117 |
|
| 118 |
+
`/api/image-descriptions` exposes image receipts so judges can inspect the prompt, seed, planned <=4B image model, and fallback status behind each bundled article illustration without lengthening the reader sidebar.
|
| 119 |
|
| 120 |
Kokoro remains the planned tiny-model TTS path. During local demos, if the server-side Kokoro call falls back, the browser speech engine can read the same transcript so screen-reader mode still produces audible feedback.
|
| 121 |
|
|
|
|
| 129 |
|
| 130 |
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.
|
| 131 |
|
| 132 |
+
The sidebar model stack reads `/api/model-budget` so judges can see each role's model id, runtime, parameter count, and Tiny Titan pass status in the live app.
|
| 133 |
|
| 134 |
+
`/api/submission-readiness` gives judges one compact rollup of the claims the live app can prove.
|
| 135 |
|
| 136 |
+
`/api/evidence-bundle` returns the core judge receipts as formatted JSON for quick review or submission notes.
|
| 137 |
|
| 138 |
+
`/api/runtime-setup` summarizes each model path's runtime, setup command, and fallback, keeping the live demo honest about what is online and what is deterministic.
|
| 139 |
|
| 140 |
+
`/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.
|
SUBMISSION.md
CHANGED
|
@@ -6,7 +6,7 @@ Tiny Narrator turns an article into a guided screen-reader experience using smal
|
|
| 6 |
|
| 7 |
## Short description
|
| 8 |
|
| 9 |
-
Tiny Narrator is a custom Gradio Server app that looks like an article/blog reader until screen-reader mode is switched on. In reader mode, the app builds
|
| 10 |
|
| 11 |
The prototype is designed for a live hackathon demo: every model-facing path has a deterministic fallback, runtime readiness is labeled in the UI, and the repo exposes machine-readable evidence for model size, setup, demo flow, and accessibility behavior.
|
| 12 |
|
|
@@ -32,15 +32,15 @@ The prototype is designed for a live hackathon demo: every model-facing path has
|
|
| 32 |
2. Turn on screen-reader mode and press `Space` or `Next` to narrate the first semantic node.
|
| 33 |
3. Use `Heading`, `Image`, and `Summary` to navigate by article meaning instead of by raw page order.
|
| 34 |
4. Show the reader-first session panel: current item, live narration, reader queue, transcript, and latency.
|
| 35 |
-
5.
|
| 36 |
-
6. Mention that `/api/demo-script` exposes the
|
| 37 |
|
| 38 |
## Evidence endpoints
|
| 39 |
|
| 40 |
- `/api/health`: app identity, custom frontend marker, llama.cpp base URL, and model manifest.
|
| 41 |
- `/api/model-budget`: Tiny Titan parameter proof with numeric `params_billion` and per-model pass values.
|
| 42 |
- `/api/runtime-setup`: app command, copyable model runtime commands rendered in the UI, environment values, and fallback paths.
|
| 43 |
-
- `/api/runtime-status`: online or fallback-ready status for each model path
|
| 44 |
- `/api/accessibility-audit`: semantic queue, keyboard navigation, reader cursor state, shortcut safety, live narration, alt text, transcript, user control, and fallback evidence.
|
| 45 |
- `/api/demo-script`: repeatable judge runbook and API checks.
|
| 46 |
- `/api/image-descriptions`: generated article image descriptions plus prompt, seed, model, asset URL, and fallback status receipts.
|
|
@@ -55,7 +55,7 @@ The image receipts keep the generated-asset claim inspectable: each article illu
|
|
| 55 |
|
| 56 |
The submission-readiness panel and API give judges a compact checklist for the whole build, so the live demo can move from individual receipts to an overall readiness view.
|
| 57 |
|
| 58 |
-
|
| 59 |
|
| 60 |
## Reliability notes
|
| 61 |
|
|
|
|
| 6 |
|
| 7 |
## Short description
|
| 8 |
|
| 9 |
+
Tiny Narrator is a custom Gradio Server app that looks like an article/blog reader until screen-reader mode is switched on. In reader mode, the app builds an internal semantic reading queue, narrates headings and paragraphs, describes generated images, summarizes the current section, speaks with Kokoro, and keeps a visible transcript of the spoken path with reader position, runtime, and latency.
|
| 10 |
|
| 11 |
The prototype is designed for a live hackathon demo: every model-facing path has a deterministic fallback, runtime readiness is labeled in the UI, and the repo exposes machine-readable evidence for model size, setup, demo flow, and accessibility behavior.
|
| 12 |
|
|
|
|
| 32 |
2. Turn on screen-reader mode and press `Space` or `Next` to narrate the first semantic node.
|
| 33 |
3. Use `Heading`, `Image`, and `Summary` to navigate by article meaning instead of by raw page order.
|
| 34 |
4. Show the reader-first session panel: current item, live narration, reader queue, transcript, and latency.
|
| 35 |
+
5. Point to the model stack panel for model id, runtime, parameter count, and Tiny Titan pass status.
|
| 36 |
+
6. Mention that `/api/demo-script` exposes the judge runbook and API evidence checks as structured data.
|
| 37 |
|
| 38 |
## Evidence endpoints
|
| 39 |
|
| 40 |
- `/api/health`: app identity, custom frontend marker, llama.cpp base URL, and model manifest.
|
| 41 |
- `/api/model-budget`: Tiny Titan parameter proof with numeric `params_billion` and per-model pass values.
|
| 42 |
- `/api/runtime-setup`: app command, copyable model runtime commands rendered in the UI, environment values, and fallback paths.
|
| 43 |
+
- `/api/runtime-status`: online or fallback-ready status for each model path.
|
| 44 |
- `/api/accessibility-audit`: semantic queue, keyboard navigation, reader cursor state, shortcut safety, live narration, alt text, transcript, user control, and fallback evidence.
|
| 45 |
- `/api/demo-script`: repeatable judge runbook and API checks.
|
| 46 |
- `/api/image-descriptions`: generated article image descriptions plus prompt, seed, model, asset URL, and fallback status receipts.
|
|
|
|
| 55 |
|
| 56 |
The submission-readiness panel and API give judges a compact checklist for the whole build, so the live demo can move from individual receipts to an overall readiness view.
|
| 57 |
|
| 58 |
+
`/api/evidence-bundle` returns formatted JSON for quick judging notes. Transcript copy actions label clipboard-unavailable fallback states in the visible live narration region.
|
| 59 |
|
| 60 |
## Reliability notes
|
| 61 |
|
app.py
CHANGED
|
@@ -432,7 +432,7 @@ def accessibility_audit_core() -> dict[str, Any]:
|
|
| 432 |
"id": "semantic_queue",
|
| 433 |
"label": "Semantic reading queue",
|
| 434 |
"status": "pass",
|
| 435 |
-
"evidence": "Readable article nodes declare data-reader-type values and the
|
| 436 |
},
|
| 437 |
{
|
| 438 |
"id": "keyboard_navigation",
|
|
@@ -816,11 +816,6 @@ async def home() -> str:
|
|
| 816 |
return (STATIC_DIR / "index.html").read_text(encoding="utf-8")
|
| 817 |
|
| 818 |
|
| 819 |
-
@app.get("/evidence", response_class=HTMLResponse)
|
| 820 |
-
async def evidence_page() -> str:
|
| 821 |
-
return (STATIC_DIR / "evidence.html").read_text(encoding="utf-8")
|
| 822 |
-
|
| 823 |
-
|
| 824 |
@app.get("/api/health")
|
| 825 |
async def health() -> JSONResponse:
|
| 826 |
return _json(
|
|
|
|
| 432 |
"id": "semantic_queue",
|
| 433 |
"label": "Semantic reading queue",
|
| 434 |
"status": "pass",
|
| 435 |
+
"evidence": "Readable article nodes declare data-reader-type values and the reader controls navigate the ordered semantic node list.",
|
| 436 |
},
|
| 437 |
{
|
| 438 |
"id": "keyboard_navigation",
|
|
|
|
| 816 |
return (STATIC_DIR / "index.html").read_text(encoding="utf-8")
|
| 817 |
|
| 818 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 819 |
@app.get("/api/health")
|
| 820 |
async def health() -> JSONResponse:
|
| 821 |
return _json(
|
scripts/verify.py
CHANGED
|
@@ -23,10 +23,8 @@ def verify_static_assets() -> None:
|
|
| 23 |
ROOT / "LICENSE",
|
| 24 |
ROOT / "SUBMISSION.md",
|
| 25 |
ROOT / "static" / "index.html",
|
| 26 |
-
ROOT / "static" / "evidence.html",
|
| 27 |
ROOT / "static" / "app.css",
|
| 28 |
ROOT / "static" / "app.js",
|
| 29 |
-
ROOT / "static" / "evidence.js",
|
| 30 |
ROOT / "static" / "generated" / "desk-reader.svg",
|
| 31 |
ROOT / "static" / "generated" / "model-map.svg",
|
| 32 |
ROOT / "static" / "generated" / "field-notes.svg",
|
|
@@ -35,9 +33,7 @@ def verify_static_assets() -> None:
|
|
| 35 |
assert_true(path.exists(), f"Missing required asset: {path}")
|
| 36 |
|
| 37 |
app_js = (ROOT / "static" / "app.js").read_text(encoding="utf-8")
|
| 38 |
-
evidence_js = (ROOT / "static" / "evidence.js").read_text(encoding="utf-8")
|
| 39 |
index_html = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
|
| 40 |
-
evidence_html = (ROOT / "static" / "evidence.html").read_text(encoding="utf-8")
|
| 41 |
assert_true('data-reader-type="heading"' in index_html, "Article should mark heading reader nodes")
|
| 42 |
assert_true('data-reader-type="image"' in index_html, "Article should mark image reader nodes")
|
| 43 |
assert_true('alt=""' not in index_html, "Article images should not start with empty alt text")
|
|
@@ -49,10 +45,12 @@ def verify_static_assets() -> None:
|
|
| 49 |
for shortcut in ['aria-keyshortcuts="Space"', 'aria-keyshortcuts="N"', 'aria-keyshortcuts="R"', 'aria-keyshortcuts="S"']:
|
| 50 |
assert_true(shortcut in index_html, f"Reader controls should expose {shortcut}")
|
| 51 |
assert_true("transcriptLog" in index_html, "Article should expose a visible transcript log")
|
| 52 |
-
assert_true("readerQueueList" in index_html, "Article should
|
| 53 |
assert_true("repeatButton" in index_html, "Reader controls should expose a visible repeat command")
|
| 54 |
assert_true("stopButton" in index_html, "Reader controls should expose a visible stop command")
|
| 55 |
-
|
|
|
|
|
|
|
| 56 |
"demoScriptList",
|
| 57 |
"demoApiCheckList",
|
| 58 |
"imageReceiptList",
|
|
@@ -62,23 +60,14 @@ def verify_static_assets() -> None:
|
|
| 62 |
"modelBudgetList",
|
| 63 |
"runtimeSetupList",
|
| 64 |
]:
|
| 65 |
-
assert_true(
|
| 66 |
-
assert_true(moved_id in evidence_html, f"Evidence page should include moved evidence panel: {moved_id}")
|
| 67 |
assert_true("loadDemoScript" not in app_js, "Article frontend should not render the structured demo script")
|
| 68 |
-
assert_true("
|
| 69 |
-
assert_true("
|
| 70 |
-
assert_true("
|
| 71 |
-
assert_true("
|
| 72 |
-
assert_true("
|
| 73 |
-
assert_true("payload.api_checks" in evidence_js, "Evidence frontend should render structured demo API checks")
|
| 74 |
-
assert_true("demo-api-command" in evidence_js, "Evidence frontend should render copyable demo commands")
|
| 75 |
-
assert_true("demo-command-copy" in evidence_js, "Evidence frontend should expose copy buttons for demo commands")
|
| 76 |
-
assert_true("item.powershell" in evidence_js, "Evidence frontend should render PowerShell-friendly demo commands")
|
| 77 |
-
assert_true("Tiny Titan pass" in evidence_js, "Evidence frontend should render per-model Tiny Titan pass labels")
|
| 78 |
-
assert_true("/api/evidence-bundle" in evidence_js, "Evidence frontend should fetch the evidence bundle for copying")
|
| 79 |
-
assert_true("/evidence" in index_html, "Article page should link to the dedicated evidence page")
|
| 80 |
assert_true("copyTextToClipboard" in app_js, "Frontend copy actions should share clipboard fallback handling")
|
| 81 |
-
assert_true("copyTextToClipboard" in evidence_js, "Evidence copy actions should share clipboard fallback handling")
|
| 82 |
assert_true(
|
| 83 |
"Transcript is visible, but clipboard access is unavailable." in app_js,
|
| 84 |
"Transcript copy should report unavailable clipboard access",
|
|
@@ -99,10 +88,8 @@ def verify_static_assets() -> None:
|
|
| 99 |
assert_true("controls.repeat.click()" in app_js, "R should route through the visible repeat command")
|
| 100 |
assert_true("controls.stop.click()" in app_js, "Escape should route through the visible stop command")
|
| 101 |
assert_true("reader-node-" in app_js, "Reader nodes should receive stable ids for control context")
|
| 102 |
-
assert_true("renderReaderQueue" in app_js, "Frontend should render
|
| 103 |
-
assert_true("
|
| 104 |
-
assert_true("reader-queue-play" in app_js, "Reader queue should expose click-to-read controls")
|
| 105 |
-
assert_true("readerQueueList.addEventListener" in app_js, "Reader queue should handle click-to-read actions")
|
| 106 |
assert_true("narrate(node.index)" in app_js, "Reader mode should support click-to-read article items")
|
| 107 |
|
| 108 |
submission = (ROOT / "SUBMISSION.md").read_text(encoding="utf-8")
|
|
@@ -255,28 +242,12 @@ def verify_routes() -> None:
|
|
| 255 |
assert_true("speedValue" in home.text, "Home route should include speed value output")
|
| 256 |
assert_true("autoAdvanceControl" in home.text, "Home route should include auto-advance control")
|
| 257 |
assert_true("transcriptLog" in home.text, "Home route should include transcript log")
|
| 258 |
-
assert_true("readerQueueList" in home.text, "Home route should include reader queue list")
|
| 259 |
-
assert_true("
|
|
|
|
|
|
|
| 260 |
assert_true("copyEvidenceButton" not in home.text, "Home route should keep judge evidence off the reader sidebar")
|
| 261 |
|
| 262 |
-
evidence_page = client.get("/evidence")
|
| 263 |
-
assert_true(evidence_page.status_code == 200, "Evidence route should return 200")
|
| 264 |
-
assert_true("Judging receipts for Tiny Narrator" in evidence_page.text, "Evidence route should include evidence title")
|
| 265 |
-
assert_true("demoScriptStatus" in evidence_page.text, "Evidence route should include demo script status")
|
| 266 |
-
assert_true("demoScriptList" in evidence_page.text, "Evidence route should include demo script list")
|
| 267 |
-
assert_true("demoApiCheckList" in evidence_page.text, "Evidence route should include demo API check list")
|
| 268 |
-
assert_true("awardEvidenceList" in evidence_page.text, "Evidence route should include award evidence list")
|
| 269 |
-
assert_true("submissionReadinessStatus" in evidence_page.text, "Evidence route should include submission readiness status")
|
| 270 |
-
assert_true("submissionReadinessList" in evidence_page.text, "Evidence route should include submission readiness list")
|
| 271 |
-
assert_true("copyEvidenceButton" in evidence_page.text, "Evidence route should include copy evidence button")
|
| 272 |
-
assert_true("budgetStatus" in evidence_page.text, "Evidence route should include model budget status")
|
| 273 |
-
assert_true("modelBudgetList" in evidence_page.text, "Evidence route should include model budget list")
|
| 274 |
-
assert_true("runtimeStatusList" in evidence_page.text, "Evidence route should include runtime status list")
|
| 275 |
-
assert_true("runtimeSetupStatus" in evidence_page.text, "Evidence route should include runtime setup status")
|
| 276 |
-
assert_true("runtimeSetupList" in evidence_page.text, "Evidence route should include runtime setup list")
|
| 277 |
-
assert_true("imageReceiptStatus" in evidence_page.text, "Evidence route should include image receipt status")
|
| 278 |
-
assert_true("imageReceiptList" in evidence_page.text, "Evidence route should include image receipt list")
|
| 279 |
-
|
| 280 |
health = client.get("/api/health")
|
| 281 |
assert_true(health.status_code == 200, "Health route should return 200")
|
| 282 |
payload = health.json()
|
|
|
|
| 23 |
ROOT / "LICENSE",
|
| 24 |
ROOT / "SUBMISSION.md",
|
| 25 |
ROOT / "static" / "index.html",
|
|
|
|
| 26 |
ROOT / "static" / "app.css",
|
| 27 |
ROOT / "static" / "app.js",
|
|
|
|
| 28 |
ROOT / "static" / "generated" / "desk-reader.svg",
|
| 29 |
ROOT / "static" / "generated" / "model-map.svg",
|
| 30 |
ROOT / "static" / "generated" / "field-notes.svg",
|
|
|
|
| 33 |
assert_true(path.exists(), f"Missing required asset: {path}")
|
| 34 |
|
| 35 |
app_js = (ROOT / "static" / "app.js").read_text(encoding="utf-8")
|
|
|
|
| 36 |
index_html = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
|
|
|
|
| 37 |
assert_true('data-reader-type="heading"' in index_html, "Article should mark heading reader nodes")
|
| 38 |
assert_true('data-reader-type="image"' in index_html, "Article should mark image reader nodes")
|
| 39 |
assert_true('alt=""' not in index_html, "Article images should not start with empty alt text")
|
|
|
|
| 45 |
for shortcut in ['aria-keyshortcuts="Space"', 'aria-keyshortcuts="N"', 'aria-keyshortcuts="R"', 'aria-keyshortcuts="S"']:
|
| 46 |
assert_true(shortcut in index_html, f"Reader controls should expose {shortcut}")
|
| 47 |
assert_true("transcriptLog" in index_html, "Article should expose a visible transcript log")
|
| 48 |
+
assert_true("readerQueueList" not in index_html, "Article sidebar should not include a visible reader queue")
|
| 49 |
assert_true("repeatButton" in index_html, "Reader controls should expose a visible repeat command")
|
| 50 |
assert_true("stopButton" in index_html, "Reader controls should expose a visible stop command")
|
| 51 |
+
assert_true("modelStackList" in index_html, "Article sidebar should expose the model stack panel")
|
| 52 |
+
assert_true("modelBudgetStatus" in index_html, "Article sidebar should expose model stack status")
|
| 53 |
+
for removed_id in [
|
| 54 |
"demoScriptList",
|
| 55 |
"demoApiCheckList",
|
| 56 |
"imageReceiptList",
|
|
|
|
| 60 |
"modelBudgetList",
|
| 61 |
"runtimeSetupList",
|
| 62 |
]:
|
| 63 |
+
assert_true(removed_id not in index_html, f"Article sidebar should not include removed evidence panel: {removed_id}")
|
|
|
|
| 64 |
assert_true("loadDemoScript" not in app_js, "Article frontend should not render the structured demo script")
|
| 65 |
+
assert_true("loadModelBudget" in app_js, "Article frontend should render the model stack panel")
|
| 66 |
+
assert_true("/api/model-budget" in app_js, "Article frontend should fetch model budget data")
|
| 67 |
+
assert_true("modelStackList.innerHTML" in app_js, "Article frontend should render model stack items")
|
| 68 |
+
assert_true("Tiny Titan pass" in app_js, "Article frontend should render per-model Tiny Titan pass labels")
|
| 69 |
+
assert_true("/evidence" not in index_html, "Article page should not link to a removed evidence page")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
assert_true("copyTextToClipboard" in app_js, "Frontend copy actions should share clipboard fallback handling")
|
|
|
|
| 71 |
assert_true(
|
| 72 |
"Transcript is visible, but clipboard access is unavailable." in app_js,
|
| 73 |
"Transcript copy should report unavailable clipboard access",
|
|
|
|
| 88 |
assert_true("controls.repeat.click()" in app_js, "R should route through the visible repeat command")
|
| 89 |
assert_true("controls.stop.click()" in app_js, "Escape should route through the visible stop command")
|
| 90 |
assert_true("reader-node-" in app_js, "Reader nodes should receive stable ids for control context")
|
| 91 |
+
assert_true("renderReaderQueue" not in app_js, "Frontend should not render a visible reader queue")
|
| 92 |
+
assert_true("readerQueueList" not in app_js, "Frontend should not bind a visible reader queue")
|
|
|
|
|
|
|
| 93 |
assert_true("narrate(node.index)" in app_js, "Reader mode should support click-to-read article items")
|
| 94 |
|
| 95 |
submission = (ROOT / "SUBMISSION.md").read_text(encoding="utf-8")
|
|
|
|
| 242 |
assert_true("speedValue" in home.text, "Home route should include speed value output")
|
| 243 |
assert_true("autoAdvanceControl" in home.text, "Home route should include auto-advance control")
|
| 244 |
assert_true("transcriptLog" in home.text, "Home route should include transcript log")
|
| 245 |
+
assert_true("readerQueueList" not in home.text, "Home route should not include reader queue list")
|
| 246 |
+
assert_true("modelBudgetStatus" in home.text, "Home route should include model stack status")
|
| 247 |
+
assert_true("modelStackList" in home.text, "Home route should include model stack list")
|
| 248 |
+
assert_true("/evidence" not in home.text, "Home route should not link to a removed evidence page")
|
| 249 |
assert_true("copyEvidenceButton" not in home.text, "Home route should keep judge evidence off the reader sidebar")
|
| 250 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
health = client.get("/api/health")
|
| 252 |
assert_true(health.status_code == 200, "Health route should return 200")
|
| 253 |
payload = health.json()
|
static/app.css
CHANGED
|
@@ -177,51 +177,6 @@ select:focus-visible {
|
|
| 177 |
|
| 178 |
.article { min-width: 0; }
|
| 179 |
|
| 180 |
-
.evidence-shell {
|
| 181 |
-
display: grid;
|
| 182 |
-
gap: 28px;
|
| 183 |
-
width: min(1040px, calc(100% - 40px));
|
| 184 |
-
margin: 56px auto 120px;
|
| 185 |
-
}
|
| 186 |
-
|
| 187 |
-
.evidence-hero {
|
| 188 |
-
padding-bottom: 12px;
|
| 189 |
-
border-bottom: 2px solid var(--ink);
|
| 190 |
-
}
|
| 191 |
-
|
| 192 |
-
.evidence-hero h1 {
|
| 193 |
-
max-width: 12ch;
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
-
.evidence-section {
|
| 197 |
-
border: 1px solid var(--line);
|
| 198 |
-
border-top: 3px solid var(--ink);
|
| 199 |
-
background: var(--surface);
|
| 200 |
-
padding: 22px 20px;
|
| 201 |
-
}
|
| 202 |
-
|
| 203 |
-
.evidence-section h2 {
|
| 204 |
-
margin: 0;
|
| 205 |
-
padding: 0;
|
| 206 |
-
font-family: var(--mono);
|
| 207 |
-
font-size: 0.72rem;
|
| 208 |
-
font-weight: 700;
|
| 209 |
-
text-transform: uppercase;
|
| 210 |
-
letter-spacing: 0.12em;
|
| 211 |
-
color: var(--ink-soft);
|
| 212 |
-
}
|
| 213 |
-
|
| 214 |
-
.evidence-section h2::before { display: none; }
|
| 215 |
-
|
| 216 |
-
.evidence-link {
|
| 217 |
-
color: var(--ink);
|
| 218 |
-
text-decoration: none;
|
| 219 |
-
}
|
| 220 |
-
|
| 221 |
-
.evidence-link:hover {
|
| 222 |
-
border-color: var(--accent);
|
| 223 |
-
}
|
| 224 |
-
|
| 225 |
/* ─── ARTICLE TYPOGRAPHY ─── */
|
| 226 |
.kicker {
|
| 227 |
display: inline-block;
|
|
|
|
| 177 |
|
| 178 |
.article { min-width: 0; }
|
| 179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
/* ─── ARTICLE TYPOGRAPHY ─── */
|
| 181 |
.kicker {
|
| 182 |
display: inline-block;
|
static/app.js
CHANGED
|
@@ -7,8 +7,6 @@ const imageStatus = document.querySelector("#imageStatus");
|
|
| 7 |
const voiceStatus = document.querySelector("#voiceStatus");
|
| 8 |
const latencyStatus = document.querySelector("#latencyStatus");
|
| 9 |
const liveNarration = document.querySelector("#liveNarration");
|
| 10 |
-
const readerQueueStatus = document.querySelector("#readerQueueStatus");
|
| 11 |
-
const readerQueueList = document.querySelector("#readerQueueList");
|
| 12 |
const audio = document.querySelector("#speechAudio");
|
| 13 |
const voiceControl = document.querySelector("#voiceControl");
|
| 14 |
const speedControl = document.querySelector("#speedControl");
|
|
@@ -17,6 +15,8 @@ const autoAdvanceControl = document.querySelector("#autoAdvanceControl");
|
|
| 17 |
const transcriptLog = document.querySelector("#transcriptLog");
|
| 18 |
const copyTranscriptButton = document.querySelector("#copyTranscriptButton");
|
| 19 |
const clearTranscriptButton = document.querySelector("#clearTranscriptButton");
|
|
|
|
|
|
|
| 20 |
|
| 21 |
const controls = {
|
| 22 |
prev: document.querySelector("#prevButton"),
|
|
@@ -126,26 +126,6 @@ function readerItemStatus(node) {
|
|
| 126 |
return `${readerTypeLabel(node.type)}, item ${node.index + 1} of ${nodes.length}`;
|
| 127 |
}
|
| 128 |
|
| 129 |
-
function readerQueueLabel(node) {
|
| 130 |
-
return `${node.index + 1}. ${readerTypeLabel(node.type)}`;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
function renderReaderQueue() {
|
| 134 |
-
readerQueueStatus.textContent = `${nodes.length} items`;
|
| 135 |
-
readerQueueList.innerHTML = nodes
|
| 136 |
-
.map((node) => `
|
| 137 |
-
<li${node.index === currentIndex ? ' aria-current="true"' : ""}>
|
| 138 |
-
<div class="runtime-row">
|
| 139 |
-
<span>${escapeHtml(readerQueueLabel(node))}</span>
|
| 140 |
-
<span class="runtime-pill">${node.index === currentIndex ? "current" : "queued"}</span>
|
| 141 |
-
</div>
|
| 142 |
-
<p>${escapeHtml(node.text.slice(0, 120))}</p>
|
| 143 |
-
<button class="reader-queue-play" type="button" data-reader-index="${node.index}">Read item</button>
|
| 144 |
-
</li>
|
| 145 |
-
`)
|
| 146 |
-
.join("");
|
| 147 |
-
}
|
| 148 |
-
|
| 149 |
function initialReaderIndex() {
|
| 150 |
if (!nodes.length) {
|
| 151 |
return -1;
|
|
@@ -232,6 +212,38 @@ function roleLabel(role) {
|
|
| 232 |
.join(" ");
|
| 233 |
}
|
| 234 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
async function loadImageDescriptions() {
|
| 236 |
imageStatus.textContent = "Describing";
|
| 237 |
try {
|
|
@@ -281,7 +293,6 @@ function setActive(index) {
|
|
| 281 |
const node = nodes[currentIndex];
|
| 282 |
if (!node) {
|
| 283 |
currentStatus.textContent = "No item selected";
|
| 284 |
-
renderReaderQueue();
|
| 285 |
return;
|
| 286 |
}
|
| 287 |
node.element.classList.add("reader-active");
|
|
@@ -290,7 +301,6 @@ function setActive(index) {
|
|
| 290 |
node.element.focus({ preventScroll: true });
|
| 291 |
node.element.scrollIntoView({ block: "center", behavior: "smooth" });
|
| 292 |
currentStatus.textContent = readerItemStatus(node);
|
| 293 |
-
renderReaderQueue();
|
| 294 |
}
|
| 295 |
|
| 296 |
function haltPlayback({ clearAutoAdvance = true } = {}) {
|
|
@@ -524,12 +534,6 @@ nodes.forEach((node) => {
|
|
| 524 |
narrate(node.index);
|
| 525 |
});
|
| 526 |
});
|
| 527 |
-
readerQueueList.addEventListener("click", (event) => {
|
| 528 |
-
const button = event.target.closest(".reader-queue-play");
|
| 529 |
-
if (!button) return;
|
| 530 |
-
setEnabled(true);
|
| 531 |
-
narrate(Number(button.dataset.readerIndex));
|
| 532 |
-
});
|
| 533 |
voiceControl.addEventListener("change", () => {
|
| 534 |
voiceStatus.textContent = voiceControl.selectedOptions[0]?.textContent || "Custom voice";
|
| 535 |
});
|
|
@@ -582,9 +586,9 @@ controls.play.addEventListener("click", () => {
|
|
| 582 |
});
|
| 583 |
|
| 584 |
loadManifest();
|
|
|
|
| 585 |
loadImageDescriptions();
|
| 586 |
updateSpeedValue();
|
| 587 |
-
renderReaderQueue();
|
| 588 |
|
| 589 |
audio.addEventListener("ended", () => {
|
| 590 |
playing = false;
|
|
|
|
| 7 |
const voiceStatus = document.querySelector("#voiceStatus");
|
| 8 |
const latencyStatus = document.querySelector("#latencyStatus");
|
| 9 |
const liveNarration = document.querySelector("#liveNarration");
|
|
|
|
|
|
|
| 10 |
const audio = document.querySelector("#speechAudio");
|
| 11 |
const voiceControl = document.querySelector("#voiceControl");
|
| 12 |
const speedControl = document.querySelector("#speedControl");
|
|
|
|
| 15 |
const transcriptLog = document.querySelector("#transcriptLog");
|
| 16 |
const copyTranscriptButton = document.querySelector("#copyTranscriptButton");
|
| 17 |
const clearTranscriptButton = document.querySelector("#clearTranscriptButton");
|
| 18 |
+
const modelBudgetStatus = document.querySelector("#modelBudgetStatus");
|
| 19 |
+
const modelStackList = document.querySelector("#modelStackList");
|
| 20 |
|
| 21 |
const controls = {
|
| 22 |
prev: document.querySelector("#prevButton"),
|
|
|
|
| 126 |
return `${readerTypeLabel(node.type)}, item ${node.index + 1} of ${nodes.length}`;
|
| 127 |
}
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
function initialReaderIndex() {
|
| 130 |
if (!nodes.length) {
|
| 131 |
return -1;
|
|
|
|
| 212 |
.join(" ");
|
| 213 |
}
|
| 214 |
|
| 215 |
+
async function loadModelBudget() {
|
| 216 |
+
try {
|
| 217 |
+
const payload = await postJson("/api/model-budget");
|
| 218 |
+
modelBudgetStatus.textContent = payload.all_models_within_limit
|
| 219 |
+
? `All <= ${payload.limit_billion}B`
|
| 220 |
+
: "Review needed";
|
| 221 |
+
modelStackList.innerHTML = payload.models
|
| 222 |
+
.map((model) => `
|
| 223 |
+
<li>
|
| 224 |
+
<div class="budget-row">
|
| 225 |
+
<span>${escapeHtml(roleLabel(model.role))}</span>
|
| 226 |
+
<span class="budget-pill">${escapeHtml(model.params)}</span>
|
| 227 |
+
</div>
|
| 228 |
+
<p>${escapeHtml(model.runtime)} | ${model.within_limit ? "Tiny Titan pass" : "Review parameter budget"}</p>
|
| 229 |
+
<p>${escapeHtml(model.id)}</p>
|
| 230 |
+
</li>
|
| 231 |
+
`)
|
| 232 |
+
.join("");
|
| 233 |
+
} catch {
|
| 234 |
+
modelBudgetStatus.textContent = "Unavailable";
|
| 235 |
+
modelStackList.innerHTML = `
|
| 236 |
+
<li>
|
| 237 |
+
<div class="budget-row">
|
| 238 |
+
<span>Model stack</span>
|
| 239 |
+
<span class="budget-pill">offline</span>
|
| 240 |
+
</div>
|
| 241 |
+
<p>Model budget is unavailable.</p>
|
| 242 |
+
</li>
|
| 243 |
+
`;
|
| 244 |
+
}
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
async function loadImageDescriptions() {
|
| 248 |
imageStatus.textContent = "Describing";
|
| 249 |
try {
|
|
|
|
| 293 |
const node = nodes[currentIndex];
|
| 294 |
if (!node) {
|
| 295 |
currentStatus.textContent = "No item selected";
|
|
|
|
| 296 |
return;
|
| 297 |
}
|
| 298 |
node.element.classList.add("reader-active");
|
|
|
|
| 301 |
node.element.focus({ preventScroll: true });
|
| 302 |
node.element.scrollIntoView({ block: "center", behavior: "smooth" });
|
| 303 |
currentStatus.textContent = readerItemStatus(node);
|
|
|
|
| 304 |
}
|
| 305 |
|
| 306 |
function haltPlayback({ clearAutoAdvance = true } = {}) {
|
|
|
|
| 534 |
narrate(node.index);
|
| 535 |
});
|
| 536 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 537 |
voiceControl.addEventListener("change", () => {
|
| 538 |
voiceStatus.textContent = voiceControl.selectedOptions[0]?.textContent || "Custom voice";
|
| 539 |
});
|
|
|
|
| 586 |
});
|
| 587 |
|
| 588 |
loadManifest();
|
| 589 |
+
loadModelBudget();
|
| 590 |
loadImageDescriptions();
|
| 591 |
updateSpeedValue();
|
|
|
|
| 592 |
|
| 593 |
audio.addEventListener("ended", () => {
|
| 594 |
playing = false;
|
static/evidence.html
DELETED
|
@@ -1,95 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="utf-8" />
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
-
<title>Tiny Narrator Evidence</title>
|
| 7 |
-
<link rel="stylesheet" href="/static/app.css" />
|
| 8 |
-
</head>
|
| 9 |
-
<body>
|
| 10 |
-
<header class="topbar">
|
| 11 |
-
<a class="brand" href="/" aria-label="Tiny Narrator home">
|
| 12 |
-
<span class="brand-mark" aria-hidden="true">TN</span>
|
| 13 |
-
<span>Tiny Narrator</span>
|
| 14 |
-
</a>
|
| 15 |
-
<nav class="topnav" aria-label="Evidence navigation">
|
| 16 |
-
<a href="/">Article</a>
|
| 17 |
-
<a href="#runbook">Runbook</a>
|
| 18 |
-
<a href="#readiness">Readiness</a>
|
| 19 |
-
<a href="#models">Models</a>
|
| 20 |
-
</nav>
|
| 21 |
-
<a class="reader-toggle evidence-link" href="/">Reader App</a>
|
| 22 |
-
</header>
|
| 23 |
-
|
| 24 |
-
<main class="evidence-shell" aria-labelledby="evidenceTitle">
|
| 25 |
-
<section class="evidence-hero">
|
| 26 |
-
<p class="kicker">Hackathon evidence</p>
|
| 27 |
-
<h1 id="evidenceTitle">Judging receipts for Tiny Narrator</h1>
|
| 28 |
-
<p class="dek">
|
| 29 |
-
This page keeps model budget, award evidence, runtime setup, and demo checks visible without crowding
|
| 30 |
-
the reader-first article experience.
|
| 31 |
-
</p>
|
| 32 |
-
</section>
|
| 33 |
-
|
| 34 |
-
<section id="runbook" class="evidence-section" aria-labelledby="runbookTitle">
|
| 35 |
-
<div class="demo-script-header">
|
| 36 |
-
<h2 id="runbookTitle">Judge Runbook</h2>
|
| 37 |
-
<span id="demoScriptStatus">Loading</span>
|
| 38 |
-
</div>
|
| 39 |
-
<ol id="demoScriptList" class="demo-script-list" aria-label="Judge demo runbook"></ol>
|
| 40 |
-
<ul id="demoApiCheckList" class="demo-api-check-list" aria-label="Judge API evidence checks"></ul>
|
| 41 |
-
</section>
|
| 42 |
-
|
| 43 |
-
<section class="evidence-section" aria-labelledby="awardTitle">
|
| 44 |
-
<div class="award-header">
|
| 45 |
-
<h2 id="awardTitle">Award Evidence</h2>
|
| 46 |
-
</div>
|
| 47 |
-
<ul id="awardEvidenceList" class="award-list" aria-label="Hackathon bonus evidence"></ul>
|
| 48 |
-
</section>
|
| 49 |
-
|
| 50 |
-
<section id="readiness" class="evidence-section" aria-labelledby="readinessTitle">
|
| 51 |
-
<div class="submission-header">
|
| 52 |
-
<h2 id="readinessTitle">Submission Readiness</h2>
|
| 53 |
-
<span id="submissionReadinessStatus">Checking</span>
|
| 54 |
-
<button id="copyEvidenceButton" type="button">Copy Evidence</button>
|
| 55 |
-
</div>
|
| 56 |
-
<p id="evidenceCopyStatus" class="live-narration" aria-live="polite">Evidence bundle ready to copy.</p>
|
| 57 |
-
<ul id="submissionReadinessList" class="award-list" aria-label="Submission readiness checks"></ul>
|
| 58 |
-
</section>
|
| 59 |
-
|
| 60 |
-
<section id="models" class="evidence-section" aria-labelledby="budgetTitle">
|
| 61 |
-
<div class="budget-header">
|
| 62 |
-
<h2 id="budgetTitle">Model Budget</h2>
|
| 63 |
-
<span id="budgetStatus">Checking</span>
|
| 64 |
-
</div>
|
| 65 |
-
<ul id="modelBudgetList" class="budget-list" aria-label="Tiny Titan model budget"></ul>
|
| 66 |
-
</section>
|
| 67 |
-
|
| 68 |
-
<section class="evidence-section" aria-labelledby="runtimeTitle">
|
| 69 |
-
<div class="runtime-header">
|
| 70 |
-
<h2 id="runtimeTitle">Runtime Status</h2>
|
| 71 |
-
<span id="runtimeStatusSummary">Checking</span>
|
| 72 |
-
</div>
|
| 73 |
-
<ul id="runtimeStatusList" class="runtime-list" aria-label="Live model runtime status"></ul>
|
| 74 |
-
</section>
|
| 75 |
-
|
| 76 |
-
<section class="evidence-section" aria-labelledby="setupTitle">
|
| 77 |
-
<div class="runtime-header">
|
| 78 |
-
<h2 id="setupTitle">Runtime Plan</h2>
|
| 79 |
-
<span id="runtimeSetupStatus">Loading</span>
|
| 80 |
-
</div>
|
| 81 |
-
<ul id="runtimeSetupList" class="runtime-list" aria-label="Model runtime setup plan"></ul>
|
| 82 |
-
</section>
|
| 83 |
-
|
| 84 |
-
<section class="evidence-section" aria-labelledby="imageReceiptTitle">
|
| 85 |
-
<div class="image-receipt-header">
|
| 86 |
-
<h2 id="imageReceiptTitle">Image Receipts</h2>
|
| 87 |
-
<span id="imageReceiptStatus">Loading</span>
|
| 88 |
-
</div>
|
| 89 |
-
<ul id="imageReceiptList" class="image-receipt-list" aria-label="Generated image provenance"></ul>
|
| 90 |
-
</section>
|
| 91 |
-
</main>
|
| 92 |
-
|
| 93 |
-
<script src="/static/evidence.js" type="module"></script>
|
| 94 |
-
</body>
|
| 95 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/evidence.js
DELETED
|
@@ -1,341 +0,0 @@
|
|
| 1 |
-
const demoScriptStatus = document.querySelector("#demoScriptStatus");
|
| 2 |
-
const demoScriptList = document.querySelector("#demoScriptList");
|
| 3 |
-
const demoApiCheckList = document.querySelector("#demoApiCheckList");
|
| 4 |
-
const awardEvidenceList = document.querySelector("#awardEvidenceList");
|
| 5 |
-
const submissionReadinessStatus = document.querySelector("#submissionReadinessStatus");
|
| 6 |
-
const submissionReadinessList = document.querySelector("#submissionReadinessList");
|
| 7 |
-
const copyEvidenceButton = document.querySelector("#copyEvidenceButton");
|
| 8 |
-
const evidenceCopyStatus = document.querySelector("#evidenceCopyStatus");
|
| 9 |
-
const budgetStatus = document.querySelector("#budgetStatus");
|
| 10 |
-
const modelBudgetList = document.querySelector("#modelBudgetList");
|
| 11 |
-
const runtimeStatusSummary = document.querySelector("#runtimeStatusSummary");
|
| 12 |
-
const runtimeStatusList = document.querySelector("#runtimeStatusList");
|
| 13 |
-
const runtimeSetupStatus = document.querySelector("#runtimeSetupStatus");
|
| 14 |
-
const runtimeSetupList = document.querySelector("#runtimeSetupList");
|
| 15 |
-
const imageReceiptStatus = document.querySelector("#imageReceiptStatus");
|
| 16 |
-
const imageReceiptList = document.querySelector("#imageReceiptList");
|
| 17 |
-
|
| 18 |
-
function escapeHtml(value) {
|
| 19 |
-
return String(value).replace(/[&<>"']/g, (char) => ({
|
| 20 |
-
"&": "&",
|
| 21 |
-
"<": "<",
|
| 22 |
-
">": ">",
|
| 23 |
-
'"': """,
|
| 24 |
-
"'": "'",
|
| 25 |
-
}[char]));
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
function roleLabel(role) {
|
| 29 |
-
return role
|
| 30 |
-
.split("_")
|
| 31 |
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
| 32 |
-
.join(" ");
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
async function postJson(url, payload) {
|
| 36 |
-
const options = payload === undefined ? {} : {
|
| 37 |
-
method: "POST",
|
| 38 |
-
headers: { "Content-Type": "application/json" },
|
| 39 |
-
body: JSON.stringify(payload),
|
| 40 |
-
};
|
| 41 |
-
const response = await fetch(url, options);
|
| 42 |
-
if (!response.ok) {
|
| 43 |
-
throw new Error(`${response.status} ${response.statusText}`);
|
| 44 |
-
}
|
| 45 |
-
return response.json();
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
async function copyTextToClipboard(text, successMessage, emptyMessage, unavailableMessage) {
|
| 49 |
-
if (!text) {
|
| 50 |
-
evidenceCopyStatus.textContent = emptyMessage;
|
| 51 |
-
return false;
|
| 52 |
-
}
|
| 53 |
-
if (!navigator.clipboard) {
|
| 54 |
-
evidenceCopyStatus.textContent = unavailableMessage;
|
| 55 |
-
return false;
|
| 56 |
-
}
|
| 57 |
-
await navigator.clipboard.writeText(text);
|
| 58 |
-
evidenceCopyStatus.textContent = successMessage;
|
| 59 |
-
return true;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
async function loadDemoScript() {
|
| 63 |
-
try {
|
| 64 |
-
const payload = await postJson("/api/demo-script");
|
| 65 |
-
demoScriptStatus.textContent = `${payload.estimated_minutes} min`;
|
| 66 |
-
demoScriptList.innerHTML = payload.actions
|
| 67 |
-
.map((item) => `
|
| 68 |
-
<li>
|
| 69 |
-
<div class="demo-script-title">
|
| 70 |
-
<span>${escapeHtml(item.label)}</span>
|
| 71 |
-
<span>${escapeHtml(`Step ${item.step}`)}</span>
|
| 72 |
-
</div>
|
| 73 |
-
<p>${escapeHtml(item.action)}</p>
|
| 74 |
-
<p>${escapeHtml(item.evidence)}</p>
|
| 75 |
-
</li>
|
| 76 |
-
`)
|
| 77 |
-
.join("");
|
| 78 |
-
demoApiCheckList.innerHTML = payload.api_checks
|
| 79 |
-
.map((item) => `
|
| 80 |
-
<li>
|
| 81 |
-
<span>${escapeHtml(item.method)}</span>
|
| 82 |
-
<code>${escapeHtml(item.path)}</code>
|
| 83 |
-
<p>${escapeHtml(item.expect)}${item.sample_body ? " Sample body included." : ""}</p>
|
| 84 |
-
<code class="demo-api-command">${escapeHtml(item.curl)}</code>
|
| 85 |
-
<button class="demo-command-copy" type="button" data-command="${escapeHtml(item.curl)}">Copy curl</button>
|
| 86 |
-
<code class="demo-api-command">${escapeHtml(item.powershell)}</code>
|
| 87 |
-
<button class="demo-command-copy" type="button" data-command="${escapeHtml(item.powershell)}">Copy PowerShell</button>
|
| 88 |
-
</li>
|
| 89 |
-
`)
|
| 90 |
-
.join("");
|
| 91 |
-
} catch {
|
| 92 |
-
demoScriptStatus.textContent = "Unavailable";
|
| 93 |
-
demoScriptList.innerHTML = `
|
| 94 |
-
<li>
|
| 95 |
-
<div class="demo-script-title">
|
| 96 |
-
<span>Judge runbook</span>
|
| 97 |
-
<span>offline</span>
|
| 98 |
-
</div>
|
| 99 |
-
<p>The structured demo script is unavailable.</p>
|
| 100 |
-
</li>
|
| 101 |
-
`;
|
| 102 |
-
demoApiCheckList.innerHTML = "";
|
| 103 |
-
}
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
async function loadAwardEvidence() {
|
| 107 |
-
try {
|
| 108 |
-
const payload = await postJson("/api/award-evidence");
|
| 109 |
-
awardEvidenceList.innerHTML = payload.items
|
| 110 |
-
.map((item) => `
|
| 111 |
-
<li>
|
| 112 |
-
<div class="award-title">
|
| 113 |
-
<span>${escapeHtml(item.label)}</span>
|
| 114 |
-
<span class="award-status">${escapeHtml(item.status)}</span>
|
| 115 |
-
</div>
|
| 116 |
-
<p class="award-evidence">${escapeHtml(item.evidence)}</p>
|
| 117 |
-
</li>
|
| 118 |
-
`)
|
| 119 |
-
.join("");
|
| 120 |
-
} catch {
|
| 121 |
-
awardEvidenceList.innerHTML = `
|
| 122 |
-
<li>
|
| 123 |
-
<div class="award-title">
|
| 124 |
-
<span>Checklist</span>
|
| 125 |
-
<span class="award-status">offline</span>
|
| 126 |
-
</div>
|
| 127 |
-
<p class="award-evidence">Award evidence is unavailable.</p>
|
| 128 |
-
</li>
|
| 129 |
-
`;
|
| 130 |
-
}
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
async function loadSubmissionReadiness() {
|
| 134 |
-
try {
|
| 135 |
-
const payload = await postJson("/api/submission-readiness");
|
| 136 |
-
submissionReadinessStatus.textContent = payload.all_passed
|
| 137 |
-
? `${payload.passed_checks}/${payload.total_checks} pass`
|
| 138 |
-
: "Review needed";
|
| 139 |
-
submissionReadinessList.innerHTML = payload.checks
|
| 140 |
-
.map((item) => `
|
| 141 |
-
<li>
|
| 142 |
-
<div class="award-title">
|
| 143 |
-
<span>${escapeHtml(item.label)}</span>
|
| 144 |
-
<span class="award-status">${escapeHtml(item.status)}</span>
|
| 145 |
-
</div>
|
| 146 |
-
<p class="award-evidence">${escapeHtml(item.evidence)}</p>
|
| 147 |
-
</li>
|
| 148 |
-
`)
|
| 149 |
-
.join("");
|
| 150 |
-
} catch {
|
| 151 |
-
submissionReadinessStatus.textContent = "Unavailable";
|
| 152 |
-
submissionReadinessList.innerHTML = `
|
| 153 |
-
<li>
|
| 154 |
-
<div class="award-title">
|
| 155 |
-
<span>Submission readiness</span>
|
| 156 |
-
<span class="award-status">offline</span>
|
| 157 |
-
</div>
|
| 158 |
-
<p class="award-evidence">Submission readiness evidence is unavailable.</p>
|
| 159 |
-
</li>
|
| 160 |
-
`;
|
| 161 |
-
}
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
async function loadModelBudget() {
|
| 165 |
-
try {
|
| 166 |
-
const payload = await postJson("/api/model-budget");
|
| 167 |
-
budgetStatus.textContent = payload.all_models_within_limit
|
| 168 |
-
? `All <= ${payload.limit_billion}B`
|
| 169 |
-
: "Review needed";
|
| 170 |
-
modelBudgetList.innerHTML = payload.models
|
| 171 |
-
.map((model) => `
|
| 172 |
-
<li>
|
| 173 |
-
<div class="budget-row">
|
| 174 |
-
<span>${escapeHtml(roleLabel(model.role))}</span>
|
| 175 |
-
<span class="budget-pill">${escapeHtml(model.params)}</span>
|
| 176 |
-
</div>
|
| 177 |
-
<p>${model.within_limit ? "Tiny Titan pass" : "Review parameter budget"}</p>
|
| 178 |
-
<p>${escapeHtml(model.id)}</p>
|
| 179 |
-
</li>
|
| 180 |
-
`)
|
| 181 |
-
.join("");
|
| 182 |
-
} catch {
|
| 183 |
-
budgetStatus.textContent = "Unavailable";
|
| 184 |
-
modelBudgetList.innerHTML = `
|
| 185 |
-
<li>
|
| 186 |
-
<div class="budget-row">
|
| 187 |
-
<span>Model budget</span>
|
| 188 |
-
<span class="budget-pill">offline</span>
|
| 189 |
-
</div>
|
| 190 |
-
<p>Tiny Titan evidence is unavailable.</p>
|
| 191 |
-
</li>
|
| 192 |
-
`;
|
| 193 |
-
}
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
-
async function loadRuntimeStatus() {
|
| 197 |
-
try {
|
| 198 |
-
const payload = await postJson("/api/runtime-status");
|
| 199 |
-
const brain = payload.reader_brain;
|
| 200 |
-
const speech = payload.speech;
|
| 201 |
-
const brainLabel = brain.available ? "llama.cpp online" : "llama.cpp fallback";
|
| 202 |
-
const speechLabel = speech.available ? "Kokoro online" : "voice fallback";
|
| 203 |
-
runtimeStatusSummary.textContent = `${brainLabel}, ${speechLabel}`;
|
| 204 |
-
const runtimeItems = [
|
| 205 |
-
["Reader brain", payload.reader_brain],
|
| 206 |
-
["Vision", payload.vision],
|
| 207 |
-
["Speech", payload.speech],
|
| 208 |
-
["Image generation", payload.image_generation],
|
| 209 |
-
];
|
| 210 |
-
runtimeStatusList.innerHTML = runtimeItems
|
| 211 |
-
.map(([label, item]) => `
|
| 212 |
-
<li>
|
| 213 |
-
<div class="runtime-row">
|
| 214 |
-
<span>${escapeHtml(label)}</span>
|
| 215 |
-
<span class="runtime-pill">${escapeHtml(item.status)}</span>
|
| 216 |
-
</div>
|
| 217 |
-
<p>${escapeHtml(item.model)}${item.fallback ? ` | ${escapeHtml(item.fallback)}` : ""}</p>
|
| 218 |
-
</li>
|
| 219 |
-
`)
|
| 220 |
-
.join("");
|
| 221 |
-
} catch {
|
| 222 |
-
runtimeStatusSummary.textContent = "Fallback ready";
|
| 223 |
-
runtimeStatusList.innerHTML = `
|
| 224 |
-
<li>
|
| 225 |
-
<div class="runtime-row">
|
| 226 |
-
<span>Runtime status</span>
|
| 227 |
-
<span class="runtime-pill">offline</span>
|
| 228 |
-
</div>
|
| 229 |
-
<p>Live runtime status is unavailable; deterministic fallbacks remain documented.</p>
|
| 230 |
-
</li>
|
| 231 |
-
`;
|
| 232 |
-
}
|
| 233 |
-
}
|
| 234 |
-
|
| 235 |
-
async function loadRuntimeSetup() {
|
| 236 |
-
try {
|
| 237 |
-
const payload = await postJson("/api/runtime-setup");
|
| 238 |
-
runtimeSetupStatus.textContent = `${payload.steps.length} paths`;
|
| 239 |
-
runtimeSetupList.innerHTML = payload.steps
|
| 240 |
-
.map((step) => `
|
| 241 |
-
<li>
|
| 242 |
-
<div class="runtime-row">
|
| 243 |
-
<span>${escapeHtml(step.label)}</span>
|
| 244 |
-
<span class="runtime-pill">${escapeHtml(step.runtime)}</span>
|
| 245 |
-
</div>
|
| 246 |
-
<code class="runtime-command">${escapeHtml(step.command)}</code>
|
| 247 |
-
<button class="runtime-command-copy" type="button" data-command="${escapeHtml(step.command)}">Copy command</button>
|
| 248 |
-
<p>${escapeHtml(step.fallback)}</p>
|
| 249 |
-
</li>
|
| 250 |
-
`)
|
| 251 |
-
.join("");
|
| 252 |
-
} catch {
|
| 253 |
-
runtimeSetupStatus.textContent = "Unavailable";
|
| 254 |
-
runtimeSetupList.innerHTML = `
|
| 255 |
-
<li>
|
| 256 |
-
<div class="runtime-row">
|
| 257 |
-
<span>Runtime plan</span>
|
| 258 |
-
<span class="runtime-pill">offline</span>
|
| 259 |
-
</div>
|
| 260 |
-
<p>Setup evidence is unavailable.</p>
|
| 261 |
-
</li>
|
| 262 |
-
`;
|
| 263 |
-
}
|
| 264 |
-
}
|
| 265 |
-
|
| 266 |
-
async function loadImageDescriptions() {
|
| 267 |
-
try {
|
| 268 |
-
const payload = await postJson("/api/image-descriptions");
|
| 269 |
-
imageReceiptStatus.textContent = `${payload.descriptions.length} receipts`;
|
| 270 |
-
imageReceiptList.innerHTML = payload.descriptions
|
| 271 |
-
.map((description) => `
|
| 272 |
-
<li>
|
| 273 |
-
<div class="image-receipt-row">
|
| 274 |
-
<span>${escapeHtml(description.id)}</span>
|
| 275 |
-
<span class="image-receipt-pill">${escapeHtml(description.generation_status)}</span>
|
| 276 |
-
</div>
|
| 277 |
-
<p>${escapeHtml(description.generation_model)} | seed ${escapeHtml(String(description.seed))}</p>
|
| 278 |
-
<p>${escapeHtml(description.prompt)}</p>
|
| 279 |
-
</li>
|
| 280 |
-
`)
|
| 281 |
-
.join("");
|
| 282 |
-
} catch {
|
| 283 |
-
imageReceiptStatus.textContent = "Unavailable";
|
| 284 |
-
imageReceiptList.innerHTML = `
|
| 285 |
-
<li>
|
| 286 |
-
<div class="image-receipt-row">
|
| 287 |
-
<span>Image receipts</span>
|
| 288 |
-
<span class="image-receipt-pill">offline</span>
|
| 289 |
-
</div>
|
| 290 |
-
<p>Generated image provenance is unavailable.</p>
|
| 291 |
-
</li>
|
| 292 |
-
`;
|
| 293 |
-
}
|
| 294 |
-
}
|
| 295 |
-
|
| 296 |
-
copyEvidenceButton.addEventListener("click", async () => {
|
| 297 |
-
try {
|
| 298 |
-
const payload = await postJson("/api/evidence-bundle");
|
| 299 |
-
const text = JSON.stringify(payload, null, 2);
|
| 300 |
-
await copyTextToClipboard(
|
| 301 |
-
text,
|
| 302 |
-
"Evidence bundle copied.",
|
| 303 |
-
"Evidence bundle is empty.",
|
| 304 |
-
"Evidence bundle loaded, but clipboard access is unavailable.",
|
| 305 |
-
);
|
| 306 |
-
} catch (error) {
|
| 307 |
-
evidenceCopyStatus.textContent = `Evidence bundle failed: ${error.message}`;
|
| 308 |
-
}
|
| 309 |
-
});
|
| 310 |
-
|
| 311 |
-
demoApiCheckList.addEventListener("click", async (event) => {
|
| 312 |
-
const button = event.target.closest(".demo-command-copy");
|
| 313 |
-
if (!button) return;
|
| 314 |
-
const command = button.dataset.command || "";
|
| 315 |
-
await copyTextToClipboard(
|
| 316 |
-
command,
|
| 317 |
-
"Demo command copied.",
|
| 318 |
-
"Demo command is empty.",
|
| 319 |
-
"Command is visible, but clipboard access is unavailable.",
|
| 320 |
-
);
|
| 321 |
-
});
|
| 322 |
-
|
| 323 |
-
runtimeSetupList.addEventListener("click", async (event) => {
|
| 324 |
-
const button = event.target.closest(".runtime-command-copy");
|
| 325 |
-
if (!button) return;
|
| 326 |
-
const command = button.dataset.command || "";
|
| 327 |
-
await copyTextToClipboard(
|
| 328 |
-
command,
|
| 329 |
-
"Runtime setup command copied.",
|
| 330 |
-
"Runtime setup command is empty.",
|
| 331 |
-
"Runtime setup command is visible, but clipboard access is unavailable.",
|
| 332 |
-
);
|
| 333 |
-
});
|
| 334 |
-
|
| 335 |
-
loadDemoScript();
|
| 336 |
-
loadAwardEvidence();
|
| 337 |
-
loadSubmissionReadiness();
|
| 338 |
-
loadModelBudget();
|
| 339 |
-
loadRuntimeStatus();
|
| 340 |
-
loadRuntimeSetup();
|
| 341 |
-
loadImageDescriptions();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/index.html
CHANGED
|
@@ -16,7 +16,6 @@
|
|
| 16 |
<a href="#why">Why</a>
|
| 17 |
<a href="#models">Models</a>
|
| 18 |
<a href="#notes">Field Notes</a>
|
| 19 |
-
<a href="/evidence">Evidence</a>
|
| 20 |
</nav>
|
| 21 |
<button id="readerToggle" class="reader-toggle" type="button" aria-pressed="false">
|
| 22 |
<span class="toggle-dot" aria-hidden="true"></span>
|
|
@@ -118,17 +117,17 @@
|
|
| 118 |
<p id="liveNarration" class="live-narration" aria-live="polite">
|
| 119 |
Turn on screen reader mode to begin.
|
| 120 |
</p>
|
| 121 |
-
<div class="reader-queue-header">
|
| 122 |
-
<h3>Reader Queue</h3>
|
| 123 |
-
<span id="readerQueueStatus">Loading</span>
|
| 124 |
-
</div>
|
| 125 |
-
<ol id="readerQueueList" class="runtime-list" aria-label="Semantic reader queue"></ol>
|
| 126 |
<div class="history-header">
|
| 127 |
<h3>Transcript</h3>
|
| 128 |
<button id="copyTranscriptButton" type="button">Copy</button>
|
| 129 |
<button id="clearTranscriptButton" type="button">Clear</button>
|
| 130 |
</div>
|
| 131 |
<ol id="transcriptLog" class="transcript-log" aria-label="Narration transcript"></ol>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
</aside>
|
| 133 |
</main>
|
| 134 |
|
|
|
|
| 16 |
<a href="#why">Why</a>
|
| 17 |
<a href="#models">Models</a>
|
| 18 |
<a href="#notes">Field Notes</a>
|
|
|
|
| 19 |
</nav>
|
| 20 |
<button id="readerToggle" class="reader-toggle" type="button" aria-pressed="false">
|
| 21 |
<span class="toggle-dot" aria-hidden="true"></span>
|
|
|
|
| 117 |
<p id="liveNarration" class="live-narration" aria-live="polite">
|
| 118 |
Turn on screen reader mode to begin.
|
| 119 |
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
<div class="history-header">
|
| 121 |
<h3>Transcript</h3>
|
| 122 |
<button id="copyTranscriptButton" type="button">Copy</button>
|
| 123 |
<button id="clearTranscriptButton" type="button">Clear</button>
|
| 124 |
</div>
|
| 125 |
<ol id="transcriptLog" class="transcript-log" aria-label="Narration transcript"></ol>
|
| 126 |
+
<div class="budget-header">
|
| 127 |
+
<h3>Model Stack</h3>
|
| 128 |
+
<span id="modelBudgetStatus">Checking</span>
|
| 129 |
+
</div>
|
| 130 |
+
<ul id="modelStackList" class="budget-list" aria-label="Tiny Titan model stack"></ul>
|
| 131 |
</aside>
|
| 132 |
</main>
|
| 133 |
|