| --- |
| title: TinyNarrator |
| emoji: 🏆 |
| colorFrom: gray |
| colorTo: yellow |
| sdk: docker |
| pinned: false |
| license: mit |
| short_description: A small-model accessibility screen reader. |
| tags: |
| - track:backyard |
| - sponsor:openbmb |
| - sponsor:openai |
| - sponsor:nvidia |
| - sponsor:modal |
| - achievement:offbrand |
| - achievement:llama |
| - achievement:sharing |
| - achievement:fieldnotes |
| --- |
| |
| Public GitHub repo: [https://github.com/cvpfus/tiny-narrator](https://github.com/cvpfus/tiny-narrator) |
|
|
| Demo video: [https://youtu.be/EYXhb9kKBzA](https://youtu.be/EYXhb9kKBzA) |
|
|
| Social post: [https://x.com/cvpfus_id/status/2066666318527713469](https://x.com/cvpfus_id/status/2066666318527713469) |
|
|
| Spaces (and blog post): [https://huggingface.co/spaces/build-small-hackathon/TinyNarrator](https://huggingface.co/spaces/build-small-hackathon/TinyNarrator) |
|
|
| # Tiny Narrator |
|
|
| Tiny Narrator is a Build Small Hackathon prototype: a custom Gradio Server app with two routes: a guided screen-reader article and a small-model article generator. |
|
|
| For the hackathon form, see [SUBMISSION.md](SUBMISSION.md). |
|
|
| ## Award Strategy |
|
|
| - **Tiny Titan:** every planned model is at or below 4B parameters. |
| - **Llama Champion:** the reader-brain layer calls a GGUF model through `llama.cpp`. |
| - **Off-Brand:** the visible UI is custom HTML, CSS, and JavaScript served by `gr.Server`. |
| - **Field Notes:** the repo documents model sizes, runtime choices, fallbacks, and accessibility decisions. |
|
|
| ## Recommended Models |
|
|
| | Role | Model | Params | Runtime | |
| | --- | ---: | ---: | --- | |
| | Reader brain | `nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF` | 3.97B | `llama.cpp` | |
| | Image understanding | `openbmb/MiniCPM-V-4.6` | 1B | OpenAI-compatible chat completions | |
| | Text to speech | `hexgrad/Kokoro-82M` | 82M | Python | |
| | Image generation | `black-forest-labs/FLUX.2-klein-4B` | 4B | Modal-hosted Klein | |
|
|
| ## Run Locally |
|
|
| Install dependencies: |
|
|
| ```powershell |
| python -m pip install -r requirements.txt |
| ``` |
|
|
| Copy the example environment file to `.env` and fill in the values. The app loads `.env` automatically at startup, so no shell exports are required. Process environment variables still override `.env` values. |
|
|
| ```powershell |
| Copy-Item .env.example .env |
| ``` |
|
|
| Start the llama.cpp reader-brain server locally: |
|
|
| ```powershell |
| llama-server -hf nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF:Q4_K_M --alias narrator-brain --port 8080 --host 0.0.0.0 --ctx-size 4096 --parallel 1 --reasoning off --n-gpu-layers 999 |
| ``` |
|
|
| Start the app: |
|
|
| ```powershell |
| python app.py |
| ``` |
|
|
| Open the local URL printed by Gradio. The Reader route calls `/api/reader-brain`, `/api/image-descriptions`, `/api/describe-image`, `/api/speak`, and `/api/model-budget`. The Generate route calls `/api/generate-article`, which drafts an article with the reader-brain model path and attaches a Klein thumbnail receipt. |
|
|
| ## Modal Reader Brain |
|
|
| For a free CPU Hugging Face Space, host the llama.cpp reader-brain server on Modal and point the app at its OpenAI-compatible `/v1` endpoint: |
|
|
| ```powershell |
| modal secret create tiny-narrator-reader-brain-token LLAMA_CPP_TOKEN=your-random-token |
| modal deploy modal_workers/reader_brain.py |
| ``` |
|
|
| Set `LLAMA_CPP_BASE_URL` to the deployed Modal URL with `/v1` appended, for example: |
|
|
| ```env |
| LLAMA_CPP_BASE_URL=https://your-workspace--tiny-narrator-reader-brain.modal.run/v1 |
| LLAMA_CPP_MODEL=narrator-brain |
| LLAMA_CPP_TOKEN=your-random-token |
| ``` |
|
|
| The Modal worker starts Nemotron on T4 with `--ctx-size 4096`, `--parallel 1`, `--reasoning off`, full GPU offload, and `--api-key` when `LLAMA_CPP_TOKEN` is configured. It uses the prebuilt `ghcr.io/ggml-org/llama.cpp:server-cuda12` image instead of compiling llama.cpp during deploy, clears that image's entrypoint so Modal can start its Python runner, and allows up to 10 minutes for the first GGUF download/load. It scales down when idle, so the first request after a cold start can be slower. |
|
|
| ## Modal Klein Image Generation |
|
|
| The image generation path uses a Modal-hosted `black-forest-labs/FLUX.2-klein-4B` worker. Deploy the worker: |
|
|
| ```powershell |
| modal secret create tiny-narrator-klein-token KLEIN_MODAL_TOKEN=your-random-token |
| modal deploy modal_workers/klein_image.py |
| ``` |
|
|
| Set `KLEIN_MODAL_ENDPOINT` to the deployed worker URL. Set the same `KLEIN_MODAL_TOKEN` value in your local `.env`. The Modal worker uses the fixed `tiny-narrator-klein-token` secret name so Modal's local and remote dependency graphs stay identical. |
|
|
| `/api/runtime-status` reports whether the Modal Klein worker is online or fallback-ready. `/api/runtime-setup` includes the deploy command and required environment variables. |
|
|
| ## MiniCPM-V-4.6 Image Descriptions |
|
|
| The image description path uses `openbmb/MiniCPM-V-4.6` through an OpenAI-compatible `/v1/chat/completions` endpoint. Provide `MINICPM_VISION_BASE_URL` and `MINICPM_VISION_API_KEY` to enable live screen-reader alt text for article images. |
|
|
| When the endpoint is not configured, unreachable, or returns invalid content, `/api/describe-image` and `/api/image-descriptions` fall back to deterministic cached alt text so reader mode remains usable. |
|
|
| Useful environment variables: |
|
|
| | Variable | Default | Purpose | |
| | --- | --- | --- | |
| | `LLAMA_CPP_BASE_URL` | `http://localhost:8080/v1` | OpenAI-compatible llama.cpp server URL, local or Modal | |
| | `LLAMA_CPP_MODEL` | `narrator-brain` | Alias passed to llama.cpp | |
| | `LLAMA_CPP_TOKEN` | *(empty)* | Optional bearer token for protected llama.cpp endpoints. When set, the app sends `Authorization: Bearer <token>`. | |
| | `GRADIO_SERVER_NAME` | `0.0.0.0` | Bind address for local or Space runtime | |
| | `GRADIO_SERVER_PORT` / `PORT` | `7860` | App port | |
| | `GRADIO_SHARE` | `false` | Set to `true` if Gradio cannot verify localhost and needs a share URL | |
| | `PUBLIC_BASE_URL` | `http://localhost:7860` | Base URL used in generated judge API commands | |
| | `LLAMA_CPP_TIMEOUT_SECONDS` | `90` | Timeout for local llama.cpp reader-brain and article generation calls | |
| | `KLEIN_MODAL_ENDPOINT` | *(empty)* | Base URL for the Modal Klein worker, without trailing slash. When set, `/api/generate-image` calls the live worker. | |
| | `KLEIN_MODAL_TOKEN` | *(empty)* | Shared bearer token for Modal worker auth. When set, the app sends `Authorization: Bearer <token>` and the worker rejects unauthenticated requests. | |
| | `KLEIN_MODAL_HEALTH_TIMEOUT_SECONDS` | `30` | Timeout for Modal Klein `/health`; increase for cold-starting Modal endpoints. | |
| | `KLEIN_MODAL_TIMEOUT_SECONDS` | `120` | Request timeout for Modal Klein image generation. | |
| | `MINICPM_VISION_BASE_URL` | *(empty)* | OpenAI-compatible MiniCPM-V-4.6 base URL; root or `/v1` both work. | |
| | `MINICPM_VISION_API_KEY` | *(empty)* | Bearer token for the MiniCPM vision endpoint. | |
| | `MINICPM_VISION_MODEL` | `openbmb/MiniCPM-V-4.6` | Model id sent to chat completions. | |
| | `MINICPM_VISION_TIMEOUT_SECONDS` | `45` | Request timeout for image descriptions. | |
|
|
| ## Verification |
|
|
| ```powershell |
| python scripts/verify.py |
| ``` |
|
|
| The verifier checks syntax, static assets, Space metadata consistency, deterministic fallback model paths, and generated speech file behavior. |
|
|
| ## Live Model Smoke Tests |
|
|
| After starting the app with configured model endpoints, run the opt-in smoke script to verify live inference: |
|
|
| ```powershell |
| python scripts/live_smoke.py --base-url http://127.0.0.1:7860 |
| ``` |
|
|
| The smoke script checks `/api/runtime-status`, `/api/describe-image`, and `/api/generate-image`. It skips checks for unconfigured runtimes and exits non-zero only when a configured live check fails. Secrets are never printed. |
|
|
| `/api/model-budget` exposes numeric parameter counts for every model role and reports whether the full stack stays within the 4B Tiny Titan limit. |
|
|
| `/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. |
|
|
| `/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, including `/api/generate-article`. The commands use `PUBLIC_BASE_URL` so they can be copied from the API response or submission notes. |
|
|
| `/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. |
|
|
| `/api/image-descriptions` includes image-generation provenance for every article illustration: the planned FLUX.2 klein model, prompt, seed, bundled asset URL, and fallback-ready status. |
|
|
| `/api/generate-article` accepts a topic and returns a generated article draft plus thumbnail provenance from `black-forest-labs/FLUX.2-klein-4B`. |
|
|
| `/api/submission-readiness` aggregates the judging receipts into one pass/fail payload covering model budget, award evidence, custom frontend assets, runtime setup, runtime status, accessibility, image receipts, demo API checks, and command base URL checks. |
|
|
| The readiness rollup only passes the demo API check when POST entries include executable sample bodies. |
|
|
| `/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. |
|
|
| ## Screen Reader Mode |
|
|
| The frontend builds a reading queue from semantic article nodes. When screen-reader mode is on: |
|
|
| - `Space` plays or pauses. |
| - `N` moves to the next item. |
| - `P` moves to the previous item. |
| - `H` moves to the next heading. |
| - `I` moves to the next image. |
| - `S` summarizes the current section. |
| - `R` repeats the current item. |
| - `Esc` stops the current audio. |
|
|
| Paragraph and heading nodes are spoken as raw article text for speed. Image nodes are described by MiniCPM-V-4.6 when configured, then the reader-brain path turns the result into concise narration; summaries and generated article drafting use the same reader-brain path. When reader-brain is needed, llama.cpp is primary, MiniCPM-V-4.6 is the first fallback when configured, and deterministic narration is the final fallback. Kokoro then generates speech. |
|
|
| 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. |
|
|
| 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. |
|
|
| Global reader shortcuts ignore buttons, links, selects, and inputs so the voice, speed, and auto-advance controls remain usable while reader mode is active. |
|
|
| Reader buttons expose `aria-keyshortcuts`, and the Repeat and Stop shortcuts are mirrored by visible controls so pointer and keyboard users share the same command surface. |
|
|
| The session panel keeps a transcript of recent narration with reader position, runtime, latency, copy, and clear controls, making the spoken path inspectable during demos and useful for the Field Notes write-up. Copy actions report when browser clipboard access is unavailable, so the visible transcript and commands remain inspectable. |
|
|
| 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 MiniCPM-V-4.6 endpoint is unavailable, deterministic alt-text fallbacks keep the screen-reader path usable. Image narration always starts with "Image description." so users can distinguish generated image descriptions from article text. |
|
|
| `/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. |
|
|
| 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. |
|
|
| Generated speech files are pruned automatically so repeated demos do not grow the Space's `outputs` directory without bound. |
|
|
| The reader bar exposes Kokoro voice selection and speaking speed controls. Defaults come from `/api/article-manifest` so the UI, docs, and backend stay aligned. |
|
|
| Auto-advance is available as an opt-in reader control. It stays off by default so users keep manual control unless they choose continuous reading. |
|
|
| Navigation commands interrupt current speech before starting the next request, matching the expectation that a screen reader responds immediately when the user moves. |
|
|
| 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. |
|
|
| 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. |
|
|
| `/api/submission-readiness` gives judges one compact rollup of the claims the live app can prove. |
|
|
| `/api/evidence-bundle` returns the core judge receipts as formatted JSON for quick review or submission notes. |
|
|
| `/api/runtime-setup` summarizes each model path's runtime, setup command, and fallback chain, keeping the live demo honest about what is online, what can fall back to MiniCPM-V-4.6, and what is deterministic. |
|
|
| `/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. |
|
|
| ## Generate Route |
|
|
| The header exposes two routes: Reader and Generate. Generate lets a user enter a topic, calls `/api/generate-article`, and renders a short semantic article with a thumbnail receipt. The text path uses the configured llama.cpp reader-brain model when available and falls back to deterministic article structure. The thumbnail path calls the Modal Klein worker for live image generation when `KLEIN_MODAL_ENDPOINT` is configured, and falls back to bundled SVG assets with explicit runtime metadata when the worker is unavailable. |
|
|