Spaces:
Paused
Paused
fix to ui to visible
Browse files- app.py +73 -55
- inference_lfm.py +7 -2
app.py
CHANGED
|
@@ -532,9 +532,6 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 532 |
pause_btn = gr.Button("βΈ Pause", variant="secondary", scale=1, visible=False)
|
| 533 |
resume_btn = gr.Button("β©οΈ Resume Story", variant="secondary", scale=2, visible=False)
|
| 534 |
|
| 535 |
-
with gr.Row():
|
| 536 |
-
ask_btn = gr.Button("β Ask a Question", variant="primary", scale=2, visible=False)
|
| 537 |
-
|
| 538 |
chunk_status = gr.HTML("""
|
| 539 |
<div style="margin-top: 8px; font-size: 10px; color: #64748b; font-family: monospace; text-align: center;">
|
| 540 |
Chunk 0 / 0
|
|
@@ -553,14 +550,14 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 553 |
story_text_display = gr.HTML(visible=False)
|
| 554 |
timeline_slider = gr.Slider(minimum=0, maximum=100, step=1, value=0, label="π Story position (paragraph)", visible=False)
|
| 555 |
|
| 556 |
-
# Q&A Panel
|
| 557 |
qa_panel = gr.HTML("""
|
| 558 |
<div style="margin-top: 20px; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 16px;">
|
| 559 |
<h4 style="font-family: 'Playfair Display', Georgia, serif; font-size: 14px; margin-bottom: 12px; color: #FAF7F2;">β Ask About the Story</h4>
|
| 560 |
</div>
|
| 561 |
-
"""
|
| 562 |
|
| 563 |
-
with gr.Group(
|
| 564 |
question_audio = gr.Audio(
|
| 565 |
sources=["microphone"], type="filepath",
|
| 566 |
label="π€ Ask your question β answer generates when you stop recording",
|
|
@@ -568,11 +565,17 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 568 |
format="wav",
|
| 569 |
elem_id="qa_audio_input",
|
| 570 |
)
|
| 571 |
-
question_text = gr.Textbox(
|
|
|
|
|
|
|
|
|
|
| 572 |
submit_question_btn = gr.Button("π Get Answer", variant="primary", elem_id="qa_submit_btn")
|
| 573 |
|
| 574 |
-
answer_display = gr.HTML(
|
| 575 |
-
answer_audio = gr.Audio(label="Answer Audio", interactive=False,
|
|
|
|
|
|
|
|
|
|
| 576 |
|
| 577 |
# TAB 3: Clone Voice Studio
|
| 578 |
with gr.TabItem("ποΈ Clone Voice Studio") as clone_tab:
|
|
@@ -945,8 +948,7 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 945 |
"<div style='text-align:center;font-size:10px;color:#64748b;font-family:monospace;margin-top:8px;'>No content loaded</div>",
|
| 946 |
gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
|
| 947 |
gr.Slider(value=0), render_story_text(paras, 0) if paras else "",
|
| 948 |
-
gr.HTML(visible=False),
|
| 949 |
-
gr.HTML(visible=False), gr.Group(visible=False), gr.Button(visible=False))
|
| 950 |
return
|
| 951 |
|
| 952 |
n = len(tts_chunks)
|
|
@@ -954,17 +956,15 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 954 |
f"<div style='text-align:center;font-size:10px;color:#4ade80;font-family:monospace;margin-top:8px;'>βΆ Paragraph 1 / {n_paras}</div>",
|
| 955 |
gr.Button(visible=False), gr.Button(visible=True), gr.Button(visible=False),
|
| 956 |
gr.Slider(value=0), render_story_text(paras, 0) if paras else "",
|
| 957 |
-
gr.HTML(visible=False),
|
| 958 |
-
gr.HTML(visible=False), gr.Group(visible=False), gr.Button(visible=False))
|
| 959 |
|
| 960 |
for sample_rate, wav, i, total, err in generate_audio_stream(tts_chunks, voice_profile_id=profile_id):
|
| 961 |
-
# Map chunk index to paragraph index
|
| 962 |
para_idx = min(int(i * n_paras / total), n_paras - 1) if total > 0 else 0
|
| 963 |
if err:
|
| 964 |
yield (gr.update(), f"<div style='color:#ef4444;font-size:11px;'>Something went wrong. Try again.</div>", "",
|
| 965 |
gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
|
| 966 |
gr.Slider(value=para_idx), gr.update(), gr.HTML(visible=False),
|
| 967 |
-
gr.
|
| 968 |
return
|
| 969 |
para_idx = min(int(i * n_paras / total), n_paras - 1) if total > 0 else 0
|
| 970 |
is_last = (i + 1 >= total)
|
|
@@ -972,21 +972,18 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 972 |
yield ((sample_rate, wav.astype(np.float32) if hasattr(wav, 'astype') else wav), _status_playing, para_status,
|
| 973 |
gr.Button(visible=False), gr.Button(visible=True), gr.Button(visible=True),
|
| 974 |
gr.Slider(value=para_idx), render_story_text(paras, para_idx),
|
| 975 |
-
gr.HTML(visible=is_last),
|
| 976 |
-
gr.HTML(visible=False), gr.Group(visible=False), gr.Button(visible=False))
|
| 977 |
|
| 978 |
yield (gr.update(), _status_done, f"<div style='text-align:center;font-size:10px;color:#94a3b8;font-family:monospace;margin-top:8px;'>β
Story complete</div>",
|
| 979 |
gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
|
| 980 |
gr.Slider(value=max(n_paras - 1, 0)), render_story_text(paras, max(n_paras - 1, 0)),
|
| 981 |
-
gr.HTML(visible=True),
|
| 982 |
-
gr.HTML(visible=False), gr.Group(visible=False), gr.Button(visible=False))
|
| 983 |
|
| 984 |
play_event = play_btn.click(
|
| 985 |
stream_tts,
|
| 986 |
inputs=[tts_chunks_state, paragraphs_state, voice_profile_state],
|
| 987 |
outputs=[player_audio_control, player_status_bar, chunk_status, play_btn, pause_btn, ask_btn,
|
| 988 |
-
timeline_slider, story_text_display, story_finished_panel,
|
| 989 |
-
qa_panel, qa_input_group, resume_btn]
|
| 990 |
)
|
| 991 |
|
| 992 |
# 6. Pause button β PAUSED state
|
|
@@ -1033,46 +1030,43 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 1033 |
"""
|
| 1034 |
return (
|
| 1035 |
status_html,
|
| 1036 |
-
gr.Button(visible=False),
|
| 1037 |
-
gr.Button(visible=False),
|
| 1038 |
-
gr.Button(visible=False),
|
| 1039 |
-
gr.Button(visible=True),
|
| 1040 |
-
gr.HTML(
|
| 1041 |
-
gr.
|
| 1042 |
-
gr.HTML(visible=False),
|
| 1043 |
-
gr.Audio(visible=False),
|
| 1044 |
)
|
| 1045 |
|
| 1046 |
ask_btn.click(
|
| 1047 |
enter_asking_state,
|
| 1048 |
inputs=[],
|
| 1049 |
-
outputs=[player_status_bar, play_btn, pause_btn, ask_btn, resume_btn,
|
| 1050 |
cancels=[play_event],
|
| 1051 |
)
|
| 1052 |
|
| 1053 |
# 9. Submit question β LFM2.5-Audio end-to-end (single model: audio-in β audio+text-out)
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 1065 |
-
)
|
| 1066 |
|
|
|
|
| 1067 |
if not result["ok"]:
|
| 1068 |
-
|
| 1069 |
<div style="padding: 10px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 10px; font-size: 12px; color: #991b1b;">
|
| 1070 |
Please type a question or record one with the microphone.
|
| 1071 |
</div>
|
| 1072 |
"""
|
| 1073 |
-
return gr.HTML(value=
|
| 1074 |
|
| 1075 |
-
answer_audio_path_out = result["audio_path"]
|
| 1076 |
safe_answer = html.escape(str(result["answer_text"]))
|
| 1077 |
safe_question = html.escape(str(result["display_question"]))
|
| 1078 |
answer_html = f"""
|
|
@@ -1086,23 +1080,51 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 1086 |
</div>
|
| 1087 |
</div>
|
| 1088 |
"""
|
|
|
|
| 1089 |
if answer_audio_path_out:
|
| 1090 |
return gr.HTML(value=answer_html, visible=True), gr.Audio(value=str(answer_audio_path_out), visible=True, autoplay=True)
|
| 1091 |
return gr.HTML(value=answer_html, visible=True), gr.Audio(visible=False)
|
| 1092 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1093 |
submit_question_btn.click(
|
| 1094 |
handle_question_submit,
|
| 1095 |
inputs=[question_text, question_audio, paragraphs_state, timeline_slider, voice_profile_state],
|
| 1096 |
outputs=[answer_display, answer_audio]
|
| 1097 |
)
|
| 1098 |
|
| 1099 |
-
# Auto-submit immediately when recording stops β
|
| 1100 |
def auto_submit_on_stop(audio_path, question_txt, paragraphs, slider_val, profile_id):
|
| 1101 |
if not audio_path:
|
| 1102 |
-
return
|
| 1103 |
if not claim_audio_submission(audio_path):
|
| 1104 |
-
return
|
| 1105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1106 |
|
| 1107 |
question_audio.stop_recording(
|
| 1108 |
auto_submit_on_stop,
|
|
@@ -1122,12 +1144,8 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 1122 |
status_html,
|
| 1123 |
gr.Button(visible=True), # play_btn
|
| 1124 |
gr.Button(visible=False), # pause_btn
|
| 1125 |
-
gr.Button(visible=True), # ask_btn
|
| 1126 |
gr.Button(visible=False), # resume_btn
|
| 1127 |
-
gr.HTML(visible=False), # qa_panel
|
| 1128 |
-
gr.Group(visible=False), # qa_input_group
|
| 1129 |
-
gr.HTML(visible=False), # answer_display
|
| 1130 |
-
gr.Audio(visible=False), # answer_audio
|
| 1131 |
gr.Textbox(value=""), # question_text β clear previous question
|
| 1132 |
None, # question_audio β clear previous recording
|
| 1133 |
)
|
|
@@ -1135,7 +1153,7 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
|
|
| 1135 |
resume_btn.click(
|
| 1136 |
enter_resume_state,
|
| 1137 |
inputs=[],
|
| 1138 |
-
outputs=[player_status_bar, play_btn, pause_btn, ask_btn, resume_btn,
|
| 1139 |
)
|
| 1140 |
|
| 1141 |
# 11. Pause playback when switching to other tabs
|
|
|
|
| 532 |
pause_btn = gr.Button("βΈ Pause", variant="secondary", scale=1, visible=False)
|
| 533 |
resume_btn = gr.Button("β©οΈ Resume Story", variant="secondary", scale=2, visible=False)
|
| 534 |
|
|
|
|
|
|
|
|
|
|
| 535 |
chunk_status = gr.HTML("""
|
| 536 |
<div style="margin-top: 8px; font-size: 10px; color: #64748b; font-family: monospace; text-align: center;">
|
| 537 |
Chunk 0 / 0
|
|
|
|
| 550 |
story_text_display = gr.HTML(visible=False)
|
| 551 |
timeline_slider = gr.Slider(minimum=0, maximum=100, step=1, value=0, label="π Story position (paragraph)", visible=False)
|
| 552 |
|
| 553 |
+
# Q&A Panel β always visible
|
| 554 |
qa_panel = gr.HTML("""
|
| 555 |
<div style="margin-top: 20px; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 16px;">
|
| 556 |
<h4 style="font-family: 'Playfair Display', Georgia, serif; font-size: 14px; margin-bottom: 12px; color: #FAF7F2;">β Ask About the Story</h4>
|
| 557 |
</div>
|
| 558 |
+
""")
|
| 559 |
|
| 560 |
+
with gr.Group() as qa_input_group:
|
| 561 |
question_audio = gr.Audio(
|
| 562 |
sources=["microphone"], type="filepath",
|
| 563 |
label="π€ Ask your question β answer generates when you stop recording",
|
|
|
|
| 565 |
format="wav",
|
| 566 |
elem_id="qa_audio_input",
|
| 567 |
)
|
| 568 |
+
question_text = gr.Textbox(
|
| 569 |
+
placeholder="β¦or type a question here",
|
| 570 |
+
label="Question (text fallback)",
|
| 571 |
+
)
|
| 572 |
submit_question_btn = gr.Button("π Get Answer", variant="primary", elem_id="qa_submit_btn")
|
| 573 |
|
| 574 |
+
answer_display = gr.HTML()
|
| 575 |
+
answer_audio = gr.Audio(label="Answer Audio", interactive=False, autoplay=True, show_download_button=False)
|
| 576 |
+
|
| 577 |
+
with gr.Row():
|
| 578 |
+
ask_btn = gr.Button("β Ask a Question", variant="primary", scale=2, visible=False)
|
| 579 |
|
| 580 |
# TAB 3: Clone Voice Studio
|
| 581 |
with gr.TabItem("ποΈ Clone Voice Studio") as clone_tab:
|
|
|
|
| 948 |
"<div style='text-align:center;font-size:10px;color:#64748b;font-family:monospace;margin-top:8px;'>No content loaded</div>",
|
| 949 |
gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
|
| 950 |
gr.Slider(value=0), render_story_text(paras, 0) if paras else "",
|
| 951 |
+
gr.HTML(visible=False), gr.Button(visible=False))
|
|
|
|
| 952 |
return
|
| 953 |
|
| 954 |
n = len(tts_chunks)
|
|
|
|
| 956 |
f"<div style='text-align:center;font-size:10px;color:#4ade80;font-family:monospace;margin-top:8px;'>βΆ Paragraph 1 / {n_paras}</div>",
|
| 957 |
gr.Button(visible=False), gr.Button(visible=True), gr.Button(visible=False),
|
| 958 |
gr.Slider(value=0), render_story_text(paras, 0) if paras else "",
|
| 959 |
+
gr.HTML(visible=False), gr.Button(visible=False))
|
|
|
|
| 960 |
|
| 961 |
for sample_rate, wav, i, total, err in generate_audio_stream(tts_chunks, voice_profile_id=profile_id):
|
|
|
|
| 962 |
para_idx = min(int(i * n_paras / total), n_paras - 1) if total > 0 else 0
|
| 963 |
if err:
|
| 964 |
yield (gr.update(), f"<div style='color:#ef4444;font-size:11px;'>Something went wrong. Try again.</div>", "",
|
| 965 |
gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
|
| 966 |
gr.Slider(value=para_idx), gr.update(), gr.HTML(visible=False),
|
| 967 |
+
gr.Button(visible=False))
|
| 968 |
return
|
| 969 |
para_idx = min(int(i * n_paras / total), n_paras - 1) if total > 0 else 0
|
| 970 |
is_last = (i + 1 >= total)
|
|
|
|
| 972 |
yield ((sample_rate, wav.astype(np.float32) if hasattr(wav, 'astype') else wav), _status_playing, para_status,
|
| 973 |
gr.Button(visible=False), gr.Button(visible=True), gr.Button(visible=True),
|
| 974 |
gr.Slider(value=para_idx), render_story_text(paras, para_idx),
|
| 975 |
+
gr.HTML(visible=is_last), gr.Button(visible=True))
|
|
|
|
| 976 |
|
| 977 |
yield (gr.update(), _status_done, f"<div style='text-align:center;font-size:10px;color:#94a3b8;font-family:monospace;margin-top:8px;'>β
Story complete</div>",
|
| 978 |
gr.Button(visible=True), gr.Button(visible=False), gr.Button(visible=False),
|
| 979 |
gr.Slider(value=max(n_paras - 1, 0)), render_story_text(paras, max(n_paras - 1, 0)),
|
| 980 |
+
gr.HTML(visible=True), gr.Button(visible=True))
|
|
|
|
| 981 |
|
| 982 |
play_event = play_btn.click(
|
| 983 |
stream_tts,
|
| 984 |
inputs=[tts_chunks_state, paragraphs_state, voice_profile_state],
|
| 985 |
outputs=[player_audio_control, player_status_bar, chunk_status, play_btn, pause_btn, ask_btn,
|
| 986 |
+
timeline_slider, story_text_display, story_finished_panel, resume_btn]
|
|
|
|
| 987 |
)
|
| 988 |
|
| 989 |
# 6. Pause button β PAUSED state
|
|
|
|
| 1030 |
"""
|
| 1031 |
return (
|
| 1032 |
status_html,
|
| 1033 |
+
gr.Button(visible=False), # play_btn
|
| 1034 |
+
gr.Button(visible=False), # pause_btn
|
| 1035 |
+
gr.Button(visible=False), # ask_btn
|
| 1036 |
+
gr.Button(visible=True), # resume_btn
|
| 1037 |
+
gr.HTML(value=""), # answer_display β clear previous answer
|
| 1038 |
+
gr.Audio(visible=False), # answer_audio
|
|
|
|
|
|
|
| 1039 |
)
|
| 1040 |
|
| 1041 |
ask_btn.click(
|
| 1042 |
enter_asking_state,
|
| 1043 |
inputs=[],
|
| 1044 |
+
outputs=[player_status_bar, play_btn, pause_btn, ask_btn, resume_btn, answer_display, answer_audio],
|
| 1045 |
cancels=[play_event],
|
| 1046 |
)
|
| 1047 |
|
| 1048 |
# 9. Submit question β LFM2.5-Audio end-to-end (single model: audio-in β audio+text-out)
|
| 1049 |
+
_GENERATING_HTML = """
|
| 1050 |
+
<div style="margin-top: 12px; padding: 20px 16px; background: rgba(245,132,31,0.08); border: 1px solid rgba(245,132,31,0.25); border-radius: 14px; text-align: center;">
|
| 1051 |
+
<div style="font-size: 28px; margin-bottom: 10px;">π§</div>
|
| 1052 |
+
<div style="font-size: 11px; color: #f5841f; font-family: monospace; font-weight: 700; letter-spacing: 1px;">GENERATING ANSWER...</div>
|
| 1053 |
+
<div style="margin-top: 10px; display: flex; justify-content: center; gap: 6px;">
|
| 1054 |
+
<span style="width: 7px; height: 7px; border-radius: 50%; background: #f5841f; display: inline-block; animation: pulse 1.2s ease-in-out infinite;"></span>
|
| 1055 |
+
<span style="width: 7px; height: 7px; border-radius: 50%; background: #f5841f; display: inline-block; animation: pulse 1.2s ease-in-out 0.3s infinite;"></span>
|
| 1056 |
+
<span style="width: 7px; height: 7px; border-radius: 50%; background: #f5841f; display: inline-block; animation: pulse 1.2s ease-in-out 0.6s infinite;"></span>
|
| 1057 |
+
</div>
|
| 1058 |
+
</div>
|
| 1059 |
+
"""
|
|
|
|
| 1060 |
|
| 1061 |
+
def _build_answer_outputs(result):
|
| 1062 |
if not result["ok"]:
|
| 1063 |
+
error_html = """
|
| 1064 |
<div style="padding: 10px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 10px; font-size: 12px; color: #991b1b;">
|
| 1065 |
Please type a question or record one with the microphone.
|
| 1066 |
</div>
|
| 1067 |
"""
|
| 1068 |
+
return gr.HTML(value=error_html, visible=True), gr.Audio(visible=False)
|
| 1069 |
|
|
|
|
| 1070 |
safe_answer = html.escape(str(result["answer_text"]))
|
| 1071 |
safe_question = html.escape(str(result["display_question"]))
|
| 1072 |
answer_html = f"""
|
|
|
|
| 1080 |
</div>
|
| 1081 |
</div>
|
| 1082 |
"""
|
| 1083 |
+
answer_audio_path_out = result["audio_path"]
|
| 1084 |
if answer_audio_path_out:
|
| 1085 |
return gr.HTML(value=answer_html, visible=True), gr.Audio(value=str(answer_audio_path_out), visible=True, autoplay=True)
|
| 1086 |
return gr.HTML(value=answer_html, visible=True), gr.Audio(visible=False)
|
| 1087 |
|
| 1088 |
+
def handle_question_submit(question_txt, question_audio_path, paragraphs, slider_val, profile_id):
|
| 1089 |
+
q_txt = (question_txt or "").strip()
|
| 1090 |
+
has_audio = question_audio_path is not None and question_audio_path != ""
|
| 1091 |
+
|
| 1092 |
+
yield gr.HTML(value=_GENERATING_HTML, visible=True), gr.Audio(visible=False)
|
| 1093 |
+
|
| 1094 |
+
result = build_qa_response(
|
| 1095 |
+
question_text=q_txt,
|
| 1096 |
+
question_audio_path=question_audio_path if has_audio else None,
|
| 1097 |
+
paragraphs=paragraphs,
|
| 1098 |
+
output_dir=SAMPLE_SOUNDS_DIR,
|
| 1099 |
+
answer_fn=answer_question_audio,
|
| 1100 |
+
max_new_tokens=512,
|
| 1101 |
+
)
|
| 1102 |
+
yield _build_answer_outputs(result)
|
| 1103 |
+
|
| 1104 |
submit_question_btn.click(
|
| 1105 |
handle_question_submit,
|
| 1106 |
inputs=[question_text, question_audio, paragraphs_state, timeline_slider, voice_profile_state],
|
| 1107 |
outputs=[answer_display, answer_audio]
|
| 1108 |
)
|
| 1109 |
|
| 1110 |
+
# Auto-submit immediately when recording stops β show waiting UI then run inference
|
| 1111 |
def auto_submit_on_stop(audio_path, question_txt, paragraphs, slider_val, profile_id):
|
| 1112 |
if not audio_path:
|
| 1113 |
+
return
|
| 1114 |
if not claim_audio_submission(audio_path):
|
| 1115 |
+
return
|
| 1116 |
+
|
| 1117 |
+
yield gr.HTML(value=_GENERATING_HTML, visible=True), gr.Audio(visible=False)
|
| 1118 |
+
|
| 1119 |
+
result = build_qa_response(
|
| 1120 |
+
question_text=(question_txt or "").strip(),
|
| 1121 |
+
question_audio_path=audio_path,
|
| 1122 |
+
paragraphs=paragraphs,
|
| 1123 |
+
output_dir=SAMPLE_SOUNDS_DIR,
|
| 1124 |
+
answer_fn=answer_question_audio,
|
| 1125 |
+
max_new_tokens=512,
|
| 1126 |
+
)
|
| 1127 |
+
yield _build_answer_outputs(result)
|
| 1128 |
|
| 1129 |
question_audio.stop_recording(
|
| 1130 |
auto_submit_on_stop,
|
|
|
|
| 1144 |
status_html,
|
| 1145 |
gr.Button(visible=True), # play_btn
|
| 1146 |
gr.Button(visible=False), # pause_btn
|
| 1147 |
+
gr.Button(visible=True), # ask_btn
|
| 1148 |
gr.Button(visible=False), # resume_btn
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1149 |
gr.Textbox(value=""), # question_text β clear previous question
|
| 1150 |
None, # question_audio β clear previous recording
|
| 1151 |
)
|
|
|
|
| 1153 |
resume_btn.click(
|
| 1154 |
enter_resume_state,
|
| 1155 |
inputs=[],
|
| 1156 |
+
outputs=[player_status_bar, play_btn, pause_btn, ask_btn, resume_btn, question_text, question_audio]
|
| 1157 |
)
|
| 1158 |
|
| 1159 |
# 11. Pause playback when switching to other tabs
|
inference_lfm.py
CHANGED
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
| 8 |
|
| 9 |
import logging
|
| 10 |
import os
|
|
|
|
| 11 |
import threading
|
| 12 |
import time
|
| 13 |
|
|
@@ -28,6 +29,9 @@ _model_lock = threading.Lock()
|
|
| 28 |
HF_REPO = "LiquidAI/LFM2.5-Audio-1.5B"
|
| 29 |
SAMPLE_RATE = 24000 # Mimi codec native rate (confirmed in official demo + library constants)
|
| 30 |
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
def _select_device() -> torch.device:
|
| 33 |
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
@@ -260,10 +264,11 @@ def answer_question_audio(
|
|
| 260 |
text_token_count, audio_frame_count, audio_frame_count / 12.5, gen_time,
|
| 261 |
)
|
| 262 |
|
| 263 |
-
# Decode text
|
| 264 |
answer_text = ""
|
| 265 |
if text_out:
|
| 266 |
-
|
|
|
|
| 267 |
|
| 268 |
# Decode audio via processor.decode (drops EOS frame)
|
| 269 |
waveform = None
|
|
|
|
| 8 |
|
| 9 |
import logging
|
| 10 |
import os
|
| 11 |
+
import re
|
| 12 |
import threading
|
| 13 |
import time
|
| 14 |
|
|
|
|
| 29 |
HF_REPO = "LiquidAI/LFM2.5-Audio-1.5B"
|
| 30 |
SAMPLE_RATE = 24000 # Mimi codec native rate (confirmed in official demo + library constants)
|
| 31 |
|
| 32 |
+
# Matches special boundary tokens like <|text_end|>, <|audio_start|> that leak into decoded text
|
| 33 |
+
_SPECIAL_TOKEN_RE = re.compile(r"<\|[^|>]+\|>")
|
| 34 |
+
|
| 35 |
|
| 36 |
def _select_device() -> torch.device:
|
| 37 |
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
|
|
| 264 |
text_token_count, audio_frame_count, audio_frame_count / 12.5, gen_time,
|
| 265 |
)
|
| 266 |
|
| 267 |
+
# Decode text β strip interleaved boundary special tokens that leak through
|
| 268 |
answer_text = ""
|
| 269 |
if text_out:
|
| 270 |
+
raw_text = "".join(processor.text.decode(t.detach().cpu()) for t in text_out)
|
| 271 |
+
answer_text = _SPECIAL_TOKEN_RE.sub("", raw_text).strip()
|
| 272 |
|
| 273 |
# Decode audio via processor.decode (drops EOS frame)
|
| 274 |
waveform = None
|