minhahwang Copilot commited on
Commit
08f3c66
Β·
1 Parent(s): eacea91

fix: pause retains position, Ask pauses play, auto-start mic, hide Q&A during play

Browse files

- Play hides Q&A panel/resume btn when playing
- Ask button cancels play_event and auto-starts microphone via JS
- stop_recording auto-submits question
- Error yield uses current para_idx (not 0)
- Fix para_idx computed before error check to avoid NameError

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Files changed (1) hide show
  1. app.py +37 -21
app.py CHANGED
@@ -529,8 +529,9 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
529
  with gr.Group(visible=False) as qa_input_group:
530
  question_audio = gr.Audio(
531
  sources=["microphone"], type="filepath",
532
- label="🎀 Ask your question (recording auto-submits when you stop)",
533
  show_label=True, streaming=False,
 
534
  )
535
  question_text = gr.Textbox(
536
  placeholder="Or type your question here...",
@@ -852,7 +853,8 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
852
  "<div style='text-align:center;font-size:10px;color:#64748b;font-family:monospace;margin-top:8px;'>No content loaded</div>",
853
  gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
854
  gr.Slider(value=0), render_story_text(paras, 0) if paras else "",
855
- gr.HTML(visible=False))
 
856
  return
857
 
858
  n = len(tts_chunks)
@@ -860,15 +862,18 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
860
  f"<div style='text-align:center;font-size:10px;color:#4ade80;font-family:monospace;margin-top:8px;'>Generating chunk 1 / {n}…</div>",
861
  gr.Button(visible=False), gr.Button(visible=True), gr.Button(visible=False),
862
  gr.Slider(value=0), render_story_text(paras, 0) if paras else "",
863
- gr.HTML(visible=False))
 
864
 
865
  for sample_rate, wav, i, total, err in generate_audio_stream(tts_chunks, voice_profile_id=profile_id):
 
 
866
  if err:
867
  yield (gr.update(), f"<div style='color:#ef4444;font-size:11px;'>Error on chunk {i+1}: {err}</div>", "",
868
  gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
869
- gr.Slider(value=0), gr.update(), gr.HTML(visible=False))
 
870
  return
871
- # Map chunk index to paragraph index (approximate: chunks β‰ˆ sentences, paragraphs β‰ˆ groups)
872
  para_idx = min(int(i * n_paras / total), n_paras - 1) if total > 0 else 0
873
  next_label = f"chunk {i+2} / {total}" if i + 1 < total else "last chunk"
874
  chunk_html = f"<div style='text-align:center;font-size:10px;color:#4ade80;font-family:monospace;margin-top:8px;'>β–Ά Playing chunk {i+1} / {total} β€” generating {next_label}</div>"
@@ -876,19 +881,22 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
876
  yield ((sample_rate, wav), _status_playing, chunk_html,
877
  gr.Button(visible=False), gr.Button(visible=True), gr.Button(visible=True),
878
  gr.Slider(value=para_idx), render_story_text(paras, para_idx),
879
- gr.HTML(visible=is_last))
 
880
 
881
  done_html = f"<div style='text-align:center;font-size:10px;color:#94a3b8;font-family:monospace;margin-top:8px;'>βœ… {n} chunks complete</div>"
882
  yield (gr.update(), _status_done, done_html,
883
  gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
884
  gr.Slider(value=max(n_paras - 1, 0)), render_story_text(paras, max(n_paras - 1, 0)),
885
- gr.HTML(visible=True))
 
886
 
887
  play_event = play_btn.click(
888
  stream_tts,
889
  inputs=[tts_chunks_state, paragraphs_state, voice_profile_state],
890
  outputs=[player_audio_control, player_status_bar, chunk_status, play_btn, pause_btn, ask_btn,
891
- timeline_slider, story_text_display, story_finished_panel]
 
892
  )
893
 
894
  # 6. Pause button β€” PAUSED state
@@ -948,7 +956,23 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
948
  enter_asking_state,
949
  inputs=[],
950
  outputs=[player_status_bar, play_btn, pause_btn, ask_btn, resume_btn, qa_panel, qa_input_group, answer_display, answer_audio],
951
- cancels=[play_event]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
952
  )
953
 
954
  # 9. Submit question
@@ -1032,18 +1056,10 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
1032
  outputs=[answer_display, answer_audio]
1033
  )
1034
 
1035
- # Auto-submit when voice recording completes
1036
- # Use .change() instead of .stop_recording() β€” .change() fires after audio value is committed
1037
- def auto_submit_on_recording(audio_path, question_txt, paragraphs, slider_val, profile_id):
1038
- """Auto-submit only when a new recording is available (not on clear)."""
1039
- if not audio_path:
1040
- # Audio was cleared, don't submit
1041
- return gr.update(), gr.update()
1042
- return handle_question_submit(question_txt, audio_path, paragraphs, slider_val, profile_id)
1043
-
1044
- question_audio.change(
1045
- auto_submit_on_recording,
1046
- inputs=[question_audio, question_text, paragraphs_state, timeline_slider, voice_profile_state],
1047
  outputs=[answer_display, answer_audio]
1048
  )
1049
 
 
529
  with gr.Group(visible=False) as qa_input_group:
530
  question_audio = gr.Audio(
531
  sources=["microphone"], type="filepath",
532
+ label="🎀 Ask your question (auto-submits when you stop)",
533
  show_label=True, streaming=False,
534
+ elem_id="qa_audio_input",
535
  )
536
  question_text = gr.Textbox(
537
  placeholder="Or type your question here...",
 
853
  "<div style='text-align:center;font-size:10px;color:#64748b;font-family:monospace;margin-top:8px;'>No content loaded</div>",
854
  gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
855
  gr.Slider(value=0), render_story_text(paras, 0) if paras else "",
856
+ gr.HTML(visible=False),
857
+ gr.HTML(visible=False), gr.Group(visible=False), gr.Button(visible=False))
858
  return
859
 
860
  n = len(tts_chunks)
 
862
  f"<div style='text-align:center;font-size:10px;color:#4ade80;font-family:monospace;margin-top:8px;'>Generating chunk 1 / {n}…</div>",
863
  gr.Button(visible=False), gr.Button(visible=True), gr.Button(visible=False),
864
  gr.Slider(value=0), render_story_text(paras, 0) if paras else "",
865
+ gr.HTML(visible=False),
866
+ gr.HTML(visible=False), gr.Group(visible=False), gr.Button(visible=False))
867
 
868
  for sample_rate, wav, i, total, err in generate_audio_stream(tts_chunks, voice_profile_id=profile_id):
869
+ # Map chunk index to paragraph index
870
+ para_idx = min(int(i * n_paras / total), n_paras - 1) if total > 0 else 0
871
  if err:
872
  yield (gr.update(), f"<div style='color:#ef4444;font-size:11px;'>Error on chunk {i+1}: {err}</div>", "",
873
  gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
874
+ gr.Slider(value=para_idx), gr.update(), gr.HTML(visible=False),
875
+ gr.HTML(visible=False), gr.Group(visible=False), gr.Button(visible=False))
876
  return
 
877
  para_idx = min(int(i * n_paras / total), n_paras - 1) if total > 0 else 0
878
  next_label = f"chunk {i+2} / {total}" if i + 1 < total else "last chunk"
879
  chunk_html = f"<div style='text-align:center;font-size:10px;color:#4ade80;font-family:monospace;margin-top:8px;'>β–Ά Playing chunk {i+1} / {total} β€” generating {next_label}</div>"
 
881
  yield ((sample_rate, wav), _status_playing, chunk_html,
882
  gr.Button(visible=False), gr.Button(visible=True), gr.Button(visible=True),
883
  gr.Slider(value=para_idx), render_story_text(paras, para_idx),
884
+ gr.HTML(visible=is_last),
885
+ gr.HTML(visible=False), gr.Group(visible=False), gr.Button(visible=False))
886
 
887
  done_html = f"<div style='text-align:center;font-size:10px;color:#94a3b8;font-family:monospace;margin-top:8px;'>βœ… {n} chunks complete</div>"
888
  yield (gr.update(), _status_done, done_html,
889
  gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
890
  gr.Slider(value=max(n_paras - 1, 0)), render_story_text(paras, max(n_paras - 1, 0)),
891
+ gr.HTML(visible=True),
892
+ gr.HTML(visible=False), gr.Group(visible=False), gr.Button(visible=False))
893
 
894
  play_event = play_btn.click(
895
  stream_tts,
896
  inputs=[tts_chunks_state, paragraphs_state, voice_profile_state],
897
  outputs=[player_audio_control, player_status_bar, chunk_status, play_btn, pause_btn, ask_btn,
898
+ timeline_slider, story_text_display, story_finished_panel,
899
+ qa_panel, qa_input_group, resume_btn]
900
  )
901
 
902
  # 6. Pause button β€” PAUSED state
 
956
  enter_asking_state,
957
  inputs=[],
958
  outputs=[player_status_bar, play_btn, pause_btn, ask_btn, resume_btn, qa_panel, qa_input_group, answer_display, answer_audio],
959
+ cancels=[play_event],
960
+ js="""() => {
961
+ // Auto-start microphone recording after a brief delay for UI to render
962
+ setTimeout(() => {
963
+ const recordBtn = document.querySelector('#qa_audio_input button[aria-label="Record"], #qa_audio_input button.record-button, .qa-audio-group button[aria-label="Record"]');
964
+ if (recordBtn) { recordBtn.click(); }
965
+ else {
966
+ // Fallback: find any record button inside the audio component
967
+ const btns = document.querySelectorAll('button');
968
+ for (const b of btns) {
969
+ if (b.getAttribute('aria-label') === 'Record' || b.textContent.includes('Record')) {
970
+ b.click(); break;
971
+ }
972
+ }
973
+ }
974
+ }, 500);
975
+ }"""
976
  )
977
 
978
  # 9. Submit question
 
1056
  outputs=[answer_display, answer_audio]
1057
  )
1058
 
1059
+ # Auto-submit when voice recording stops
1060
+ question_audio.stop_recording(
1061
+ handle_question_submit,
1062
+ inputs=[question_text, question_audio, paragraphs_state, timeline_slider, voice_profile_state],
 
 
 
 
 
 
 
 
1063
  outputs=[answer_display, answer_audio]
1064
  )
1065