Spaces:
Paused
Paused
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>
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 (
|
| 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=
|
|
|
|
| 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
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 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 |
|