cvpfus Codex commited on
Commit ·
4b953a3
1
Parent(s): bb63fe8
Add click-to-read queue controls
Browse filesCo-authored-by: Codex <codex@openai.com>
- FIELD_NOTES.md +1 -1
- README.md +1 -1
- SUBMISSION.md +1 -1
- app.py +1 -1
- scripts/verify.py +2 -0
- static/app.js +7 -0
FIELD_NOTES.md
CHANGED
|
@@ -40,7 +40,7 @@ Accessibility evidence lives at `/api/accessibility-audit`. It records the reade
|
|
| 40 |
|
| 41 |
## Reader Mode Behavior
|
| 42 |
|
| 43 |
-
The frontend creates a reading queue from semantic nodes: headings, paragraphs, quotes, figures, captions, and controls. It renders that same queue in the session panel, speaks one item at a time, keeps keyboard focus synchronized with the active node, marks that node with `aria-current`, and exposes the current narration through an `aria-live` region.
|
| 44 |
|
| 45 |
When screen reader mode turns on, the reader cursor starts at the focused article node or the most visible article node. That makes the feature feel connected to what the user is already reading instead of always jumping back to the top of the page.
|
| 46 |
|
|
|
|
| 40 |
|
| 41 |
## Reader Mode Behavior
|
| 42 |
|
| 43 |
+
The frontend creates a reading queue from semantic nodes: headings, paragraphs, quotes, figures, captions, and controls. It renders that same queue in the session panel with click-to-read controls, speaks one item at a time, keeps keyboard focus synchronized with the active node, marks that node with `aria-current`, and exposes the current narration through an `aria-live` region.
|
| 44 |
|
| 45 |
When screen reader mode turns on, the reader cursor starts at the focused article node or the most visible article node. That makes the feature feel connected to what the user is already reading instead of always jumping back to the top of the page.
|
| 46 |
|
README.md
CHANGED
|
@@ -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 renders the same semantic reader queue used by playback, including the active item marker
|
| 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 |
|
|
|
|
| 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 renders the same semantic reader queue used by playback, including the active item marker, total item count, and click-to-read controls for individual queue items.
|
| 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 |
|
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 and displays a 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 |
|
|
|
|
| 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 and displays a semantic reading queue with click-to-read controls, 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 |
|
app.py
CHANGED
|
@@ -415,7 +415,7 @@ def accessibility_audit_core() -> dict[str, Any]:
|
|
| 415 |
"id": "semantic_queue",
|
| 416 |
"label": "Semantic reading queue",
|
| 417 |
"status": "pass",
|
| 418 |
-
"evidence": "Readable article nodes declare data-reader-type values and the session panel renders their ordered queue.",
|
| 419 |
},
|
| 420 |
{
|
| 421 |
"id": "keyboard_navigation",
|
|
|
|
| 415 |
"id": "semantic_queue",
|
| 416 |
"label": "Semantic reading queue",
|
| 417 |
"status": "pass",
|
| 418 |
+
"evidence": "Readable article nodes declare data-reader-type values and the session panel renders their ordered queue with click-to-read controls.",
|
| 419 |
},
|
| 420 |
{
|
| 421 |
"id": "keyboard_navigation",
|
scripts/verify.py
CHANGED
|
@@ -84,6 +84,8 @@ def verify_static_assets() -> None:
|
|
| 84 |
assert_true("reader-node-" in app_js, "Reader nodes should receive stable ids for control context")
|
| 85 |
assert_true("renderReaderQueue" in app_js, "Frontend should render the semantic reader queue")
|
| 86 |
assert_true("readerQueueStatus.textContent" in app_js, "Reader queue should expose item count")
|
|
|
|
|
|
|
| 87 |
assert_true("narrate(node.index)" in app_js, "Reader mode should support click-to-read article items")
|
| 88 |
|
| 89 |
submission = (ROOT / "SUBMISSION.md").read_text(encoding="utf-8")
|
|
|
|
| 84 |
assert_true("reader-node-" in app_js, "Reader nodes should receive stable ids for control context")
|
| 85 |
assert_true("renderReaderQueue" in app_js, "Frontend should render the semantic reader queue")
|
| 86 |
assert_true("readerQueueStatus.textContent" in app_js, "Reader queue should expose item count")
|
| 87 |
+
assert_true("reader-queue-play" in app_js, "Reader queue should expose click-to-read controls")
|
| 88 |
+
assert_true("readerQueueList.addEventListener" in app_js, "Reader queue should handle click-to-read actions")
|
| 89 |
assert_true("narrate(node.index)" in app_js, "Reader mode should support click-to-read article items")
|
| 90 |
|
| 91 |
submission = (ROOT / "SUBMISSION.md").read_text(encoding="utf-8")
|
static/app.js
CHANGED
|
@@ -156,6 +156,7 @@ function renderReaderQueue() {
|
|
| 156 |
<span class="runtime-pill">${node.index === currentIndex ? "current" : "queued"}</span>
|
| 157 |
</div>
|
| 158 |
<p>${escapeHtml(node.text.slice(0, 120))}</p>
|
|
|
|
| 159 |
</li>
|
| 160 |
`)
|
| 161 |
.join("");
|
|
@@ -765,6 +766,12 @@ nodes.forEach((node) => {
|
|
| 765 |
narrate(node.index);
|
| 766 |
});
|
| 767 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 768 |
voiceControl.addEventListener("change", () => {
|
| 769 |
voiceStatus.textContent = voiceControl.selectedOptions[0]?.textContent || "Custom voice";
|
| 770 |
});
|
|
|
|
| 156 |
<span class="runtime-pill">${node.index === currentIndex ? "current" : "queued"}</span>
|
| 157 |
</div>
|
| 158 |
<p>${escapeHtml(node.text.slice(0, 120))}</p>
|
| 159 |
+
<button class="reader-queue-play" type="button" data-reader-index="${node.index}">Read item</button>
|
| 160 |
</li>
|
| 161 |
`)
|
| 162 |
.join("");
|
|
|
|
| 766 |
narrate(node.index);
|
| 767 |
});
|
| 768 |
});
|
| 769 |
+
readerQueueList.addEventListener("click", (event) => {
|
| 770 |
+
const button = event.target.closest(".reader-queue-play");
|
| 771 |
+
if (!button) return;
|
| 772 |
+
setEnabled(true);
|
| 773 |
+
narrate(Number(button.dataset.readerIndex));
|
| 774 |
+
});
|
| 775 |
voiceControl.addEventListener("change", () => {
|
| 776 |
voiceStatus.textContent = voiceControl.selectedOptions[0]?.textContent || "Custom voice";
|
| 777 |
});
|