github-actions[bot] commited on
Commit
ea7dc28
·
1 Parent(s): 62f0a86

Sync from GitHub 568a6a97b4e0075c2edef972568daa49f4f256c0

Browse files
frontend/app.py CHANGED
@@ -1,5 +1,6 @@
1
  from __future__ import annotations
2
  import os
 
3
  from typing import Any
4
 
5
  import requests
@@ -693,6 +694,12 @@ elif page == "Notebooks":
693
  if ok and isinstance(artifact_result, list):
694
  artifacts = artifact_result
695
  if artifacts:
 
 
 
 
 
 
696
  st.dataframe(artifacts, use_container_width=True)
697
  artifact_options = {
698
  f"{a['id']} - {a.get('type', 'unknown')} - {a.get('status', '')}": a
@@ -756,6 +763,19 @@ elif page == "Notebooks":
756
  st.info(f"Podcast status: {artifact_status}")
757
  else:
758
  st.info("Select an artifact to preview.")
 
 
 
 
 
 
 
 
 
 
 
 
 
759
  else:
760
  st.info("No artifacts generated yet.")
761
  else:
 
1
  from __future__ import annotations
2
  import os
3
+ import time
4
  from typing import Any
5
 
6
  import requests
 
694
  if ok and isinstance(artifact_result, list):
695
  artifacts = artifact_result
696
  if artifacts:
697
+ auto_refresh_key = f"auto_refresh_artifacts_{selected_notebook_id}"
698
+ auto_refresh = st.checkbox(
699
+ "Auto-refresh while artifacts are processing",
700
+ value=bool(st.session_state.get(auto_refresh_key, True)),
701
+ key=auto_refresh_key,
702
+ )
703
  st.dataframe(artifacts, use_container_width=True)
704
  artifact_options = {
705
  f"{a['id']} - {a.get('type', 'unknown')} - {a.get('status', '')}": a
 
763
  st.info(f"Podcast status: {artifact_status}")
764
  else:
765
  st.info("Select an artifact to preview.")
766
+
767
+ in_flight = sum(
768
+ 1
769
+ for a in artifacts
770
+ if str(a.get("status", "")).lower() in {"pending", "processing"}
771
+ )
772
+ if auto_refresh and in_flight > 0:
773
+ st.caption(
774
+ f"{in_flight} artifact(s) still processing. "
775
+ "Refreshing in 4 seconds..."
776
+ )
777
+ time.sleep(4)
778
+ st.rerun()
779
  else:
780
  st.info("No artifacts generated yet.")
781
  else:
src/artifacts/podcast_generator.py CHANGED
@@ -426,6 +426,26 @@ IMPORTANT:
426
  - Make it sound like a real conversation, not a lecture
427
  """
428
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  def _synthesize_segments(
430
  self,
431
  script: List[Dict[str, str]],
@@ -477,6 +497,9 @@ IMPORTANT:
477
  )
478
  self._last_tts_errors.append(error_detail)
479
  print(f" ⚠️ Failed {error_detail}")
 
 
 
480
  continue
481
 
482
  return audio_files
 
426
  - Make it sound like a real conversation, not a lecture
427
  """
428
 
429
+ @staticmethod
430
+ def _is_fatal_tts_error(exc: Exception) -> bool:
431
+ """
432
+ Detect provider/configuration errors where retrying further segments is pointless.
433
+ """
434
+ text = " ".join(str(exc).lower().split())
435
+ fatal_markers = [
436
+ "voice_not_found",
437
+ "no compatible elevenlabs synthesis method found",
438
+ "invalid_api_key",
439
+ "unauthorized",
440
+ "authentication",
441
+ "forbidden",
442
+ "insufficient_credits",
443
+ "quota",
444
+ "status_code: 401",
445
+ "status_code: 403",
446
+ ]
447
+ return any(marker in text for marker in fatal_markers)
448
+
449
  def _synthesize_segments(
450
  self,
451
  script: List[Dict[str, str]],
 
497
  )
498
  self._last_tts_errors.append(error_detail)
499
  print(f" ⚠️ Failed {error_detail}")
500
+ if self._is_fatal_tts_error(e):
501
+ print(" ⛔ Fatal TTS configuration/provider error detected. Stopping remaining segments.")
502
+ break
503
  continue
504
 
505
  return audio_files
src/artifacts/tts_adapter.py CHANGED
@@ -12,6 +12,20 @@ load_dotenv()
12
  # TTS Provider type
13
  TTSProvider = Literal["openai", "elevenlabs", "edge"]
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  class TTSAdapter(ABC):
17
  """Base class for TTS providers."""
@@ -76,13 +90,27 @@ class ElevenLabsTTS(TTSAdapter):
76
 
77
  def _load_voice_aliases(self) -> dict[str, str]:
78
  """Best-effort map of configured voice names to voice IDs."""
 
 
 
79
  try:
80
- response = self.client.voices.get_all()
 
 
 
 
 
 
 
 
 
 
 
 
81
  voices = getattr(response, "voices", response)
82
  except Exception:
83
- return {}
84
 
85
- aliases: dict[str, str] = {}
86
  for voice in voices or []:
87
  if isinstance(voice, dict):
88
  name = voice.get("name")
 
12
  # TTS Provider type
13
  TTSProvider = Literal["openai", "elevenlabs", "edge"]
14
 
15
+ # Common ElevenLabs preset voice name -> voice_id mapping.
16
+ # This allows env values like "Rachel"/"Antoni" to work with SDK methods that require voice_id.
17
+ ELEVENLABS_PRESET_VOICE_IDS: dict[str, str] = {
18
+ "rachel": "21m00Tcm4TlvDq8ikWAM",
19
+ "domi": "AZnzlk1XvdvUeBnXmlld",
20
+ "bella": "EXAVITQu4vr4xnSDxMaL",
21
+ "antoni": "ErXwobaYiN019PkySvjV",
22
+ "elli": "MF3mGyEYCl7XYWbV9V6O",
23
+ "josh": "TxGEqnHWrfWFTfGW9XjX",
24
+ "arnold": "VR6AewLTigWG4xSOukaG",
25
+ "adam": "pNInz6obpgDQGcFmaJgB",
26
+ "sam": "yoZ06aMxZJJ28mfd3POQ",
27
+ }
28
+
29
 
30
  class TTSAdapter(ABC):
31
  """Base class for TTS providers."""
 
90
 
91
  def _load_voice_aliases(self) -> dict[str, str]:
92
  """Best-effort map of configured voice names to voice IDs."""
93
+ aliases: dict[str, str] = dict(ELEVENLABS_PRESET_VOICE_IDS)
94
+
95
+ # First try the latest SDK shape.
96
  try:
97
+ voices_api = getattr(self.client, "voices", None)
98
+ if voices_api is None:
99
+ return aliases
100
+
101
+ if hasattr(voices_api, "get_all"):
102
+ response = voices_api.get_all()
103
+ elif hasattr(voices_api, "search"):
104
+ response = voices_api.search()
105
+ elif hasattr(voices_api, "list"):
106
+ response = voices_api.list()
107
+ else:
108
+ return aliases
109
+
110
  voices = getattr(response, "voices", response)
111
  except Exception:
112
+ return aliases
113
 
 
114
  for voice in voices or []:
115
  if isinstance(voice, dict):
116
  name = voice.get("name")