cvpfus Codex commited on
Commit ·
680e5da
1
Parent(s): a1018e3
Interrupt speech on reader navigation
Browse filesCo-authored-by: Codex <codex@openai.com>
- .gitignore +1 -0
- FIELD_NOTES.md +2 -0
- README.md +2 -0
- scripts/verify.py +7 -0
- static/app.js +10 -2
.gitignore
CHANGED
|
@@ -5,6 +5,7 @@ venv/
|
|
| 5 |
outputs/*.wav
|
| 6 |
outputs/*.mp3
|
| 7 |
outputs/*.flac
|
|
|
|
| 8 |
.gradio/
|
| 9 |
.env
|
| 10 |
server.pid
|
|
|
|
| 5 |
outputs/*.wav
|
| 6 |
outputs/*.mp3
|
| 7 |
outputs/*.flac
|
| 8 |
+
outputs/*.log
|
| 9 |
.gradio/
|
| 10 |
.env
|
| 11 |
server.pid
|
FIELD_NOTES.md
CHANGED
|
@@ -53,6 +53,8 @@ Reader settings are manifest-backed: the app exposes known Kokoro voices and spe
|
|
| 53 |
|
| 54 |
Auto-advance is intentionally opt-in. This keeps the default reader mode user-controlled while still supporting a continuous-reading demo path.
|
| 55 |
|
|
|
|
|
|
|
| 56 |
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.
|
| 57 |
|
| 58 |
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.
|
|
|
|
| 53 |
|
| 54 |
Auto-advance is intentionally opt-in. This keeps the default reader mode user-controlled while still supporting a continuous-reading demo path.
|
| 55 |
|
| 56 |
+
Reader navigation interrupts active playback before requesting the next narration. That keeps fast keyboard movement predictable and avoids overlapping speech during demos.
|
| 57 |
+
|
| 58 |
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.
|
| 59 |
|
| 60 |
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.
|
README.md
CHANGED
|
@@ -94,6 +94,8 @@ The reader bar exposes Kokoro voice selection and speaking speed controls. Defau
|
|
| 94 |
|
| 95 |
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.
|
| 96 |
|
|
|
|
|
|
|
| 97 |
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.
|
| 98 |
|
| 99 |
The session panel also renders a manifest-backed demo checklist for Tiny Titan, Llama Champion, Off-Brand, and Field Notes evidence.
|
|
|
|
| 94 |
|
| 95 |
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.
|
| 96 |
|
| 97 |
+
Navigation commands interrupt current speech before starting the next request, matching the expectation that a screen reader responds immediately when the user moves.
|
| 98 |
+
|
| 99 |
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.
|
| 100 |
|
| 101 |
The session panel also renders a manifest-backed demo checklist for Tiny Titan, Llama Champion, Off-Brand, and Field Notes evidence.
|
scripts/verify.py
CHANGED
|
@@ -30,6 +30,13 @@ def verify_static_assets() -> None:
|
|
| 30 |
for path in required:
|
| 31 |
assert_true(path.exists(), f"Missing required asset: {path}")
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
def verify_core_fallbacks() -> None:
|
| 35 |
narration = app.reader_brain_core(
|
|
|
|
| 30 |
for path in required:
|
| 31 |
assert_true(path.exists(), f"Missing required asset: {path}")
|
| 32 |
|
| 33 |
+
app_js = (ROOT / "static" / "app.js").read_text(encoding="utf-8")
|
| 34 |
+
assert_true("function haltPlayback" in app_js, "Reader controls should expose a shared playback halt helper")
|
| 35 |
+
assert_true(
|
| 36 |
+
"haltPlayback({ clearAutoAdvance: false });" in app_js,
|
| 37 |
+
"New narration commands should interrupt current speech immediately",
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
|
| 41 |
def verify_core_fallbacks() -> None:
|
| 42 |
narration = app.reader_brain_core(
|
static/app.js
CHANGED
|
@@ -220,16 +220,22 @@ function setActive(index) {
|
|
| 220 |
currentStatus.textContent = `${node.type}, item ${currentIndex + 1} of ${nodes.length}`;
|
| 221 |
}
|
| 222 |
|
| 223 |
-
function
|
| 224 |
audio.pause();
|
| 225 |
audio.removeAttribute("src");
|
| 226 |
window.speechSynthesis?.cancel();
|
| 227 |
-
|
|
|
|
|
|
|
| 228 |
activeSpeechSerial = 0;
|
| 229 |
playing = false;
|
| 230 |
controls.play.textContent = "Play";
|
| 231 |
}
|
| 232 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
function maybeAutoAdvance(serial) {
|
| 234 |
if (!enabled || !autoAdvanceControl.checked || !activeAutoAdvance || serial !== requestSerial) {
|
| 235 |
return;
|
|
@@ -314,6 +320,7 @@ async function postJson(url, payload) {
|
|
| 314 |
async function narrate(index) {
|
| 315 |
if (!enabled || !nodes[index]) return;
|
| 316 |
const serial = ++requestSerial;
|
|
|
|
| 317 |
setActive(index);
|
| 318 |
const node = nodes[index];
|
| 319 |
runtimeStatus.textContent = "Thinking";
|
|
@@ -364,6 +371,7 @@ async function narrate(index) {
|
|
| 364 |
async function summarizeCurrentSection() {
|
| 365 |
if (!enabled) return;
|
| 366 |
const serial = ++requestSerial;
|
|
|
|
| 367 |
const section = currentSectionPayload();
|
| 368 |
runtimeStatus.textContent = "Summarizing";
|
| 369 |
controls.summary.disabled = true;
|
|
|
|
| 220 |
currentStatus.textContent = `${node.type}, item ${currentIndex + 1} of ${nodes.length}`;
|
| 221 |
}
|
| 222 |
|
| 223 |
+
function haltPlayback({ clearAutoAdvance = true } = {}) {
|
| 224 |
audio.pause();
|
| 225 |
audio.removeAttribute("src");
|
| 226 |
window.speechSynthesis?.cancel();
|
| 227 |
+
if (clearAutoAdvance) {
|
| 228 |
+
activeAutoAdvance = false;
|
| 229 |
+
}
|
| 230 |
activeSpeechSerial = 0;
|
| 231 |
playing = false;
|
| 232 |
controls.play.textContent = "Play";
|
| 233 |
}
|
| 234 |
|
| 235 |
+
function stopAudio() {
|
| 236 |
+
haltPlayback();
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
function maybeAutoAdvance(serial) {
|
| 240 |
if (!enabled || !autoAdvanceControl.checked || !activeAutoAdvance || serial !== requestSerial) {
|
| 241 |
return;
|
|
|
|
| 320 |
async function narrate(index) {
|
| 321 |
if (!enabled || !nodes[index]) return;
|
| 322 |
const serial = ++requestSerial;
|
| 323 |
+
haltPlayback({ clearAutoAdvance: false });
|
| 324 |
setActive(index);
|
| 325 |
const node = nodes[index];
|
| 326 |
runtimeStatus.textContent = "Thinking";
|
|
|
|
| 371 |
async function summarizeCurrentSection() {
|
| 372 |
if (!enabled) return;
|
| 373 |
const serial = ++requestSerial;
|
| 374 |
+
haltPlayback({ clearAutoAdvance: false });
|
| 375 |
const section = currentSectionPayload();
|
| 376 |
runtimeStatus.textContent = "Summarizing";
|
| 377 |
controls.summary.disabled = true;
|