Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
# app.py: Workshop-in-a-Box Hugging Face Space with TTS and Slide Previews
|
| 4 |
import os
|
| 5 |
import json
|
| 6 |
import tempfile
|
|
@@ -10,8 +9,24 @@ import gradio as gr
|
|
| 10 |
from agents import Agent, AgentRunner, handoff
|
| 11 |
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
|
| 12 |
|
| 13 |
-
#
|
| 14 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
# --- Multi-Agent Scaffold ---
|
| 17 |
# 1. Topic Agent
|
|
@@ -55,7 +70,7 @@ voice_agent = Agent(
|
|
| 55 |
name="Voiceover Agent",
|
| 56 |
instructions=(
|
| 57 |
f"{RECOMMENDED_PROMPT_PREFIX}\n"
|
| 58 |
-
"Create a 1-2 minute voiceover script. Return JSON with keys 'script'
|
| 59 |
)
|
| 60 |
)
|
| 61 |
|
|
@@ -63,7 +78,7 @@ voice_agent = Agent(
|
|
| 63 |
document_orchestrator = Agent(
|
| 64 |
name="Workshop Orchestrator",
|
| 65 |
instructions=(
|
| 66 |
-
"Invoke
|
| 67 |
),
|
| 68 |
handoffs=[
|
| 69 |
handoff(topic_agent, name="outline"),
|
|
@@ -76,15 +91,15 @@ document_orchestrator = Agent(
|
|
| 76 |
|
| 77 |
runner = AgentRunner()
|
| 78 |
|
| 79 |
-
# --- Helper: Run pipeline
|
| 80 |
def build_workshop_bundle(topic: str, audience: str):
|
| 81 |
prompt = f"Create a {topic} workshop for {audience}."
|
| 82 |
results = runner.run(document_orchestrator, prompt).outputs
|
| 83 |
|
| 84 |
-
#
|
| 85 |
voice_info = results.get('voiceover', {})
|
| 86 |
audio_path = None
|
| 87 |
-
if 'script' in voice_info:
|
| 88 |
audio_path = generate_tts_audio(voice_info['script'])
|
| 89 |
|
| 90 |
# Render slides to HTML
|
|
@@ -93,37 +108,56 @@ def build_workshop_bundle(topic: str, audience: str):
|
|
| 93 |
template = f.read()
|
| 94 |
slide_html = template.replace('{{SLIDES_JSON}}', json.dumps(slides_json))
|
| 95 |
|
| 96 |
-
# Create ZIP
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
with ZipFile(
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
(
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
if audio_path and os.path.exists(audio_path):
|
| 110 |
-
|
| 111 |
-
|
|
|
|
| 112 |
|
| 113 |
# --- Gradio UI ---
|
| 114 |
def run_app(topic, audience):
|
| 115 |
-
|
| 116 |
-
return slide_html, audio_path, zip_path
|
| 117 |
|
| 118 |
-
with gr.Blocks(title='Workshop in a Box') as demo:
|
| 119 |
-
gr.Markdown('#
|
| 120 |
-
topic = gr.Textbox(label='Workshop Topic')
|
| 121 |
-
audience = gr.Textbox(label='Audience')
|
| 122 |
btn = gr.Button('Generate Workshop')
|
| 123 |
-
slide_preview = gr.HTML()
|
| 124 |
-
audio_player = gr.Audio(interactive=True)
|
| 125 |
-
download = gr.File()
|
| 126 |
-
btn.click(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
if __name__ == '__main__':
|
| 129 |
demo.launch()
|
|
|
|
| 1 |
+
|
| 2 |
+
# app.py: Workshop-in-a-Box Space with OpenTTS & Live Slide Previews
|
|
|
|
| 3 |
import os
|
| 4 |
import json
|
| 5 |
import tempfile
|
|
|
|
| 9 |
from agents import Agent, AgentRunner, handoff
|
| 10 |
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
|
| 11 |
|
| 12 |
+
# OpenTTS client
|
| 13 |
+
from open_tts import OpenTTSClient
|
| 14 |
+
|
| 15 |
+
# Initialize OpenTTS with your server URL (set via env var or default localhost)
|
| 16 |
+
opentts = OpenTTSClient(api_url=os.getenv("OPENTTS_API_URL", "http://localhost:5500"))
|
| 17 |
+
|
| 18 |
+
def generate_tts_audio(script: str) -> str:
|
| 19 |
+
"""
|
| 20 |
+
Synthesize speech using OpenTTS and save to a temp MP3 file.
|
| 21 |
+
Returns the local file path to the audio.
|
| 22 |
+
"""
|
| 23 |
+
# Request synthesis (voice and format can be configured)
|
| 24 |
+
audio_bytes = opentts.synthesize(text=script, voice="alloy", format="mp3")
|
| 25 |
+
# Write to a temp file
|
| 26 |
+
out_path = os.path.join(tempfile.gettempdir(), "voiceover.mp3")
|
| 27 |
+
with open(out_path, "wb") as f:
|
| 28 |
+
f.write(audio_bytes)
|
| 29 |
+
return out_path
|
| 30 |
|
| 31 |
# --- Multi-Agent Scaffold ---
|
| 32 |
# 1. Topic Agent
|
|
|
|
| 70 |
name="Voiceover Agent",
|
| 71 |
instructions=(
|
| 72 |
f"{RECOMMENDED_PROMPT_PREFIX}\n"
|
| 73 |
+
"Create a 1-2 minute voiceover script. Return JSON with keys 'script'."
|
| 74 |
)
|
| 75 |
)
|
| 76 |
|
|
|
|
| 78 |
document_orchestrator = Agent(
|
| 79 |
name="Workshop Orchestrator",
|
| 80 |
instructions=(
|
| 81 |
+
"Invoke: topic_agent, content_agent, slide_agent, code_agent, voice_agent (optional); collect outputs."
|
| 82 |
),
|
| 83 |
handoffs=[
|
| 84 |
handoff(topic_agent, name="outline"),
|
|
|
|
| 91 |
|
| 92 |
runner = AgentRunner()
|
| 93 |
|
| 94 |
+
# --- Helper: Run pipeline & bundle outputs ---
|
| 95 |
def build_workshop_bundle(topic: str, audience: str):
|
| 96 |
prompt = f"Create a {topic} workshop for {audience}."
|
| 97 |
results = runner.run(document_orchestrator, prompt).outputs
|
| 98 |
|
| 99 |
+
# Synthesize voiceover if script present
|
| 100 |
voice_info = results.get('voiceover', {})
|
| 101 |
audio_path = None
|
| 102 |
+
if isinstance(voice_info, dict) and 'script' in voice_info:
|
| 103 |
audio_path = generate_tts_audio(voice_info['script'])
|
| 104 |
|
| 105 |
# Render slides to HTML
|
|
|
|
| 108 |
template = f.read()
|
| 109 |
slide_html = template.replace('{{SLIDES_JSON}}', json.dumps(slides_json))
|
| 110 |
|
| 111 |
+
# Create a ZIP bundle
|
| 112 |
+
tmpdir = tempfile.mkdtemp()
|
| 113 |
+
zip_path = os.path.join(tmpdir, 'workshop_bundle.zip')
|
| 114 |
+
with ZipFile(zip_path, 'w') as zipf:
|
| 115 |
+
# JSON outputs
|
| 116 |
+
out_json = os.path.join(tmpdir, 'workshop_outputs.json')
|
| 117 |
+
with open(out_json, 'w') as jf:
|
| 118 |
+
jf.write(json.dumps(results, indent=2))
|
| 119 |
+
zipf.write(out_json, 'workshop_outputs.json')
|
| 120 |
+
|
| 121 |
+
# Slides
|
| 122 |
+
slides_file = os.path.join(tmpdir, 'slides.json')
|
| 123 |
+
with open(slides_file, 'w') as sf:
|
| 124 |
+
sf.write(json.dumps(slides_json, indent=2))
|
| 125 |
+
zipf.write(slides_file, 'slides.json')
|
| 126 |
+
|
| 127 |
+
slide_html_file = os.path.join(tmpdir, 'slides.html')
|
| 128 |
+
with open(slide_html_file, 'w') as hf:
|
| 129 |
+
hf.write(slide_html)
|
| 130 |
+
zipf.write(slide_html_file, 'slides.html')
|
| 131 |
+
|
| 132 |
+
# Code labs
|
| 133 |
+
code_file = os.path.join(tmpdir, 'code_labs.py')
|
| 134 |
+
with open(code_file, 'w') as cf:
|
| 135 |
+
cf.write(results.get('code_labs', ''))
|
| 136 |
+
zipf.write(code_file, 'code_labs.py')
|
| 137 |
+
|
| 138 |
+
# Audio
|
| 139 |
if audio_path and os.path.exists(audio_path):
|
| 140 |
+
zipf.write(audio_path, os.path.basename(audio_path))
|
| 141 |
+
|
| 142 |
+
return slide_html, audio_path, zip_path
|
| 143 |
|
| 144 |
# --- Gradio UI ---
|
| 145 |
def run_app(topic, audience):
|
| 146 |
+
return build_workshop_bundle(topic, audience)
|
|
|
|
| 147 |
|
| 148 |
+
with gr.Blocks(title='🚀 Workshop in a Box') as demo:
|
| 149 |
+
gr.Markdown('# Workshop in a Box')
|
| 150 |
+
topic = gr.Textbox(label='Workshop Topic', placeholder='e.g., AI Agents 101')
|
| 151 |
+
audience = gr.Textbox(label='Audience', placeholder='e.g., Product Managers')
|
| 152 |
btn = gr.Button('Generate Workshop')
|
| 153 |
+
slide_preview = gr.HTML(label='Slide Preview')
|
| 154 |
+
audio_player = gr.Audio(label='Voiceover Preview', interactive=True)
|
| 155 |
+
download = gr.File(label='Download ZIP')
|
| 156 |
+
btn.click(
|
| 157 |
+
fn=run_app,
|
| 158 |
+
inputs=[topic, audience],
|
| 159 |
+
outputs=[slide_preview, audio_player, download]
|
| 160 |
+
)
|
| 161 |
|
| 162 |
if __name__ == '__main__':
|
| 163 |
demo.launch()
|