sshtel commited on
Commit
fbb26dd
Β·
1 Parent(s): f30d5ae

fix to ui to visible

Browse files
Files changed (2) hide show
  1. app.py +73 -55
  2. 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
- """, visible=False)
562
 
563
- with gr.Group(visible=False) as qa_input_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(visible=False)
 
 
 
572
  submit_question_btn = gr.Button("πŸ” Get Answer", variant="primary", elem_id="qa_submit_btn")
573
 
574
- answer_display = gr.HTML(visible=False)
575
- answer_audio = gr.Audio(label="Answer Audio", interactive=False, visible=False, autoplay=True)
 
 
 
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.HTML(visible=False), gr.Group(visible=False), 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,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(visible=True),
1041
- gr.Group(visible=True),
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, qa_panel, qa_input_group, answer_display, answer_audio],
1050
  cancels=[play_event],
1051
  )
1052
 
1053
  # 9. Submit question β€” LFM2.5-Audio end-to-end (single model: audio-in β†’ audio+text-out)
1054
- def handle_question_submit(question_txt, question_audio_path, paragraphs, slider_val, profile_id):
1055
- q_txt = (question_txt or "").strip()
1056
- has_audio = question_audio_path is not None and question_audio_path != ""
1057
-
1058
- result = build_qa_response(
1059
- question_text=q_txt,
1060
- question_audio_path=question_audio_path if has_audio else None,
1061
- paragraphs=paragraphs,
1062
- output_dir=SAMPLE_SOUNDS_DIR,
1063
- answer_fn=answer_question_audio,
1064
- max_new_tokens=512,
1065
- )
1066
 
 
1067
  if not result["ok"]:
1068
- answer_html = """
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=answer_html, visible=True), gr.Audio(visible=False)
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 β€” directly calls inference
1100
  def auto_submit_on_stop(audio_path, question_txt, paragraphs, slider_val, profile_id):
1101
  if not audio_path:
1102
- return gr.update(), gr.update()
1103
  if not claim_audio_submission(audio_path):
1104
- return gr.update(), gr.update()
1105
- return handle_question_submit(question_txt, audio_path, paragraphs, slider_val, profile_id)
 
 
 
 
 
 
 
 
 
 
 
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 β€” keep visible so user can ask again
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, qa_panel, qa_input_group, answer_display, answer_audio, question_text, question_audio]
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
- answer_text = "".join(processor.text.decode(t.detach().cpu()) for t in text_out).strip()
 
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