minhahwang Copilot commited on
Commit
57efb6d
Β·
1 Parent(s): a51c3c3

fix: voice cards clickable - select cloned voice as narrator

Browse files

- Add onclick JS to voice cards to set voice_selector
- Add hidden voice_selector textbox + change handler
- Store profile_id in voice list when saving cloned voice
- Map card click β†’ voice_profile_state update

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

Files changed (1) hide show
  1. app.py +43 -4
app.py CHANGED
@@ -347,9 +347,21 @@ def render_cloned_voices_html(voices_list):
347
  status_bg = "#f0fdf4" if v["status"] == "ready" else "#fef3c7"
348
  safe_name = html.escape(v['name'])
349
  safe_desc = html.escape(v['description'])
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
  markup += f"""
352
- <div class="shelf-card" style="align-items: center;">
353
  <div style="width: 48px; height: 48px; border-radius: 50%; background: #ede8e3; border: 1px solid #ddc1b0; font-size: 24px; display: flex; align-items: center; justify-content: center; shrink-0: 0;">
354
  {v['avatar_url']}
355
  </div>
@@ -586,9 +598,11 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
586
  gr.HTML("""
587
  <div style="margin-top: 32px; border-top: 1px solid #ebdccb; padding-top: 24px;">
588
  <h4 class="serif-header" style="font-size: 18px; margin-bottom: 16px;">Currently Available Voices</h4>
 
589
  </div>
590
  """)
591
  cloned_voices_list_grid = gr.HTML(value=render_cloned_voices_html(mock_voices))
 
592
 
593
  # TAB 4: Profile
594
  with gr.TabItem("βš™οΈ Profile") as profile_tab:
@@ -803,7 +817,7 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
803
  )
804
 
805
  # 4. Add cloned voice to inventory
806
- def save_new_cloned_voice(v_name, v_gender, list_voices):
807
  avatar = "πŸ‘©" if v_gender == "Female" else "πŸ‘¨"
808
  new_voice_item = {
809
  "id": f"v-{time.time()}",
@@ -812,7 +826,8 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
812
  "description": f"Custom cloned {v_gender.lower()} companion voice. Highly optimized.",
813
  "gender": v_gender,
814
  "created_date": "2026-06-06",
815
- "status": "ready"
 
816
  }
817
  updated = [new_voice_item] + list_voices
818
  return (
@@ -827,10 +842,34 @@ with gr.Blocks(title="MomsVoice", css=css_code) as demo:
827
 
828
  add_to_library_btn.click(
829
  save_new_cloned_voice,
830
- inputs=[new_voice_name, new_voice_gender, voices_state],
831
  outputs=[voices_state, cloned_voices_list_grid, cloning_progress_msg, voice_cloning_success_panel, voice_sample_preview_widget, add_to_library_btn, new_voice_name]
832
  )
833
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
834
  # 5. Play button β€” streams TTS audio chunk by chunk
835
  def stream_tts(tts_chunks, paras, profile_id):
836
  _status_playing = """
 
347
  status_bg = "#f0fdf4" if v["status"] == "ready" else "#fef3c7"
348
  safe_name = html.escape(v['name'])
349
  safe_desc = html.escape(v['description'])
350
+ voice_id = html.escape(v.get('profile_id', v['id']))
351
+
352
+ onclick = f"""onclick="
353
+ const sel = document.querySelector('#voice_selector textarea, #voice_selector input');
354
+ if (sel) {{
355
+ sel.value = '{voice_id}';
356
+ sel.dispatchEvent(new Event('input', {{bubbles: true}}));
357
+ sel.dispatchEvent(new Event('change', {{bubbles: true}}));
358
+ }}
359
+ document.querySelectorAll('.voice-card-selected').forEach(c => c.classList.remove('voice-card-selected'));
360
+ this.classList.add('voice-card-selected');
361
+ " """
362
 
363
  markup += f"""
364
+ <div class="shelf-card" style="align-items: center; cursor: pointer;" {onclick}>
365
  <div style="width: 48px; height: 48px; border-radius: 50%; background: #ede8e3; border: 1px solid #ddc1b0; font-size: 24px; display: flex; align-items: center; justify-content: center; shrink-0: 0;">
366
  {v['avatar_url']}
367
  </div>
 
598
  gr.HTML("""
599
  <div style="margin-top: 32px; border-top: 1px solid #ebdccb; padding-top: 24px;">
600
  <h4 class="serif-header" style="font-size: 18px; margin-bottom: 16px;">Currently Available Voices</h4>
601
+ <p style="font-size: 11px; color: #6f6257; margin-bottom: 12px;">Click a voice card to select it as the narrator.</p>
602
  </div>
603
  """)
604
  cloned_voices_list_grid = gr.HTML(value=render_cloned_voices_html(mock_voices))
605
+ voice_selector = gr.Textbox(visible=False, elem_id="voice_selector")
606
 
607
  # TAB 4: Profile
608
  with gr.TabItem("βš™οΈ Profile") as profile_tab:
 
817
  )
818
 
819
  # 4. Add cloned voice to inventory
820
+ def save_new_cloned_voice(v_name, v_gender, list_voices, current_profile_id):
821
  avatar = "πŸ‘©" if v_gender == "Female" else "πŸ‘¨"
822
  new_voice_item = {
823
  "id": f"v-{time.time()}",
 
826
  "description": f"Custom cloned {v_gender.lower()} companion voice. Highly optimized.",
827
  "gender": v_gender,
828
  "created_date": "2026-06-06",
829
+ "status": "ready",
830
+ "profile_id": current_profile_id,
831
  }
832
  updated = [new_voice_item] + list_voices
833
  return (
 
842
 
843
  add_to_library_btn.click(
844
  save_new_cloned_voice,
845
+ inputs=[new_voice_name, new_voice_gender, voices_state, voice_profile_state],
846
  outputs=[voices_state, cloned_voices_list_grid, cloning_progress_msg, voice_cloning_success_panel, voice_sample_preview_widget, add_to_library_btn, new_voice_name]
847
  )
848
 
849
+ # Voice card selection β€” update voice_profile_state when a card is clicked
850
+ def handle_voice_select(selected_id, voices):
851
+ """Map selected voice ID to profile_id for TTS."""
852
+ if not selected_id:
853
+ return gr.update()
854
+ # Check if it matches a saved profile ID directly
855
+ from voice_clone import has_profile
856
+ if has_profile(selected_id):
857
+ return selected_id
858
+ # Check if it matches a voice in the list with a profile_id
859
+ for v in voices:
860
+ if v.get('profile_id') == selected_id or v.get('id') == selected_id:
861
+ pid = v.get('profile_id')
862
+ if pid and has_profile(pid):
863
+ return pid
864
+ # No valid profile β€” use stock voice (None)
865
+ return None
866
+
867
+ voice_selector.change(
868
+ handle_voice_select,
869
+ inputs=[voice_selector, voices_state],
870
+ outputs=[voice_profile_state]
871
+ )
872
+
873
  # 5. Play button β€” streams TTS audio chunk by chunk
874
  def stream_tts(tts_chunks, paras, profile_id):
875
  _status_playing = """