cvpfus Codex commited on
Commit
680e5da
·
1 Parent(s): a1018e3

Interrupt speech on reader navigation

Browse files

Co-authored-by: Codex <codex@openai.com>

Files changed (5) hide show
  1. .gitignore +1 -0
  2. FIELD_NOTES.md +2 -0
  3. README.md +2 -0
  4. scripts/verify.py +7 -0
  5. 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 stopAudio() {
224
  audio.pause();
225
  audio.removeAttribute("src");
226
  window.speechSynthesis?.cancel();
227
- activeAutoAdvance = false;
 
 
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;