BrianIsaac commited on
Commit
e877e4d
·
1 Parent(s): e628e1f

fix: resolve audio integration issues and improve UI layout

Browse files

Fix multiple issues preventing audio features from working correctly:

Backend fixes:
- Remove incorrect await on ElevenLabs async generator in TTSService
- Remove incorrect await in DebateAudioGenerator._generate_segment
- Fix async generator pattern (use async for without await on generator call)

Frontend fixes:
- Fix handle_analysis to return and handle audio button in all yields
- Fix handle_build_portfolio to include audio button in all 4 yield statements
- Fix handle_compare_portfolio to include audio button in all 5 yield statements
- Handle both list and dict formats for portfolio_data in build result storage
- Add os import for file existence checks
- Wrap all audio players in Row containers for proper layout spacing
- Add debug logging to generate_debate_audio for troubleshooting

All three audio features (Analyse Portfolio, Build Portfolio, Compare Strategies) now work correctly with visible audio players.

Files changed (2) hide show
  1. app.py +59 -28
  2. backend/audio/tts_service.py +2 -2
app.py CHANGED
@@ -11,6 +11,7 @@ Features:
11
 
12
  import gradio as gr
13
  import asyncio
 
14
  import re
15
  import logging
16
  import warnings
@@ -979,7 +980,8 @@ async def run_analysis_with_ui_update(
979
  "input",
980
  f"❌ Error: {str(e)}",
981
  "",
982
- None, None, None, None, None
 
983
  )
984
 
985
 
@@ -2077,12 +2079,13 @@ def create_interface() -> gr.Blocks:
2077
  size="sm",
2078
  visible=False
2079
  )
2080
- build_audio_player = gr.Audio(
2081
- label="Portfolio Summary Audio",
2082
- interactive=False,
2083
- visible=False,
2084
- show_download_button=True
2085
- )
 
2086
 
2087
  with gr.Row():
2088
  build_regenerate_btn = gr.Button("Regenerate", variant="secondary", size="sm")
@@ -2155,12 +2158,13 @@ def create_interface() -> gr.Blocks:
2155
  size="sm",
2156
  visible=False
2157
  )
2158
- compare_audio_player = gr.Audio(
2159
- label="Advisory Council Debate Audio",
2160
- interactive=False,
2161
- visible=False,
2162
- show_download_button=True
2163
- )
 
2164
 
2165
  # Debate transcript
2166
  with gr.Accordion("View Full Debate", open=False):
@@ -2475,12 +2479,13 @@ def create_interface() -> gr.Blocks:
2475
  size="sm",
2476
  visible=False
2477
  )
2478
- analysis_audio_player = gr.Audio(
2479
- label="Audio Summary",
2480
- interactive=False,
2481
- visible=False,
2482
- show_download_button=True
2483
- )
 
2484
 
2485
  # Performance Metrics Accordion (progressive disclosure)
2486
  with gr.Accordion("Performance Metrics & Reasoning", open=False):
@@ -2901,6 +2906,13 @@ def create_interface() -> gr.Blocks:
2901
  """Generate multi-speaker debate audio on-demand."""
2902
  global LAST_DEBATE_DATA
2903
 
 
 
 
 
 
 
 
2904
  if not LAST_DEBATE_DATA:
2905
  logger.warning("No debate data available for audio generation")
2906
  return (
@@ -2947,6 +2959,8 @@ def create_interface() -> gr.Blocks:
2947
  )
2948
 
2949
  logger.info(f"Debate audio generated: {audio_path}")
 
 
2950
 
2951
  return (
2952
  gr.update(value=audio_path, visible=True),
@@ -2982,7 +2996,8 @@ def create_interface() -> gr.Blocks:
2982
  yield (
2983
  gr.update(value=[], visible=False), # build_agent_chat (empty, hidden)
2984
  gr.update(visible=True), # build_results_container
2985
- "Please select at least one investment goal." # build_status
 
2986
  )
2987
  return
2988
 
@@ -3029,7 +3044,8 @@ def create_interface() -> gr.Blocks:
3029
  yield (
3030
  gr.update(value=chat_messages, visible=True), # build_agent_chat (visible, growing list)
3031
  gr.update(visible=False), # build_results_container (hidden during streaming)
3032
- "" # build_status
 
3033
  )
3034
  logger.debug(f"UI update yielded successfully")
3035
  except Exception as e:
@@ -3052,9 +3068,17 @@ def create_interface() -> gr.Blocks:
3052
  final_message = chat_messages[-1]
3053
  if isinstance(final_message, dict) and "metadata" in final_message:
3054
  portfolio_data = final_message.get("metadata", {}).get("portfolio", {})
 
 
 
 
 
 
 
 
3055
  LAST_BUILD_RESULT = {
3056
  "summary": final_message.get("content", ""),
3057
- "holdings": portfolio_data.get("holdings", []),
3058
  "reasoning": final_message.get("metadata", {}).get("reasoning_trace", [])
3059
  }
3060
  logger.info("Build result stored for audio generation")
@@ -3092,7 +3116,8 @@ def create_interface() -> gr.Blocks:
3092
  yield (
3093
  gr.update(value=[], visible=False), # build_agent_chat (empty, hidden on error)
3094
  gr.update(visible=True), # build_results_container
3095
- f"Error building portfolio: {str(e)}" # build_status
 
3096
  )
3097
 
3098
  def handle_build_accept(portfolio_table):
@@ -3160,7 +3185,8 @@ def create_interface() -> gr.Blocks:
3160
  0, # compare_bear_confidence
3161
  "", # compare_consensus
3162
  "", # compare_stance
3163
- [] # compare_debate_transcript
 
3164
  )
3165
  return
3166
 
@@ -3172,7 +3198,8 @@ def create_interface() -> gr.Blocks:
3172
  gr.update(value=[], visible=False), # compare_debate_chat (empty, hidden)
3173
  gr.update(visible=True), # compare_results_container
3174
  "Could not parse portfolio. Please check format.", # compare_status
3175
- "", 0, "", 0, "", "", []
 
3176
  )
3177
  return
3178
 
@@ -3205,7 +3232,8 @@ def create_interface() -> gr.Blocks:
3205
  0, # compare_bear_confidence
3206
  "", # compare_consensus
3207
  "", # compare_stance
3208
- [] # compare_debate_transcript
 
3209
  )
3210
 
3211
  # Extract data from final consensus message
@@ -3264,7 +3292,8 @@ def create_interface() -> gr.Blocks:
3264
  gr.update(value=[], visible=False), # compare_debate_chat (empty, hidden on error)
3265
  gr.update(visible=True), # compare_results_container
3266
  f"Error: {str(e)}", # compare_status
3267
- "", 0, "", 0, "", "", []
 
3268
  )
3269
 
3270
  async def handle_test_changes(portfolio_text, changes_text, portfolio_value, session_state):
@@ -4068,7 +4097,7 @@ Please try again with different parameters.
4068
  }
4069
 
4070
  # Run analysis with progress updates
4071
- page, analysis, perf_metrics, alloc, risk, perf, corr, opt = await run_analysis_with_ui_update(
4072
  session_state, portfolio_text, roast_mode, persona, progress
4073
  )
4074
 
@@ -4087,6 +4116,7 @@ Please try again with different parameters.
4087
  performance_plot: perf,
4088
  correlation_plot: corr,
4089
  optimization_plot: opt,
 
4090
  load_past_portfolio_dropdown: gr.update(choices=dropdown_choices) if dropdown_choices else gr.skip(),
4091
  export_pdf_btn: LAST_EXPORT_PDF_PATH,
4092
  export_csv_btn: LAST_EXPORT_CSV_PATH
@@ -4104,6 +4134,7 @@ Please try again with different parameters.
4104
  performance_plot: None,
4105
  correlation_plot: None,
4106
  optimization_plot: None,
 
4107
  load_past_portfolio_dropdown: gr.skip(),
4108
  export_pdf_btn: gr.skip(),
4109
  export_csv_btn: gr.skip()
 
11
 
12
  import gradio as gr
13
  import asyncio
14
+ import os
15
  import re
16
  import logging
17
  import warnings
 
980
  "input",
981
  f"❌ Error: {str(e)}",
982
  "",
983
+ None, None, None, None, None,
984
+ gr.update(visible=False) # analysis_audio_btn (hide on error)
985
  )
986
 
987
 
 
2079
  size="sm",
2080
  visible=False
2081
  )
2082
+ with gr.Row():
2083
+ build_audio_player = gr.Audio(
2084
+ label="Portfolio Summary Audio",
2085
+ interactive=False,
2086
+ visible=False,
2087
+ show_download_button=True
2088
+ )
2089
 
2090
  with gr.Row():
2091
  build_regenerate_btn = gr.Button("Regenerate", variant="secondary", size="sm")
 
2158
  size="sm",
2159
  visible=False
2160
  )
2161
+ with gr.Row():
2162
+ compare_audio_player = gr.Audio(
2163
+ label="Advisory Council Debate Audio",
2164
+ interactive=False,
2165
+ visible=False,
2166
+ show_download_button=True
2167
+ )
2168
 
2169
  # Debate transcript
2170
  with gr.Accordion("View Full Debate", open=False):
 
2479
  size="sm",
2480
  visible=False
2481
  )
2482
+ with gr.Row():
2483
+ analysis_audio_player = gr.Audio(
2484
+ label="Audio Summary",
2485
+ interactive=False,
2486
+ visible=False,
2487
+ show_download_button=True
2488
+ )
2489
 
2490
  # Performance Metrics Accordion (progressive disclosure)
2491
  with gr.Accordion("Performance Metrics & Reasoning", open=False):
 
2906
  """Generate multi-speaker debate audio on-demand."""
2907
  global LAST_DEBATE_DATA
2908
 
2909
+ logger.info(f"generate_debate_audio called. LAST_DEBATE_DATA exists: {LAST_DEBATE_DATA is not None}")
2910
+ if LAST_DEBATE_DATA:
2911
+ logger.info(f"Debate data keys: {LAST_DEBATE_DATA.keys()}")
2912
+ logger.info(f"Bull case length: {len(LAST_DEBATE_DATA.get('bull_case', ''))}")
2913
+ logger.info(f"Bear case length: {len(LAST_DEBATE_DATA.get('bear_case', ''))}")
2914
+ logger.info(f"Consensus length: {len(LAST_DEBATE_DATA.get('consensus', ''))}")
2915
+
2916
  if not LAST_DEBATE_DATA:
2917
  logger.warning("No debate data available for audio generation")
2918
  return (
 
2959
  )
2960
 
2961
  logger.info(f"Debate audio generated: {audio_path}")
2962
+ logger.info(f"Audio file exists: {os.path.exists(audio_path) if audio_path else False}")
2963
+ logger.info(f"Returning audio player update with visible=True")
2964
 
2965
  return (
2966
  gr.update(value=audio_path, visible=True),
 
2996
  yield (
2997
  gr.update(value=[], visible=False), # build_agent_chat (empty, hidden)
2998
  gr.update(visible=True), # build_results_container
2999
+ "Please select at least one investment goal.", # build_status
3000
+ gr.update(visible=False) # build_audio_btn (hide on error)
3001
  )
3002
  return
3003
 
 
3044
  yield (
3045
  gr.update(value=chat_messages, visible=True), # build_agent_chat (visible, growing list)
3046
  gr.update(visible=False), # build_results_container (hidden during streaming)
3047
+ "", # build_status
3048
+ gr.update(visible=False) # build_audio_btn (hide during streaming)
3049
  )
3050
  logger.debug(f"UI update yielded successfully")
3051
  except Exception as e:
 
3068
  final_message = chat_messages[-1]
3069
  if isinstance(final_message, dict) and "metadata" in final_message:
3070
  portfolio_data = final_message.get("metadata", {}).get("portfolio", {})
3071
+ # Handle both dict and list formats
3072
+ if isinstance(portfolio_data, list):
3073
+ holdings = portfolio_data
3074
+ elif isinstance(portfolio_data, dict):
3075
+ holdings = portfolio_data.get("holdings", [])
3076
+ else:
3077
+ holdings = []
3078
+
3079
  LAST_BUILD_RESULT = {
3080
  "summary": final_message.get("content", ""),
3081
+ "holdings": holdings,
3082
  "reasoning": final_message.get("metadata", {}).get("reasoning_trace", [])
3083
  }
3084
  logger.info("Build result stored for audio generation")
 
3116
  yield (
3117
  gr.update(value=[], visible=False), # build_agent_chat (empty, hidden on error)
3118
  gr.update(visible=True), # build_results_container
3119
+ f"Error building portfolio: {str(e)}", # build_status
3120
+ gr.update(visible=False) # build_audio_btn (hide on error)
3121
  )
3122
 
3123
  def handle_build_accept(portfolio_table):
 
3185
  0, # compare_bear_confidence
3186
  "", # compare_consensus
3187
  "", # compare_stance
3188
+ [], # compare_debate_transcript
3189
+ gr.update(visible=False) # compare_audio_btn (hide on error)
3190
  )
3191
  return
3192
 
 
3198
  gr.update(value=[], visible=False), # compare_debate_chat (empty, hidden)
3199
  gr.update(visible=True), # compare_results_container
3200
  "Could not parse portfolio. Please check format.", # compare_status
3201
+ "", 0, "", 0, "", "", [],
3202
+ gr.update(visible=False) # compare_audio_btn (hide on error)
3203
  )
3204
  return
3205
 
 
3232
  0, # compare_bear_confidence
3233
  "", # compare_consensus
3234
  "", # compare_stance
3235
+ [], # compare_debate_transcript
3236
+ gr.update(visible=False) # compare_audio_btn (hide during streaming)
3237
  )
3238
 
3239
  # Extract data from final consensus message
 
3292
  gr.update(value=[], visible=False), # compare_debate_chat (empty, hidden on error)
3293
  gr.update(visible=True), # compare_results_container
3294
  f"Error: {str(e)}", # compare_status
3295
+ "", 0, "", 0, "", "", [],
3296
+ gr.update(visible=False) # compare_audio_btn (hide on error)
3297
  )
3298
 
3299
  async def handle_test_changes(portfolio_text, changes_text, portfolio_value, session_state):
 
4097
  }
4098
 
4099
  # Run analysis with progress updates
4100
+ page, analysis, perf_metrics, alloc, risk, perf, corr, opt, audio_btn = await run_analysis_with_ui_update(
4101
  session_state, portfolio_text, roast_mode, persona, progress
4102
  )
4103
 
 
4116
  performance_plot: perf,
4117
  correlation_plot: corr,
4118
  optimization_plot: opt,
4119
+ analysis_audio_btn: audio_btn,
4120
  load_past_portfolio_dropdown: gr.update(choices=dropdown_choices) if dropdown_choices else gr.skip(),
4121
  export_pdf_btn: LAST_EXPORT_PDF_PATH,
4122
  export_csv_btn: LAST_EXPORT_CSV_PATH
 
4134
  performance_plot: None,
4135
  correlation_plot: None,
4136
  optimization_plot: None,
4137
+ analysis_audio_btn: audio_btn,
4138
  load_past_portfolio_dropdown: gr.skip(),
4139
  export_pdf_btn: gr.skip(),
4140
  export_csv_btn: gr.skip()
backend/audio/tts_service.py CHANGED
@@ -63,7 +63,7 @@ class TTSService:
63
  logger.info(f"Generating audio: {len(text)} characters")
64
 
65
  try:
66
- audio_generator = await self.client.text_to_speech.convert(
67
  text=text,
68
  voice_id=voice_id or self.default_voice_id,
69
  model_id=model,
@@ -277,7 +277,7 @@ class DebateAudioGenerator:
277
  Returns:
278
  Audio data as bytes
279
  """
280
- audio_generator = await self.client.text_to_speech.convert(
281
  text=text,
282
  voice_id=voice_id,
283
  model_id="eleven_multilingual_v2",
 
63
  logger.info(f"Generating audio: {len(text)} characters")
64
 
65
  try:
66
+ audio_generator = self.client.text_to_speech.convert(
67
  text=text,
68
  voice_id=voice_id or self.default_voice_id,
69
  model_id=model,
 
277
  Returns:
278
  Audio data as bytes
279
  """
280
+ audio_generator = self.client.text_to_speech.convert(
281
  text=text,
282
  voice_id=voice_id,
283
  model_id="eleven_multilingual_v2",