"""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() # Custom CSS for better styling CUSTOM_CSS = """ /* Dark mode input fix */ .dark input, .dark textarea { background-color: #374151 !important; color: #ffffff !important; } /* Header styling */ .header-container { text-align: center; padding: 1rem 0; } /* Card-like sections */ .input-section, .output-section { border-radius: 12px; padding: 1rem; } /* Primary button enhancement */ .primary-btn { font-size: 1.1rem !important; padding: 0.75rem 2rem !important; } /* Audio section layout */ .audio-row { display: flex; align-items: center; gap: 1rem; } /* Persona cards in examples */ .example-row { margin-top: 0.5rem; } /* Footer styling */ .footer { text-align: center; opacity: 0.8; font-size: 0.9rem; } /* MCP badge */ .mcp-badge { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 0.5rem 1rem; border-radius: 8px; display: inline-block; font-weight: bold; } """ 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 format_mcp_tools(tools: list[dict]) -> str: """Format tools used as markdown table.""" if not tools: return "*Waiting for explanation...*" md = "| Tool | Description |\n|------|-------------|\n" for tool in tools: md += f"| {tool['icon']} `{tool['name']}` | {tool['desc']} |\n" return md def explain_topic(topic: str, persona_name: str, audience: str = "", progress=gr.Progress()): """Main function to explain a topic in a persona's voice.""" if not topic.strip(): return "Please enter a topic to explain!", "", "", "" if not persona_name: persona_name = "5-Year-Old" steps_log = [] explanation = "" sources = [] mcp_tools = [] progress(0, desc="Starting...") for update in run_agent(topic, persona_name, audience): 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) mcp_tools = update.get("mcp_tools", []) progress(1.0, desc="โ Done!") steps_md = "\n\n---\n\n".join(steps_log) sources_md = format_sources(sources) mcp_md = format_mcp_tools(mcp_tools) return explanation, sources_md, steps_md, mcp_md def generate_audio(explanation: str, persona_name: str, progress=gr.Progress()): """Generate audio from the explanation text.""" if not explanation or not explanation.strip(): return None if not persona_name: persona_name = "5-Year-Old" persona = get_persona(persona_name) voice_id = persona["voice_id"] voice_settings = persona.get("voice_settings") progress(0.3, desc="๐ Generating audio...") try: audio_bytes = generate_speech(explanation, voice_id, voice_settings) with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f: f.write(audio_bytes) audio_path = f.name progress(1.0, desc="โ Audio ready!") return audio_path except Exception as e: progress(1.0, desc="โ Audio failed") raise gr.Error(f"Audio generation failed: {str(e)}") def create_app(): """Create and configure the Gradio app.""" # Build persona choices persona_choices = [ f"{PERSONAS[name]['emoji']} {name}" for name in get_persona_names() ] # Audience choices audience_choices = [ "๐ค Just me", "๐ต Confused grandmother", "๐ค Skeptical robot", "๐ฝ Alien visitor", "๐ง Zombie", "๐ Stressed CEO", ] with gr.Blocks(title="Explainor", fill_width=True) as app: # ===== HEADER ===== gr.Markdown( """
Learn anything through the voice of your favorite characters!