explainor / app.py
Emperor555's picture
Initial commit: Explainor - AI agent with persona voices
074e8ce
raw
history blame
8.37 kB
"""Explainor - AI Agent that explains any topic in fun persona voices.
MCP's 1st Birthday Hackathon Submission
Track: MCP in Action (Creative)
Team: kaiser-data
"""
import os
import tempfile
import gradio as gr
from dotenv import load_dotenv
from src.personas import PERSONAS, get_persona_names, get_persona
from src.agent import run_agent
from src.tts import generate_speech
# Load environment variables
load_dotenv()
def format_sources(sources: list[dict]) -> str:
"""Format sources as markdown."""
if not sources:
return "*No external sources used*"
md = ""
for i, src in enumerate(sources, 1):
if src.get("url"):
md += f"{i}. [{src['title']}]({src['url']})\n"
else:
md += f"{i}. {src['title']} ({src.get('source', 'General')})\n"
return md
def explain_topic(topic: str, persona_name: str, progress=gr.Progress()):
"""Main function to explain a topic in a persona's voice.
Returns: (explanation_text, audio_path, sources_md, steps_md)
"""
if not topic.strip():
return (
"Please enter a topic to explain!",
None,
"",
"❌ No topic provided",
)
if not persona_name:
persona_name = "5-Year-Old"
steps_log = []
explanation = ""
sources = []
voice_id = None
# Run the agent pipeline
progress(0, desc="Starting...")
for update in run_agent(topic, persona_name):
if update["type"] == "step":
step_text = f"**{update['title']}**\n{update['content']}"
steps_log.append(step_text)
if update["step"] == "research":
progress(0.2, desc="Researching...")
elif update["step"] == "research_done":
progress(0.4, desc="Research complete")
if "sources" in update:
sources = update["sources"]
elif update["step"] == "generating":
progress(0.6, desc="Generating explanation...")
elif update["type"] == "result":
explanation = update["explanation"]
sources = update.get("sources", sources)
voice_id = update["voice_id"]
progress(0.8, desc="Explanation ready!")
# Format the steps log
steps_md = "\n\n---\n\n".join(steps_log)
# Generate audio
audio_path = None
if explanation and voice_id:
progress(0.9, desc="Generating audio...")
try:
audio_bytes = generate_speech(explanation, voice_id)
# Save to temp file for Gradio
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
f.write(audio_bytes)
audio_path = f.name
progress(1.0, desc="Done!")
except Exception as e:
steps_log.append(f"**⚠️ Audio generation failed**\n{str(e)}")
steps_md = "\n\n---\n\n".join(steps_log)
progress(1.0, desc="Done (no audio)")
# Format sources
sources_md = format_sources(sources)
return explanation, audio_path, sources_md, steps_md
# Build the Gradio interface
def create_app():
"""Create and configure the Gradio app."""
# Custom CSS for better styling
css = """
.gradio-container {
max-width: 900px !important;
margin: auto !important;
}
.persona-dropdown {
font-size: 1.1em !important;
}
#title-row {
text-align: center;
margin-bottom: 1rem;
}
.explanation-box {
font-size: 1.15em;
line-height: 1.6;
padding: 1rem;
background: linear-gradient(135deg, #667eea11 0%, #764ba211 100%);
border-radius: 10px;
}
.steps-accordion {
font-size: 0.95em;
}
"""
with gr.Blocks(
title="Explainor - AI Persona Explanations",
css=css,
theme=gr.themes.Soft(
primary_hue="violet",
secondary_hue="blue",
),
) as app:
# Header
gr.Markdown(
"""
# 🎭 Explainor
**Learn anything through the voice of your favorite characters!**
Enter any topic and choose a persona. The AI will research your topic,
transform the explanation into that character's unique voice, and read it aloud.
""",
elem_id="title-row",
)
with gr.Row():
with gr.Column(scale=2):
topic_input = gr.Textbox(
label="πŸ“ What do you want to learn about?",
placeholder="e.g., Blockchain, Photosynthesis, Black Holes...",
lines=1,
max_lines=1,
)
with gr.Column(scale=1):
# Build persona choices with emojis
persona_choices = [
f"{PERSONAS[name]['emoji']} {name}"
for name in get_persona_names()
]
persona_dropdown = gr.Dropdown(
choices=persona_choices,
value=persona_choices[0],
label="🎭 Choose your explainer",
elem_classes=["persona-dropdown"],
)
explain_btn = gr.Button(
"✨ Explain it to me!",
variant="primary",
size="lg",
)
# Output section
with gr.Row():
with gr.Column():
explanation_output = gr.Textbox(
label="πŸ“– Explanation",
lines=8,
max_lines=15,
elem_classes=["explanation-box"],
show_copy_button=True,
)
audio_output = gr.Audio(
label="πŸ”Š Listen to the explanation",
type="filepath",
autoplay=False,
)
with gr.Row():
with gr.Column():
with gr.Accordion("πŸ” Sources", open=False):
sources_output = gr.Markdown("")
with gr.Column():
with gr.Accordion("🧠 Agent Reasoning", open=False):
steps_output = gr.Markdown(
"",
elem_classes=["steps-accordion"],
)
# Example topics
gr.Examples(
examples=[
["Quantum Computing", f"{PERSONAS['5-Year-Old']['emoji']} 5-Year-Old"],
["Blockchain", f"{PERSONAS['Gordon Ramsay']['emoji']} Gordon Ramsay"],
["Black Holes", f"{PERSONAS['Pirate']['emoji']} Pirate"],
["Machine Learning", f"{PERSONAS['Shakespeare']['emoji']} Shakespeare"],
["Climate Change", f"{PERSONAS['Surfer Dude']['emoji']} Surfer Dude"],
["The Force", f"{PERSONAS['Yoda']['emoji']} Yoda"],
],
inputs=[topic_input, persona_dropdown],
label="Try these examples:",
)
# Footer
gr.Markdown(
"""
---
**Built for MCP's 1st Birthday Hackathon** | Track: MCP in Action (Creative)
Powered by: [Nebius AI](https://nebius.com) (LLM) + [ElevenLabs](https://elevenlabs.io) (TTS)
Made with ❀️ by **kaiser-data**
""",
elem_id="footer",
)
# Event handler
def process_and_explain(topic, persona_with_emoji):
# Extract persona name (remove emoji prefix)
persona_name = persona_with_emoji.split(" ", 1)[1] if " " in persona_with_emoji else persona_with_emoji
return explain_topic(topic, persona_name)
explain_btn.click(
fn=process_and_explain,
inputs=[topic_input, persona_dropdown],
outputs=[explanation_output, audio_output, sources_output, steps_output],
)
# Also trigger on Enter key in topic input
topic_input.submit(
fn=process_and_explain,
inputs=[topic_input, persona_dropdown],
outputs=[explanation_output, audio_output, sources_output, steps_output],
)
return app
# Create the app
app = create_app()
if __name__ == "__main__":
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
)