# app.py: Workshop-in-a-Box Space with OpenTTS & Live Slide Previews import os import json import tempfile from zipfile import ZipFile import gradio as gr from agents import Agent, AgentRunner, handoff from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX # OpenTTS client from open_tts import OpenTTSClient # Initialize OpenTTS with your server URL (set via env var or default localhost) opentts = OpenTTSClient(api_url=os.getenv("OPENTTS_API_URL", "http://localhost:5500")) def generate_tts_audio(script: str) -> str: """ Synthesize speech using OpenTTS and save to a temp MP3 file. Returns the local file path to the audio. """ # Request synthesis (voice and format can be configured) audio_bytes = opentts.synthesize(text=script, voice="alloy", format="mp3") # Write to a temp file out_path = os.path.join(tempfile.gettempdir(), "voiceover.mp3") with open(out_path, "wb") as f: f.write(audio_bytes) return out_path # --- Multi-Agent Scaffold --- # 1. Topic Agent topic_agent = Agent( name="Topic Agent", instructions=( f"{RECOMMENDED_PROMPT_PREFIX}\n" "You are given a workshop topic and audience. Draft a structured learning path: goals, 4 modules, and a hands-on exercise for each." ) ) # 2. Content Agent content_agent = Agent( name="Content Agent", instructions=( f"{RECOMMENDED_PROMPT_PREFIX}\n" "Convert the outline into detailed module scripts, speaker notes, and 3 quiz questions per module." ) ) # 3. Slide Agent slide_agent = Agent( name="Slide Agent", instructions=( f"{RECOMMENDED_PROMPT_PREFIX}\n" "Given module content, produce slide JSON with title, bullet points, and design hints." ) ) # 4. Code Agent code_agent = Agent( name="Code Agent", instructions=( f"{RECOMMENDED_PROMPT_PREFIX}\n" "Generate runnable Python code snippets or a Colab notebook for hands-on labs in each module." ) ) # 5. Voiceover Agent (Optional) voice_agent = Agent( name="Voiceover Agent", instructions=( f"{RECOMMENDED_PROMPT_PREFIX}\n" "Create a 1-2 minute voiceover script. Return JSON with keys 'script'." ) ) # Orchestrator: sequences agents document_orchestrator = Agent( name="Workshop Orchestrator", instructions=( "Invoke: topic_agent, content_agent, slide_agent, code_agent, voice_agent (optional); collect outputs." ), handoffs=[ handoff(topic_agent, name="outline"), handoff(content_agent, name="content"), handoff(slide_agent, name="slides"), handoff(code_agent, name="code_labs"), handoff(voice_agent, name="voiceover", optional=True), ] ) runner = AgentRunner() # --- Helper: Run pipeline & bundle outputs --- def build_workshop_bundle(topic: str, audience: str): prompt = f"Create a {topic} workshop for {audience}." results = runner.run(document_orchestrator, prompt).outputs # Synthesize voiceover if script present voice_info = results.get('voiceover', {}) audio_path = None if isinstance(voice_info, dict) and 'script' in voice_info: audio_path = generate_tts_audio(voice_info['script']) # Render slides to HTML slides_json = results.get('slides', {}) with open('static/slides_template.html') as f: template = f.read() slide_html = template.replace('{{SLIDES_JSON}}', json.dumps(slides_json)) # Create a ZIP bundle tmpdir = tempfile.mkdtemp() zip_path = os.path.join(tmpdir, 'workshop_bundle.zip') with ZipFile(zip_path, 'w') as zipf: # JSON outputs out_json = os.path.join(tmpdir, 'workshop_outputs.json') with open(out_json, 'w') as jf: jf.write(json.dumps(results, indent=2)) zipf.write(out_json, 'workshop_outputs.json') # Slides slides_file = os.path.join(tmpdir, 'slides.json') with open(slides_file, 'w') as sf: sf.write(json.dumps(slides_json, indent=2)) zipf.write(slides_file, 'slides.json') slide_html_file = os.path.join(tmpdir, 'slides.html') with open(slide_html_file, 'w') as hf: hf.write(slide_html) zipf.write(slide_html_file, 'slides.html') # Code labs code_file = os.path.join(tmpdir, 'code_labs.py') with open(code_file, 'w') as cf: cf.write(results.get('code_labs', '')) zipf.write(code_file, 'code_labs.py') # Audio if audio_path and os.path.exists(audio_path): zipf.write(audio_path, os.path.basename(audio_path)) return slide_html, audio_path, zip_path # --- Gradio UI --- def run_app(topic, audience): return build_workshop_bundle(topic, audience) with gr.Blocks(title='🚀 Workshop in a Box') as demo: gr.Markdown('# Workshop in a Box') topic = gr.Textbox(label='Workshop Topic', placeholder='e.g., AI Agents 101') audience = gr.Textbox(label='Audience', placeholder='e.g., Product Managers') btn = gr.Button('Generate Workshop') slide_preview = gr.HTML(label='Slide Preview') audio_player = gr.Audio(label='Voiceover Preview', interactive=True) download = gr.File(label='Download ZIP') btn.click( fn=run_app, inputs=[topic, audience], outputs=[slide_preview, audio_player, download] ) if __name__ == '__main__': demo.launch()