mgbam commited on
Commit
c185f68
Β·
verified Β·
1 Parent(s): 7154b6b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +395 -74
app.py CHANGED
@@ -10,7 +10,6 @@ import base64
10
  import textwrap
11
  from dotenv import load_dotenv
12
  from openai import OpenAI # Updated OpenAI client
13
- from elevenlabs import ElevenLabs # Official ElevenLabs SDK import
14
 
15
  # Load environment variables
16
  load_dotenv()
@@ -19,8 +18,6 @@ load_dotenv()
19
  openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) if os.getenv("OPENAI_API_KEY") else None
20
  ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
21
 
22
- eleven_client = ElevenLabs(api_key=ELEVENLABS_API_KEY) if ELEVENLABS_API_KEY else None
23
-
24
  # =============================
25
  # UPDATED AGENT IMPLEMENTATION (OpenAI v1.x compatible)
26
  # =============================
@@ -29,18 +26,25 @@ class TopicAgent:
29
  def generate_outline(self, topic, duration, difficulty):
30
  if not openai_client:
31
  return self._mock_outline(topic, duration, difficulty)
 
32
  try:
33
  response = openai_client.chat.completions.create(
34
  model="gpt-4-turbo",
35
  messages=[
36
- {"role": "system", "content": "You're an expert corporate trainer creating comprehensive AI workshop outlines."},
37
- {"role": "user", "content": (
38
- f"Create a detailed {duration}-hour {difficulty} workshop outline on {topic}. "
39
- "Include: 4-6 modules with specific learning objectives, hands-on exercises, "
40
- "and real-world case studies. Format as JSON with keys: "
41
- "{'topic', 'duration', 'difficulty', 'goals', 'modules': ["
42
- "{'title', 'duration', 'learning_objectives', 'case_study', 'exercises'}]}"
43
- )}
 
 
 
 
 
 
44
  ],
45
  temperature=0.3,
46
  max_tokens=1500,
@@ -48,9 +52,9 @@ class TopicAgent:
48
  )
49
  return json.loads(response.choices[0].message.content)
50
  except Exception as e:
51
- st.error(f"Outline generation error: {e}")
52
  return self._mock_outline(topic, duration, difficulty)
53
-
54
  def _mock_outline(self, topic, duration, difficulty):
55
  return {
56
  "topic": topic,
@@ -98,18 +102,25 @@ class ContentAgent:
98
  def generate_content(self, outline):
99
  if not openai_client:
100
  return self._mock_content(outline)
 
101
  try:
102
  response = openai_client.chat.completions.create(
103
  model="gpt-4-turbo",
104
  messages=[
105
- {"role": "system", "content": "You're a corporate training content developer creating detailed workshop materials."},
106
- {"role": "user", "content": (
107
- f"Expand this workshop outline into comprehensive content: {json.dumps(outline)}. "
108
- "For each module, include: detailed script (3-5 paragraphs), speaker notes (bullet points), "
109
- "3 quiz questions with explanations, and exercise instructions. Format as JSON with keys: "
110
- "{'workshop_title', 'modules': [{'title', 'script', 'speaker_notes', 'quiz': ["
111
- "{'question', 'options', 'answer', 'explanation'}], 'exercise_instructions'}]}"
112
- )}
 
 
 
 
 
 
113
  ],
114
  temperature=0.4,
115
  max_tokens=2000,
@@ -117,9 +128,9 @@ class ContentAgent:
117
  )
118
  return json.loads(response.choices[0].message.content)
119
  except Exception as e:
120
- st.error(f"Content generation error: {e}")
121
  return self._mock_content(outline)
122
-
123
  def _mock_content(self, outline):
124
  return {
125
  "workshop_title": f"Mastering {outline['topic']}",
@@ -133,10 +144,12 @@ class ContentAgent:
133
  "Discuss token limitations and their impact"
134
  ],
135
  "quiz": [
136
- {"question": "What's the most important element of a good prompt?",
137
- "options": ["Length", "Specificity", "Complexity", "Creativity"],
138
- "answer": "Specificity",
139
- "explanation": "Specific prompts yield more accurate and relevant responses"}
 
 
140
  ],
141
  "exercise_instructions": "Create a prompt that extracts key insights from a financial report..."
142
  }
@@ -147,26 +160,33 @@ class SlideAgent:
147
  def generate_slides(self, content):
148
  if not openai_client:
149
  return self._mock_slides(content)
 
150
  try:
151
  response = openai_client.chat.completions.create(
152
  model="gpt-4-turbo",
153
  messages=[
154
- {"role": "system", "content": "You create professional slide decks in Markdown format using Marp syntax."},
155
- {"role": "user", "content": (
156
- f"Create a slide deck for this workshop content: {json.dumps(content)}. "
157
- "Use Marp Markdown format with themes and visual elements. "
158
- "Include: title slide, module slides with key points, case studies, "
159
- "exercise instructions, and summary slides. Make it visually appealing."
160
- )}
 
 
 
 
 
 
161
  ],
162
  temperature=0.2,
163
  max_tokens=2500
164
  )
165
  return response.choices[0].message.content
166
  except Exception as e:
167
- st.error(f"Slide generation error: {e}")
168
  return self._mock_slides(content)
169
-
170
  def _mock_slides(self, content):
171
  return f"""---
172
  marp: true
@@ -201,34 +221,41 @@ backgroundImage: url('https://marp.app/assets/hero-background.svg')
201
  1. Craft effective prompts for different scenarios
202
  2. Optimize prompts for specific AI models
203
  3. Analyze and refine prompt performance
 
204
  """
205
 
206
  class CodeAgent:
207
  def generate_code(self, content):
208
  if not openai_client:
209
  return self._mock_code(content)
 
210
  try:
211
  response = openai_client.chat.completions.create(
212
  model="gpt-4-turbo",
213
  messages=[
214
- {"role": "system", "content": "You create practical code labs for technical workshops."},
215
- {"role": "user", "content": (
216
- f"Create a Jupyter notebook with code exercises for this workshop: {json.dumps(content)}. "
217
- "Include: setup instructions, practical exercises with solutions, "
218
- "and real-world implementation examples. Use Python with popular AI libraries."
219
- )}
 
 
 
 
 
 
220
  ],
221
  temperature=0.3,
222
  max_tokens=2000
223
  )
224
  return response.choices[0].message.content
225
  except Exception as e:
226
- st.error(f"Code generation error: {e}")
227
  return self._mock_code(content)
228
-
229
  def _mock_code(self, content):
230
- return f"""
231
- # {content['workshop_title']} - Code Labs
232
 
233
  import openai
234
  import pandas as pd
@@ -237,10 +264,11 @@ import pandas as pd
237
  def generate_response(prompt):
238
  response = openai.chat.completions.create(
239
  model="gpt-4",
240
- messages=[{"role": "user", "content": prompt}]
241
  )
242
  return response.choices[0].message.content
243
 
 
244
  print(generate_response("Explain quantum computing in simple terms"))
245
 
246
  ## Exercise 2: Advanced Prompt Patterns
@@ -255,6 +283,7 @@ class DesignAgent:
255
  def generate_design(self, slide_content):
256
  if not openai_client:
257
  return None
 
258
  try:
259
  response = openai_client.images.generate(
260
  prompt=f"Create a professional slide background for a corporate AI workshop about: {slide_content[:500]}",
@@ -263,45 +292,65 @@ class DesignAgent:
263
  )
264
  return response.data[0].url
265
  except Exception as e:
266
- st.error(f"Design generation error: {e}")
267
  return None
268
 
269
  class VoiceoverAgent:
270
  def __init__(self):
271
- self.client = eleven_client
272
- self.default_voice_id = "9BWtsMINqrJLrRacOk9x" # Default fallback voice
273
- self.model_id = "eleven_monolingual_v1"
274
-
275
- def get_voices(self):
276
- if not self.client:
277
- return []
278
- try:
279
- voices = self.client.voices.list()
280
- return [{"voice_id": v.voice_id, "name": v.name} for v in voices]
281
- except Exception as e:
282
- st.error(f"Voice loading error: {e}")
283
- return []
284
-
285
  def generate_voiceover(self, text, voice_id=None):
286
- if not self.client:
287
  return None
 
288
  try:
289
- vid = voice_id or self.default_voice_id
290
- audio = self.client.text_to_speech.convert(
291
- text=text,
292
- voice_id=vid,
293
- model_id=self.model_id,
294
- voice_settings={
 
 
 
 
 
 
 
295
  "stability": 0.7,
296
  "similarity_boost": 0.8,
297
  "style": 0.5,
298
  "use_speaker_boost": True
299
  }
300
- )
301
- return audio
 
 
 
 
 
 
302
  except Exception as e:
303
- st.error(f"Voiceover generation error: {e}")
304
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
  # Initialize agents
307
  topic_agent = TopicAgent()
@@ -321,13 +370,14 @@ st.set_page_config(
321
  initial_sidebar_state="expanded"
322
  )
323
 
324
- # Custom CSS
325
  st.markdown("""
326
  <style>
327
  .stApp {
328
  background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
329
  color: #fff;
330
  }
 
331
  .stTextInput>div>div>input {
332
  color: #333 !important;
333
  background-color: #fff !important;
@@ -363,4 +413,275 @@ st.markdown("""
363
  display: flex;
364
  align-items: center;
365
  margin: 5px 0;
366
- padding
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  import textwrap
11
  from dotenv import load_dotenv
12
  from openai import OpenAI # Updated OpenAI client
 
13
 
14
  # Load environment variables
15
  load_dotenv()
 
18
  openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) if os.getenv("OPENAI_API_KEY") else None
19
  ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
20
 
 
 
21
  # =============================
22
  # UPDATED AGENT IMPLEMENTATION (OpenAI v1.x compatible)
23
  # =============================
 
26
  def generate_outline(self, topic, duration, difficulty):
27
  if not openai_client:
28
  return self._mock_outline(topic, duration, difficulty)
29
+
30
  try:
31
  response = openai_client.chat.completions.create(
32
  model="gpt-4-turbo",
33
  messages=[
34
+ {
35
+ "role": "system",
36
+ "content": "You're an expert corporate trainer creating comprehensive AI workshop outlines."
37
+ },
38
+ {
39
+ "role": "user",
40
+ "content": (
41
+ f"Create a detailed {duration}-hour {difficulty} workshop outline on {topic}. "
42
+ "Include: 4-6 modules with specific learning objectives, hands-on exercises, "
43
+ "and real-world case studies. Format as JSON with keys: "
44
+ "{'topic', 'duration', 'difficulty', 'goals', 'modules': ["
45
+ "{'title', 'duration', 'learning_objectives', 'case_study', 'exercises'}]}"
46
+ )
47
+ }
48
  ],
49
  temperature=0.3,
50
  max_tokens=1500,
 
52
  )
53
  return json.loads(response.choices[0].message.content)
54
  except Exception as e:
55
+ st.error(f"Outline generation error: {str(e)}")
56
  return self._mock_outline(topic, duration, difficulty)
57
+
58
  def _mock_outline(self, topic, duration, difficulty):
59
  return {
60
  "topic": topic,
 
102
  def generate_content(self, outline):
103
  if not openai_client:
104
  return self._mock_content(outline)
105
+
106
  try:
107
  response = openai_client.chat.completions.create(
108
  model="gpt-4-turbo",
109
  messages=[
110
+ {
111
+ "role": "system",
112
+ "content": "You're a corporate training content developer creating detailed workshop materials."
113
+ },
114
+ {
115
+ "role": "user",
116
+ "content": (
117
+ f"Expand this workshop outline into comprehensive content: {json.dumps(outline)}. "
118
+ "For each module, include: detailed script (3-5 paragraphs), speaker notes (bullet points), "
119
+ "3 quiz questions with explanations, and exercise instructions. Format as JSON with keys: "
120
+ "{'workshop_title', 'modules': [{'title', 'script', 'speaker_notes', 'quiz': ["
121
+ "{'question', 'options', 'answer', 'explanation'}], 'exercise_instructions'}]}"
122
+ )
123
+ }
124
  ],
125
  temperature=0.4,
126
  max_tokens=2000,
 
128
  )
129
  return json.loads(response.choices[0].message.content)
130
  except Exception as e:
131
+ st.error(f"Content generation error: {str(e)}")
132
  return self._mock_content(outline)
133
+
134
  def _mock_content(self, outline):
135
  return {
136
  "workshop_title": f"Mastering {outline['topic']}",
 
144
  "Discuss token limitations and their impact"
145
  ],
146
  "quiz": [
147
+ {
148
+ "question": "What's the most important element of a good prompt?",
149
+ "options": ["Length", "Specificity", "Complexity", "Creativity"],
150
+ "answer": "Specificity",
151
+ "explanation": "Specific prompts yield more accurate and relevant responses"
152
+ }
153
  ],
154
  "exercise_instructions": "Create a prompt that extracts key insights from a financial report..."
155
  }
 
160
  def generate_slides(self, content):
161
  if not openai_client:
162
  return self._mock_slides(content)
163
+
164
  try:
165
  response = openai_client.chat.completions.create(
166
  model="gpt-4-turbo",
167
  messages=[
168
+ {
169
+ "role": "system",
170
+ "content": "You create professional slide decks in Markdown format using Marp syntax."
171
+ },
172
+ {
173
+ "role": "user",
174
+ "content": (
175
+ f"Create a slide deck for this workshop content: {json.dumps(content)}. "
176
+ "Use Marp Markdown format with themes and visual elements. "
177
+ "Include: title slide, module slides with key points, case studies, "
178
+ "exercise instructions, and summary slides. Make it visually appealing."
179
+ )
180
+ }
181
  ],
182
  temperature=0.2,
183
  max_tokens=2500
184
  )
185
  return response.choices[0].message.content
186
  except Exception as e:
187
+ st.error(f"Slide generation error: {str(e)}")
188
  return self._mock_slides(content)
189
+
190
  def _mock_slides(self, content):
191
  return f"""---
192
  marp: true
 
221
  1. Craft effective prompts for different scenarios
222
  2. Optimize prompts for specific AI models
223
  3. Analyze and refine prompt performance
224
+
225
  """
226
 
227
  class CodeAgent:
228
  def generate_code(self, content):
229
  if not openai_client:
230
  return self._mock_code(content)
231
+
232
  try:
233
  response = openai_client.chat.completions.create(
234
  model="gpt-4-turbo",
235
  messages=[
236
+ {
237
+ "role": "system",
238
+ "content": "You create practical code labs for technical workshops."
239
+ },
240
+ {
241
+ "role": "user",
242
+ "content": (
243
+ f"Create a Jupyter notebook with code exercises for this workshop: {json.dumps(content)}. "
244
+ "Include: setup instructions, practical exercises with solutions, "
245
+ "and real-world implementation examples. Use Python with popular AI libraries."
246
+ )
247
+ }
248
  ],
249
  temperature=0.3,
250
  max_tokens=2000
251
  )
252
  return response.choices[0].message.content
253
  except Exception as e:
254
+ st.error(f"Code generation error: {str(e)}")
255
  return self._mock_code(content)
256
+
257
  def _mock_code(self, content):
258
+ return f"""# {content['workshop_title']} - Code Labs
 
259
 
260
  import openai
261
  import pandas as pd
 
264
  def generate_response(prompt):
265
  response = openai.chat.completions.create(
266
  model="gpt-4",
267
+ messages=[{{"role": "user", "content": prompt}}]
268
  )
269
  return response.choices[0].message.content
270
 
271
+ # Test your function
272
  print(generate_response("Explain quantum computing in simple terms"))
273
 
274
  ## Exercise 2: Advanced Prompt Patterns
 
283
  def generate_design(self, slide_content):
284
  if not openai_client:
285
  return None
286
+
287
  try:
288
  response = openai_client.images.generate(
289
  prompt=f"Create a professional slide background for a corporate AI workshop about: {slide_content[:500]}",
 
292
  )
293
  return response.data[0].url
294
  except Exception as e:
295
+ st.error(f"Design generation error: {str(e)}")
296
  return None
297
 
298
  class VoiceoverAgent:
299
  def __init__(self):
300
+ self.api_key = ELEVENLABS_API_KEY
301
+ self.voice_id = "9BWtsMINqrJLrRacOk9x" # Default voice ID
302
+ self.model = "eleven_monolingual_v1"
303
+
 
 
 
 
 
 
 
 
 
 
304
  def generate_voiceover(self, text, voice_id=None):
305
+ if not self.api_key:
306
  return None
307
+
308
  try:
309
+ # Use custom voice if provided, otherwise use default
310
+ voice = voice_id if voice_id else self.voice_id
311
+
312
+ url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice}"
313
+ headers = {
314
+ "Accept": "audio/mpeg",
315
+ "Content-Type": "application/json",
316
+ "xi-api-key": self.api_key
317
+ }
318
+ data = {
319
+ "text": text,
320
+ "model_id": self.model,
321
+ "voice_settings": {
322
  "stability": 0.7,
323
  "similarity_boost": 0.8,
324
  "style": 0.5,
325
  "use_speaker_boost": True
326
  }
327
+ }
328
+ response = requests.post(url, json=data, headers=headers)
329
+
330
+ if response.status_code == 200:
331
+ return response.content
332
+ else:
333
+ st.error(f"Voiceover API error: {response.status_code} - {response.text}")
334
+ return None
335
  except Exception as e:
336
+ st.error(f"Voiceover generation error: {str(e)}")
337
  return None
338
+
339
+ def get_voices(self):
340
+ if not self.api_key:
341
+ return []
342
+
343
+ try:
344
+ url = "https://api.elevenlabs.io/v1/voices"
345
+ headers = {"xi-api-key": self.api_key}
346
+ response = requests.get(url, headers=headers)
347
+
348
+ if response.status_code == 200:
349
+ return response.json().get("voices", [])
350
+ return []
351
+ except Exception as e:
352
+ st.error(f"Voice loading error: {str(e)}")
353
+ return []
354
 
355
  # Initialize agents
356
  topic_agent = TopicAgent()
 
370
  initial_sidebar_state="expanded"
371
  )
372
 
373
+ # Custom CSS with fixed input styling
374
  st.markdown("""
375
  <style>
376
  .stApp {
377
  background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
378
  color: #fff;
379
  }
380
+ /* Fix for input text color */
381
  .stTextInput>div>div>input {
382
  color: #333 !important;
383
  background-color: #fff !important;
 
413
  display: flex;
414
  align-items: center;
415
  margin: 5px 0;
416
+ padding: 8px;
417
+ border-radius: 8px;
418
+ cursor: pointer;
419
+ transition: background 0.3s;
420
+ }
421
+ .voice-option:hover {
422
+ background: rgba(255,255,255,0.2);
423
+ }
424
+ .voice-option.selected {
425
+ background: rgba(0,180,155,0.3);
426
+ border: 2px solid #00b09b;
427
+ }
428
+ .voice-thumb {
429
+ width: 40px;
430
+ height: 40px;
431
+ border-radius: 50%;
432
+ margin-right: 10px;
433
+ object-fit: cover;
434
+ }
435
+ </style>
436
+ """, unsafe_allow_html=True)
437
+
438
+ # Header
439
+ col1, col2 = st.columns([1, 3])
440
+ with col1:
441
+ st.image("https://cdn-icons-png.flaticon.com/512/1995/1995485.png", width=100)
442
+ with col2:
443
+ st.title("πŸ€– Workshop in a Box Pro")
444
+ st.caption("Generate Premium Corporate AI Training Workshops with Voiceovers")
445
+
446
+ # Initialize session state
447
+ if 'workshop_topic' not in st.session_state:
448
+ st.session_state.workshop_topic = "Advanced Prompt Engineering"
449
+ if 'generated' not in st.session_state:
450
+ st.session_state.generated = False
451
+ if 'generating' not in st.session_state:
452
+ st.session_state.generating = False
453
+ if 'voiceovers' not in st.session_state:
454
+ st.session_state.voiceovers = {}
455
+ if 'selected_voice' not in st.session_state:
456
+ st.session_state.selected_voice = "21m00Tcm4TlvDq8ikWAM" # Default voice ID
457
+
458
+ # Sidebar configuration
459
+ with st.sidebar:
460
+ st.header("βš™οΈ Workshop Configuration")
461
+
462
+ # Workshop topic input with session state
463
+ st.session_state.workshop_topic = st.text_input(
464
+ "Workshop Topic",
465
+ st.session_state.workshop_topic,
466
+ key="topic_input",
467
+ help="Enter the main topic for your workshop"
468
+ )
469
+
470
+ # Validate topic input
471
+ if st.session_state.workshop_topic.strip() == "":
472
+ st.warning("Please enter a workshop topic")
473
+ st.stop()
474
+
475
+ duration = st.slider("Duration (hours)", 1.0, 8.0, 3.0, 0.5)
476
+ difficulty = st.selectbox("Difficulty Level",
477
+ ["Beginner", "Intermediate", "Advanced", "Expert"])
478
+ include_code = st.checkbox("Include Code Labs", True)
479
+ include_design = st.checkbox("Generate Visual Designs", True)
480
+ include_voiceover = st.checkbox("Generate Voiceovers", True)
481
+
482
+ # Voice selection
483
+ if include_voiceover:
484
+ st.subheader("πŸŽ™οΈ Voice Selection")
485
+
486
+ # Get available voices
487
+ voices = voiceover_agent.get_voices()
488
+
489
+ # If we have voices, let the user select one
490
+ if voices:
491
+ # Create 2 columns for voice selection
492
+ cols = st.columns(2)
493
+ for i, voice in enumerate(voices[:4]): # Show first 4 voices
494
+ with cols[i % 2]:
495
+ # Create a unique key for each voice button
496
+ voice_key = f"voice_{voice['voice_id']}"
497
+
498
+ # Display voice option
499
+ if st.button(
500
+ f"πŸ—£οΈ {voice['name']}",
501
+ key=voice_key,
502
+ use_container_width=True,
503
+ help=f"Select {voice['name']} voice"
504
+ ):
505
+ st.session_state.selected_voice = voice['voice_id']
506
+
507
+ # Show which voice is currently selected
508
+ selected_voice_name = next((v['name'] for v in voices if v['voice_id'] == st.session_state.selected_voice), "Default")
509
+ st.info(f"Selected Voice: **{selected_voice_name}**")
510
+ else:
511
+ if ELEVENLABS_API_KEY:
512
+ st.warning("Couldn't load voices. Using default voice.")
513
+ else:
514
+ st.warning("ElevenLabs API key not set. Voiceovers disabled.")
515
+
516
+ if st.button("✨ Generate Workshop", type="primary", use_container_width=True):
517
+ st.session_state.generating = True
518
+ st.session_state.voiceovers = {} # Reset previous voiceovers
519
+
520
+ # Generation pipeline
521
+ if st.session_state.generating:
522
+ with st.spinner(f"πŸš€ Creating your {st.session_state.workshop_topic} workshop..."):
523
+ start_time = time.time()
524
+
525
+ # Agent pipeline
526
+ outline = topic_agent.generate_outline(st.session_state.workshop_topic, duration, difficulty)
527
+ content = content_agent.generate_content(outline)
528
+ slides = slide_agent.generate_slides(content)
529
+ code_labs = code_agent.generate_code(content) if include_code else None
530
+ design_url = design_agent.generate_design(slides) if include_design else None
531
+
532
+ # Generate voiceovers if enabled
533
+ voiceovers = {}
534
+ if include_voiceover and ELEVENLABS_API_KEY:
535
+ for i, module in enumerate(content.get("modules", [])):
536
+ # Create a short intro for each module
537
+ intro_text = f"Welcome to Module {i+1}: {module['title']}. " + \
538
+ f"In this module, we'll cover: {', '.join(module.get('speaker_notes', []))[:300]}"
539
+
540
+ # Generate voiceover
541
+ audio_data = voiceover_agent.generate_voiceover(
542
+ intro_text,
543
+ st.session_state.selected_voice
544
+ )
545
+
546
+ if audio_data:
547
+ voiceovers[f"module_{i+1}_intro.mp3"] = audio_data
548
+
549
+ # Prepare download package
550
+ zip_buffer = io.BytesIO()
551
+ with zipfile.ZipFile(zip_buffer, "a") as zip_file:
552
+ zip_file.writestr("outline.json", json.dumps(outline, indent=2))
553
+ zip_file.writestr("content.json", json.dumps(content, indent=2))
554
+ zip_file.writestr("slides.md", slides)
555
+ if code_labs:
556
+ zip_file.writestr("code_labs.ipynb", code_labs)
557
+ if design_url:
558
+ try:
559
+ img_data = requests.get(design_url).content
560
+ zip_file.writestr("slide_design.png", img_data)
561
+ except:
562
+ pass
563
+ # Add voiceovers to ZIP
564
+ for filename, audio_data in voiceovers.items():
565
+ zip_file.writestr(f"voiceovers/{filename}", audio_data)
566
+
567
+ # Store results
568
+ st.session_state.outline = outline
569
+ st.session_state.content = content
570
+ st.session_state.slides = slides
571
+ st.session_state.code_labs = code_labs
572
+ st.session_state.design_url = design_url
573
+ st.session_state.voiceovers = voiceovers
574
+ st.session_state.zip_buffer = zip_buffer
575
+ st.session_state.gen_time = round(time.time() - start_time, 2)
576
+ st.session_state.generated = True
577
+ st.session_state.generating = False
578
+
579
+ # Results display
580
+ if st.session_state.generated:
581
+ st.success(f"βœ… {st.session_state.workshop_topic} workshop generated in {st.session_state.gen_time} seconds!")
582
+
583
+ # Download button
584
+ st.download_button(
585
+ label="πŸ“₯ Download Workshop Package",
586
+ data=st.session_state.zip_buffer.getvalue(),
587
+ file_name=f"{st.session_state.workshop_topic.replace(' ', '_')}_workshop.zip",
588
+ mime="application/zip",
589
+ use_container_width=True
590
+ )
591
+
592
+ # Preview sections
593
+ with st.expander("πŸ“ Workshop Outline", expanded=True):
594
+ st.json(st.session_state.outline)
595
+
596
+ with st.expander("πŸ“„ Content Script"):
597
+ st.write(st.session_state.content)
598
+
599
+ with st.expander("πŸ–₯️ Slide Deck Preview"):
600
+ st.markdown("```markdown\n" + textwrap.dedent(st.session_state.slides[:2000]) + "\n```")
601
+
602
+ if st.session_state.code_labs:
603
+ with st.expander("πŸ’» Code Labs"):
604
+ st.code(st.session_state.code_labs)
605
+
606
+ if st.session_state.design_url:
607
+ with st.expander("🎨 Generated Design"):
608
+ st.image(st.session_state.design_url, caption="Custom Slide Design")
609
+
610
+ # Voiceover player
611
+ if st.session_state.voiceovers:
612
+ with st.expander("πŸ”Š Voiceover Previews"):
613
+ for i, (filename, audio_bytes) in enumerate(st.session_state.voiceovers.items()):
614
+ module_num = filename.split("_")[1]
615
+ st.subheader(f"Module {module_num} Introduction")
616
+
617
+ # Create an audio player for each voiceover
618
+ st.audio(audio_bytes, format="audio/mp3")
619
+
620
+ # Add download button for individual voiceover
621
+ st.download_button(
622
+ label=f"Download Module {module_num} Voiceover",
623
+ data=audio_bytes,
624
+ file_name=filename,
625
+ mime="audio/mpeg",
626
+ key=f"voiceover_dl_{i}"
627
+ )
628
+ elif include_voiceover and ELEVENLABS_API_KEY:
629
+ st.warning("Voiceovers not generated. Check your ElevenLabs API key.")
630
+
631
+ # Sales and booking section
632
+ st.divider()
633
+ st.subheader("πŸš€ Ready to Deliver This Workshop?")
634
+ st.markdown(f"""
635
+ ### Premium {st.session_state.workshop_topic} Training Package
636
+ - **Live Workshop Delivery**: $10,000 per session
637
+ - **On-Demand Course**: $5,000 (unlimited access)
638
+ - **Pilot Program**: $1,000 refundable deposit
639
+ - **Voiceover Add-on**: $500 per module
640
+
641
+ ✨ **All inclusive**: Customization, materials, and follow-up support
642
+ """)
643
+
644
+ col1, col2 = st.columns(2)
645
+ with col1:
646
+ st.link_button("πŸ“… Book a Live Workshop", "https://calendly.com/your-link",
647
+ use_container_width=True)
648
+ with col2:
649
+ st.link_button("πŸ’³ Purchase On-Demand Course", "https://your-store.com",
650
+ use_container_width=True)
651
+
652
+ # Debug info
653
+ with st.sidebar:
654
+ st.divider()
655
+ if openai_client:
656
+ st.success("OpenAI API Connected")
657
+ else:
658
+ st.warning("OpenAI API not set - using enhanced mock data")
659
+
660
+ if ELEVENLABS_API_KEY:
661
+ st.success("ElevenLabs API Key Found")
662
+ elif include_voiceover:
663
+ st.warning("ElevenLabs API key not set")
664
+
665
+ st.info(f"""
666
+ **Current Workshop:**
667
+ {st.session_state.workshop_topic}
668
+
669
+ **Premium Features:**
670
+ - AI-generated voiceovers
671
+ - Professional slide designs
672
+ - Real-world case studies
673
+ - Practical code labs
674
+ """)
675
+
676
+ # How it works section
677
+ st.divider()
678
+ st.subheader("πŸ’‘ How It Works")
679
+ st.markdown("""
680
+ 1. **Configure** your workshop topic and parameters
681
+ 2. **Generate** premium training materials with voiceovers
682
+ 3. **Customize** the content to your specific needs
683
+ 4. **Deliver** high-value corporate training at $10K/session
684
+ 5. **Reuse** the materials for unlimited revenue
685
+
686
+ *"The voiceover feature helped me create on-demand courses that sold for $5K each"* - Michael L., AI Consultant
687
+ """)