Update app.py
Browse files
app.py
CHANGED
|
@@ -980,9 +980,8 @@ voice_conversation_state = {
|
|
| 980 |
# return f"β Error: {str(e)}"
|
| 981 |
|
| 982 |
def generate_podcast_ui(doc_ids, style, duration, voice1, voice2):
|
| 983 |
-
"""UI wrapper for podcast generation"""
|
| 984 |
try:
|
| 985 |
-
# Add detailed logging
|
| 986 |
logger.info(f"generate_podcast_ui called with:")
|
| 987 |
logger.info(f" doc_ids: {doc_ids} (type: {type(doc_ids)})")
|
| 988 |
logger.info(f" style: {style}")
|
|
@@ -990,22 +989,18 @@ def generate_podcast_ui(doc_ids, style, duration, voice1, voice2):
|
|
| 990 |
logger.info(f" voice1: {voice1}")
|
| 991 |
logger.info(f" voice2: {voice2}")
|
| 992 |
|
| 993 |
-
# Handle various input formats
|
| 994 |
if doc_ids is None:
|
| 995 |
logger.warning("doc_ids is None")
|
| 996 |
return ("β οΈ Please select at least one document", None, "No documents selected", "")
|
| 997 |
|
| 998 |
-
# Convert to list if needed
|
| 999 |
if isinstance(doc_ids, str):
|
| 1000 |
logger.info(f"Converting string doc_id to list: {doc_ids}")
|
| 1001 |
doc_ids = [doc_ids]
|
| 1002 |
|
| 1003 |
-
# Check if empty
|
| 1004 |
if not doc_ids or len(doc_ids) == 0:
|
| 1005 |
logger.warning(f"doc_ids is empty or has length 0: {doc_ids}")
|
| 1006 |
return ("β οΈ Please select at least one document", None, "No documents selected", "")
|
| 1007 |
|
| 1008 |
-
# Filter out None or empty string values
|
| 1009 |
doc_ids = [doc_id for doc_id in doc_ids if doc_id and doc_id.strip()]
|
| 1010 |
|
| 1011 |
if not doc_ids:
|
|
@@ -1094,6 +1089,105 @@ def load_dashboard_stats():
|
|
| 1094 |
logger.error(f"Error loading dashboard stats: {str(e)}")
|
| 1095 |
return (0, 0, 0.0, [], "β Error", "β Error", "β Error")
|
| 1096 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1097 |
def create_gradio_interface():
|
| 1098 |
custom_theme = gr.themes.Soft(
|
| 1099 |
primary_hue=gr.themes.colors.indigo,
|
|
@@ -1530,40 +1624,34 @@ def create_gradio_interface():
|
|
| 1530 |
gr.Markdown("""
|
| 1531 |
# ποΈ AI Podcast Studio
|
| 1532 |
## Transform Documents into Engaging Audio
|
| 1533 |
-
|
| 1534 |
Convert your documents into professional podcast conversations with AI-generated voices.
|
| 1535 |
-
|
| 1536 |
### How It Works:
|
| 1537 |
1. **Select Documents** - Choose 1-5 documents from your library
|
| 1538 |
2. **Choose Style** - Pick conversation style (casual, educational, etc.)
|
| 1539 |
3. **Set Duration** - Select podcast length (5-30 minutes)
|
| 1540 |
4. **Select Voices** - Pick two AI hosts from available voices
|
| 1541 |
5. **Generate** - AI creates natural dialogue discussing your content
|
| 1542 |
-
|
| 1543 |
### Powered By:
|
| 1544 |
- π΅ **ElevenLabs AI** - Ultra-realistic voice synthesis
|
| 1545 |
- π€ **LLM** - Intelligent content analysis and script generation
|
| 1546 |
- π **RAG** - Context-aware information retrieval
|
| 1547 |
-
|
| 1548 |
---
|
| 1549 |
""")
|
| 1550 |
-
|
| 1551 |
with gr.Row():
|
| 1552 |
with gr.Column(scale=2):
|
| 1553 |
# Configuration Panel
|
| 1554 |
with gr.Group():
|
| 1555 |
gr.Markdown("#### π Select Content")
|
| 1556 |
|
| 1557 |
-
# IMPORTANT: CheckboxGroup that stores document IDs
|
| 1558 |
podcast_doc_selector = gr.CheckboxGroup(
|
| 1559 |
-
choices=get_document_choices(),
|
| 1560 |
label="Documents to Include",
|
| 1561 |
info="Choose 1-5 documents for best results",
|
| 1562 |
interactive=True,
|
| 1563 |
-
value=[]
|
| 1564 |
)
|
| 1565 |
|
| 1566 |
-
# Debug display (optional - remove in production)
|
| 1567 |
gr.Markdown("*Selected document IDs will be used for podcast generation*")
|
| 1568 |
|
| 1569 |
with gr.Accordion("π¨ Podcast Settings", open=True):
|
|
@@ -1616,26 +1704,25 @@ def create_gradio_interface():
|
|
| 1616 |
)
|
| 1617 |
|
| 1618 |
with gr.Column(scale=3):
|
| 1619 |
-
# Output Panel
|
| 1620 |
with gr.Group():
|
| 1621 |
-
gr.Markdown("#### π΅ Generated Podcast")
|
| 1622 |
|
| 1623 |
podcast_audio_player = gr.Audio(
|
| 1624 |
-
label="podcast_audio_player",
|
| 1625 |
type="filepath",
|
| 1626 |
interactive=False,
|
| 1627 |
autoplay=True,
|
| 1628 |
-
container=False
|
| 1629 |
show_label=False
|
| 1630 |
)
|
| 1631 |
-
gr.DownloadButton("πΎ Download MP3", value=podcast_audio_player)
|
| 1632 |
|
| 1633 |
with gr.Accordion("π Transcript", open=False):
|
| 1634 |
podcast_transcript_display = gr.Markdown(
|
| 1635 |
value="*Transcript will appear after generation...*"
|
| 1636 |
)
|
| 1637 |
|
| 1638 |
-
|
|
|
|
|
|
|
| 1639 |
generate_podcast_btn.click(
|
| 1640 |
fn=generate_podcast_ui,
|
| 1641 |
inputs=[
|
|
@@ -1651,6 +1738,30 @@ def create_gradio_interface():
|
|
| 1651 |
podcast_transcript_display,
|
| 1652 |
podcast_id_display
|
| 1653 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1654 |
)
|
| 1655 |
|
| 1656 |
with gr.Tab("β Ask Questions"):
|
|
|
|
| 980 |
# return f"β Error: {str(e)}"
|
| 981 |
|
| 982 |
def generate_podcast_ui(doc_ids, style, duration, voice1, voice2):
|
| 983 |
+
"""UI wrapper for podcast generation (EXISTING FUNCTION - keep as is)"""
|
| 984 |
try:
|
|
|
|
| 985 |
logger.info(f"generate_podcast_ui called with:")
|
| 986 |
logger.info(f" doc_ids: {doc_ids} (type: {type(doc_ids)})")
|
| 987 |
logger.info(f" style: {style}")
|
|
|
|
| 989 |
logger.info(f" voice1: {voice1}")
|
| 990 |
logger.info(f" voice2: {voice2}")
|
| 991 |
|
|
|
|
| 992 |
if doc_ids is None:
|
| 993 |
logger.warning("doc_ids is None")
|
| 994 |
return ("β οΈ Please select at least one document", None, "No documents selected", "")
|
| 995 |
|
|
|
|
| 996 |
if isinstance(doc_ids, str):
|
| 997 |
logger.info(f"Converting string doc_id to list: {doc_ids}")
|
| 998 |
doc_ids = [doc_ids]
|
| 999 |
|
|
|
|
| 1000 |
if not doc_ids or len(doc_ids) == 0:
|
| 1001 |
logger.warning(f"doc_ids is empty or has length 0: {doc_ids}")
|
| 1002 |
return ("β οΈ Please select at least one document", None, "No documents selected", "")
|
| 1003 |
|
|
|
|
| 1004 |
doc_ids = [doc_id for doc_id in doc_ids if doc_id and doc_id.strip()]
|
| 1005 |
|
| 1006 |
if not doc_ids:
|
|
|
|
| 1089 |
logger.error(f"Error loading dashboard stats: {str(e)}")
|
| 1090 |
return (0, 0, 0.0, [], "β Error", "β Error", "β Error")
|
| 1091 |
|
| 1092 |
+
# ADD THESE HELPER FUNCTIONS BEFORE create_gradio_interface():
|
| 1093 |
+
def load_podcast_library_ui():
|
| 1094 |
+
"""Load and display podcast library with audio players"""
|
| 1095 |
+
try:
|
| 1096 |
+
result = mcp_server.list_podcasts_sync(limit=50)
|
| 1097 |
+
|
| 1098 |
+
if not result.get("success"):
|
| 1099 |
+
return (
|
| 1100 |
+
gr.Column(visible=False),
|
| 1101 |
+
f"β Failed to load podcasts: {result.get('error', 'Unknown error')}"
|
| 1102 |
+
)
|
| 1103 |
+
|
| 1104 |
+
podcasts = result.get("podcasts", [])
|
| 1105 |
+
|
| 1106 |
+
if not podcasts:
|
| 1107 |
+
return (
|
| 1108 |
+
gr.Column(visible=False),
|
| 1109 |
+
"π No podcasts generated yet. Create your first podcast above!"
|
| 1110 |
+
)
|
| 1111 |
+
|
| 1112 |
+
# Build the library UI dynamically
|
| 1113 |
+
with gr.Column(visible=True) as library_col:
|
| 1114 |
+
for idx, podcast in enumerate(podcasts, 1):
|
| 1115 |
+
with gr.Group():
|
| 1116 |
+
with gr.Row():
|
| 1117 |
+
# Column 1: Podcast Info (40%)
|
| 1118 |
+
with gr.Column(scale=2):
|
| 1119 |
+
gr.Markdown(f"### ποΈ Podcast #{idx}")
|
| 1120 |
+
|
| 1121 |
+
# Extract document names from metadata
|
| 1122 |
+
doc_names = []
|
| 1123 |
+
doc_ids = [] # Initialize here to avoid reference error
|
| 1124 |
+
|
| 1125 |
+
if podcast.get("metadata"):
|
| 1126 |
+
doc_ids = podcast["metadata"].get("document_ids", [])
|
| 1127 |
+
# Get document names
|
| 1128 |
+
for doc_id in doc_ids:
|
| 1129 |
+
try:
|
| 1130 |
+
doc = mcp_server.run_async(
|
| 1131 |
+
mcp_server.document_store.get_document(doc_id)
|
| 1132 |
+
)
|
| 1133 |
+
if doc:
|
| 1134 |
+
doc_names.append(doc.filename)
|
| 1135 |
+
except Exception as e:
|
| 1136 |
+
logger.warning(f"Could not fetch document {doc_id}: {e}")
|
| 1137 |
+
doc_names.append(f"Doc {doc_id[:8]}...")
|
| 1138 |
+
|
| 1139 |
+
# Display document names
|
| 1140 |
+
if doc_names:
|
| 1141 |
+
gr.Markdown(f"**π Documents:** {', '.join(doc_names)}")
|
| 1142 |
+
else:
|
| 1143 |
+
doc_count = len(doc_ids) if doc_ids else 'N/A'
|
| 1144 |
+
gr.Markdown(f"**π Documents:** {doc_count} document(s)")
|
| 1145 |
+
|
| 1146 |
+
# Podcast metadata
|
| 1147 |
+
style = podcast.get("metadata", {}).get("style", "Unknown")
|
| 1148 |
+
duration = podcast.get("metadata", {}).get("duration_minutes", "N/A")
|
| 1149 |
+
created = podcast.get("created_at", "Unknown")[:19] if podcast.get("created_at") else "Unknown"
|
| 1150 |
+
|
| 1151 |
+
gr.Markdown(
|
| 1152 |
+
f"**π¨ Style:** {style.title()} \n"
|
| 1153 |
+
f"**β±οΈ Duration:** ~{duration} min \n"
|
| 1154 |
+
f"**π
Created:** {created} \n"
|
| 1155 |
+
f"**π ID:** `{podcast['id'][:16]}...`"
|
| 1156 |
+
)
|
| 1157 |
+
|
| 1158 |
+
# Column 2: Audio Player (60%)
|
| 1159 |
+
with gr.Column(scale=3):
|
| 1160 |
+
audio_file = podcast.get("audio_file")
|
| 1161 |
+
|
| 1162 |
+
if audio_file and os.path.exists(audio_file):
|
| 1163 |
+
gr.Audio(
|
| 1164 |
+
value=audio_file,
|
| 1165 |
+
type="filepath",
|
| 1166 |
+
interactive=False,
|
| 1167 |
+
show_label=False,
|
| 1168 |
+
show_download_button=True,
|
| 1169 |
+
waveform_options={"show_controls": True}
|
| 1170 |
+
)
|
| 1171 |
+
else:
|
| 1172 |
+
gr.Markdown("β οΈ *Audio file not found*")
|
| 1173 |
+
if audio_file:
|
| 1174 |
+
gr.Markdown(f"*Expected path: {audio_file}*")
|
| 1175 |
+
|
| 1176 |
+
# Optional: Show transcript in accordion
|
| 1177 |
+
with gr.Accordion(f"π View Transcript", open=False):
|
| 1178 |
+
transcript = podcast.get("transcript", "Transcript not available")
|
| 1179 |
+
gr.Markdown(transcript)
|
| 1180 |
+
|
| 1181 |
+
status_msg = f"β
Loaded {len(podcasts)} podcast{'s' if len(podcasts) != 1 else ''}"
|
| 1182 |
+
return library_col, status_msg
|
| 1183 |
+
|
| 1184 |
+
except Exception as e:
|
| 1185 |
+
logger.error(f"Error loading podcast library: {str(e)}", exc_info=True)
|
| 1186 |
+
return (
|
| 1187 |
+
gr.Column(visible=False),
|
| 1188 |
+
f"β Error loading library: {str(e)}"
|
| 1189 |
+
)
|
| 1190 |
+
|
| 1191 |
def create_gradio_interface():
|
| 1192 |
custom_theme = gr.themes.Soft(
|
| 1193 |
primary_hue=gr.themes.colors.indigo,
|
|
|
|
| 1624 |
gr.Markdown("""
|
| 1625 |
# ποΈ AI Podcast Studio
|
| 1626 |
## Transform Documents into Engaging Audio
|
|
|
|
| 1627 |
Convert your documents into professional podcast conversations with AI-generated voices.
|
|
|
|
| 1628 |
### How It Works:
|
| 1629 |
1. **Select Documents** - Choose 1-5 documents from your library
|
| 1630 |
2. **Choose Style** - Pick conversation style (casual, educational, etc.)
|
| 1631 |
3. **Set Duration** - Select podcast length (5-30 minutes)
|
| 1632 |
4. **Select Voices** - Pick two AI hosts from available voices
|
| 1633 |
5. **Generate** - AI creates natural dialogue discussing your content
|
|
|
|
| 1634 |
### Powered By:
|
| 1635 |
- π΅ **ElevenLabs AI** - Ultra-realistic voice synthesis
|
| 1636 |
- π€ **LLM** - Intelligent content analysis and script generation
|
| 1637 |
- π **RAG** - Context-aware information retrieval
|
|
|
|
| 1638 |
---
|
| 1639 |
""")
|
| 1640 |
+
|
| 1641 |
with gr.Row():
|
| 1642 |
with gr.Column(scale=2):
|
| 1643 |
# Configuration Panel
|
| 1644 |
with gr.Group():
|
| 1645 |
gr.Markdown("#### π Select Content")
|
| 1646 |
|
|
|
|
| 1647 |
podcast_doc_selector = gr.CheckboxGroup(
|
| 1648 |
+
choices=get_document_choices(),
|
| 1649 |
label="Documents to Include",
|
| 1650 |
info="Choose 1-5 documents for best results",
|
| 1651 |
interactive=True,
|
| 1652 |
+
value=[]
|
| 1653 |
)
|
| 1654 |
|
|
|
|
| 1655 |
gr.Markdown("*Selected document IDs will be used for podcast generation*")
|
| 1656 |
|
| 1657 |
with gr.Accordion("π¨ Podcast Settings", open=True):
|
|
|
|
| 1704 |
)
|
| 1705 |
|
| 1706 |
with gr.Column(scale=3):
|
| 1707 |
+
# Output Panel - Latest Generated
|
| 1708 |
with gr.Group():
|
| 1709 |
+
gr.Markdown("#### π΅ Latest Generated Podcast")
|
| 1710 |
|
| 1711 |
podcast_audio_player = gr.Audio(
|
|
|
|
| 1712 |
type="filepath",
|
| 1713 |
interactive=False,
|
| 1714 |
autoplay=True,
|
|
|
|
| 1715 |
show_label=False
|
| 1716 |
)
|
|
|
|
| 1717 |
|
| 1718 |
with gr.Accordion("π Transcript", open=False):
|
| 1719 |
podcast_transcript_display = gr.Markdown(
|
| 1720 |
value="*Transcript will appear after generation...*"
|
| 1721 |
)
|
| 1722 |
|
| 1723 |
+
podcast_library_container = gr.Column()
|
| 1724 |
+
|
| 1725 |
+
# Event handler for generate button
|
| 1726 |
generate_podcast_btn.click(
|
| 1727 |
fn=generate_podcast_ui,
|
| 1728 |
inputs=[
|
|
|
|
| 1738 |
podcast_transcript_display,
|
| 1739 |
podcast_id_display
|
| 1740 |
]
|
| 1741 |
+
).then(
|
| 1742 |
+
# Auto-refresh library after generation
|
| 1743 |
+
fn=load_podcast_library_ui,
|
| 1744 |
+
outputs=[podcast_library_container, podcast_library_status]
|
| 1745 |
+
)
|
| 1746 |
+
|
| 1747 |
+
# NEW SECTION: Podcast Library (at tab level, same as first gr.Row())
|
| 1748 |
+
gr.Markdown("---")
|
| 1749 |
+
gr.Markdown("### π Podcast Library")
|
| 1750 |
+
gr.Markdown("Browse and play all your generated podcasts")
|
| 1751 |
+
|
| 1752 |
+
with gr.Row():
|
| 1753 |
+
refresh_podcast_library_btn = gr.Button("π Refresh Library", variant="secondary")
|
| 1754 |
+
podcast_library_status = gr.Textbox(
|
| 1755 |
+
label="Library Status",
|
| 1756 |
+
value="Click 'Refresh Library' to load podcasts",
|
| 1757 |
+
interactive=False,
|
| 1758 |
+
scale=3
|
| 1759 |
+
)
|
| 1760 |
+
|
| 1761 |
+
# Event handler for refresh button
|
| 1762 |
+
refresh_podcast_library_btn.click(
|
| 1763 |
+
fn=load_podcast_library_ui,
|
| 1764 |
+
outputs=[podcast_library_container, podcast_library_status]
|
| 1765 |
)
|
| 1766 |
|
| 1767 |
with gr.Tab("β Ask Questions"):
|