mgbam commited on
Commit
a2a4fa2
·
verified ·
1 Parent(s): eb9907d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +633 -118
app.py CHANGED
@@ -1,119 +1,634 @@
1
- import os
2
- import json
3
- import tempfile
4
- from zipfile import ZipFile
5
-
6
- import gradio as gr
7
- from agents import Agent, handoff, Runner
8
- from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
9
- from TTS.api import TTS
10
-
11
- # Initialize Coqui TTS (Tacotron2-DDC)
12
- tts = TTS(
13
- model_name="tts_models/en/ljspeech/tacotron2-DDC",
14
- progress_bar=False,
15
- gpu=False
16
- )
17
-
18
- def generate_tts_audio(script: str) -> str:
19
- """
20
- Synthesize speech using Coqui TTS and save to a WAV file.
21
- Returns the local file path to the audio.
22
- """
23
- out_path = os.path.join(tempfile.gettempdir(), "voiceover.wav")
24
- tts.tts_to_file(text=script, file_path=out_path)
25
- return out_path
26
-
27
- # Agents definitions
28
- topic_agent = Agent(
29
- name="Topic Agent",
30
- instructions=f"{RECOMMENDED_PROMPT_PREFIX}\n"
31
- "You are given a workshop topic and audience. Draft a structured learning path: goals, 4 modules, and a hands-on exercise for each."
32
- )
33
- content_agent = Agent(
34
- name="Content Agent",
35
- instructions=f"{RECOMMENDED_PROMPT_PREFIX}\n"
36
- "Convert the outline into detailed module scripts, speaker notes, and 3 quiz questions per module."
37
- )
38
- slide_agent = Agent(
39
- name="Slide Agent",
40
- instructions=f"{RECOMMENDED_PROMPT_PREFIX}\n"
41
- "Given module content, produce slide JSON with title, bullet points, and design hints."
42
- )
43
- code_agent = Agent(
44
- name="Code Agent",
45
- instructions=f"{RECOMMENDED_PROMPT_PREFIX}\n"
46
- "Generate runnable Python code snippets or a Colab notebook for hands-on labs in each module."
47
- )
48
- voice_agent = Agent(
49
- name="Voiceover Agent",
50
- instructions=f"{RECOMMENDED_PROMPT_PREFIX}\n"
51
- "Create a 1-2 minute voiceover script. Return JSON with key 'script'."
52
- )
53
-
54
- # Orchestrator with corrected handoff signature
55
- document_orchestrator = Agent(
56
- name="Workshop Orchestrator",
57
- instructions="Invoke: topic_agent, content_agent, slide_agent, code_agent, voice_agent (optional); collect outputs.",
58
- handoffs=[
59
- handoff(topic_agent, "outline"),
60
- handoff(content_agent, "content"),
61
- handoff(slide_agent, "slides"),
62
- handoff(code_agent, "code_labs"),
63
- handoff(voice_agent, "voiceover", optional=True),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  ]
65
- )
66
-
67
- runner = Runner()
68
-
69
- # Bundle builder
70
- def build_workshop_bundle(topic: str, audience: str):
71
- prompt = f"Create a {topic} workshop for {audience}."
72
- results = runner.run(document_orchestrator, prompt).outputs
73
-
74
- # Synthesize voiceover
75
- voice_info = results.get("voiceover", {})
76
- audio_path = None
77
- if isinstance(voice_info, dict) and "script" in voice_info:
78
- audio_path = generate_tts_audio(voice_info["script"])
79
-
80
- # Render slides to HTML
81
- slides_json = results.get("slides", {})
82
- with open("static/slides_template.html") as f:
83
- template = f.read()
84
- slide_html = template.replace("{{SLIDES_JSON}}", json.dumps(slides_json))
85
-
86
- # Bundle outputs into ZIP
87
- tmpdir = tempfile.mkdtemp()
88
- zip_path = os.path.join(tmpdir, "workshop_bundle.zip")
89
- with ZipFile(zip_path, "w") as zipf:
90
- for name, content in [
91
- ("workshop_outputs.json", json.dumps(results, indent=2)),
92
- ("slides.json", json.dumps(slides_json, indent=2)),
93
- ("slides.html", slide_html),
94
- ("code_labs.py", results.get("code_labs", "")),
95
- ]:
96
- p = os.path.join(tmpdir, name)
97
- with open(p, "w") as file:
98
- file.write(content)
99
- zipf.write(p, arcname=name)
100
- if audio_path and os.path.exists(audio_path):
101
- zipf.write(audio_path, os.path.basename(audio_path))
102
- return slide_html, audio_path, zip_path
103
-
104
- # Gradio UI setup
105
- def run_app(topic, audience):
106
- return build_workshop_bundle(topic, audience)
107
-
108
- with gr.Blocks(title="🚀 Workshop in a Box") as demo:
109
- gr.Markdown("# Workshop in a Box")
110
- topic = gr.Textbox(label="Workshop Topic", placeholder="e.g., AI Agents 101")
111
- audience = gr.Textbox(label="Audience", placeholder="e.g., Product Managers")
112
- btn = gr.Button("Generate Workshop")
113
- slide_preview = gr.HTML(label="Slide Preview")
114
- audio_player = gr.Audio(label="Voiceover Preview", interactive=True)
115
- download = gr.File(label="Download ZIP")
116
- btn.click(fn=run_app, inputs=[topic, audience], outputs=[slide_preview, audio_player, download])
117
-
118
- if __name__ == "__main__":
119
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { ChevronRight, Play, Download, Users, Clock, DollarSign, CheckCircle, Loader, Zap, BookOpen, Code, Mic, AlertCircle } from 'lucide-react';
3
+
4
+ const WorkshopGenerator = () => {
5
+ const [currentStep, setCurrentStep] = useState('input');
6
+ const [workshopData, setWorkshopData] = useState({
7
+ topic: '',
8
+ audience: '',
9
+ duration: '90',
10
+ difficulty: 'intermediate'
11
+ });
12
+ const [generatedContent, setGeneratedContent] = useState(null);
13
+ const [isGenerating, setIsGenerating] = useState(false);
14
+ const [activeAgent, setActiveAgent] = useState('');
15
+ const [error, setError] = useState('');
16
+
17
+ const agents = [
18
+ { id: 'topic', name: 'Topic Agent', icon: BookOpen, description: 'Analyzing topic and creating learning path' },
19
+ { id: 'content', name: 'Content Agent', icon: Users, description: 'Writing module scripts and speaker notes' },
20
+ { id: 'slide', name: 'Slide Agent', icon: Play, description: 'Generating presentation slides' },
21
+ { id: 'code', name: 'Code Agent', icon: Code, description: 'Creating hands-on exercises' }
22
+ ];
23
+
24
+ // Real Claude API call function
25
+ const callClaudeAPI = async (prompt, maxTokens = 2000) => {
26
+ try {
27
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
28
+ method: "POST",
29
+ headers: {
30
+ "Content-Type": "application/json",
31
+ },
32
+ body: JSON.stringify({
33
+ model: "claude-sonnet-4-20250514",
34
+ max_tokens: maxTokens,
35
+ messages: [
36
+ { role: "user", content: prompt }
37
+ ]
38
+ })
39
+ });
40
+
41
+ if (!response.ok) {
42
+ throw new Error(`API request failed: ${response.status}`);
43
+ }
44
+
45
+ const data = await response.json();
46
+ return data.content[0].text;
47
+ } catch (error) {
48
+ console.error("Claude API Error:", error);
49
+ throw error;
50
+ }
51
+ };
52
+
53
+ // Topic Agent - Creates learning outline
54
+ const topicAgent = async (topic, audience, duration, difficulty) => {
55
+ const prompt = `You are an expert corporate trainer creating a workshop outline.
56
+
57
+ Topic: ${topic}
58
+ Audience: ${audience}
59
+ Duration: ${duration} minutes
60
+ Difficulty: ${difficulty}
61
+
62
+ Create a comprehensive workshop structure. Respond ONLY with valid JSON in this exact format:
63
+
64
+ {
65
+ "title": "Workshop title",
66
+ "objective": "Main learning objective",
67
+ "modules": [
68
+ {
69
+ "name": "Module 1 name",
70
+ "duration": 20,
71
+ "objectives": ["objective 1", "objective 2"],
72
+ "key_points": ["point 1", "point 2", "point 3"]
73
+ }
74
+ ],
75
+ "target_outcomes": ["outcome 1", "outcome 2"],
76
+ "prerequisites": ["prereq 1", "prereq 2"],
77
+ "difficulty_level": "${difficulty}"
78
+ }
79
+
80
+ DO NOT include any text outside the JSON structure.`;
81
+
82
+ return await callClaudeAPI(prompt, 1500);
83
+ };
84
+
85
+ // Content Agent - Creates detailed content
86
+ const contentAgent = async (topicStructure, topic, audience) => {
87
+ const prompt = `You are a corporate training content writer. Create detailed workshop content based on this structure:
88
+
89
+ ${topicStructure}
90
+
91
+ Topic: ${topic}
92
+ Audience: ${audience}
93
+
94
+ Generate comprehensive content. Respond ONLY with valid JSON:
95
+
96
+ {
97
+ "speaker_notes": {
98
+ "introduction": "Detailed intro script with talking points",
99
+ "modules": [
100
+ {
101
+ "module_name": "Name",
102
+ "opening": "Module opening script",
103
+ "main_content": "Detailed teaching content with examples",
104
+ "activities": "Instructions for hands-on activities",
105
+ "closing": "Module wrap-up and transition"
106
+ }
107
+ ],
108
+ "conclusion": "Workshop conclusion script"
109
+ },
110
+ "participant_materials": {
111
+ "worksheets": ["Worksheet 1 description", "Worksheet 2 description"],
112
+ "reference_guides": ["Guide 1 content outline", "Guide 2 content outline"]
113
+ },
114
+ "assessment": {
115
+ "quiz_questions": [
116
+ {
117
+ "question": "Question text",
118
+ "type": "multiple_choice",
119
+ "options": ["A", "B", "C", "D"],
120
+ "correct_answer": "A",
121
+ "explanation": "Why this is correct"
122
+ }
123
  ]
124
+ }
125
+ }
126
+
127
+ DO NOT include any text outside the JSON structure.`;
128
+
129
+ return await callClaudeAPI(prompt, 3000);
130
+ };
131
+
132
+ // Slide Agent - Creates presentation structure
133
+ const slideAgent = async (contentData, topic) => {
134
+ const prompt = `You are a presentation designer. Create a professional slide deck structure based on this workshop content:
135
+
136
+ ${contentData}
137
+
138
+ Topic: ${topic}
139
+
140
+ Generate slide-by-slide content. Respond ONLY with valid JSON:
141
+
142
+ {
143
+ "slides": [
144
+ {
145
+ "slide_number": 1,
146
+ "title": "Slide title",
147
+ "type": "title_slide",
148
+ "content": {
149
+ "main_text": "Primary text content",
150
+ "bullet_points": ["Point 1", "Point 2"],
151
+ "notes": "Speaker notes for this slide"
152
+ },
153
+ "design_hints": {
154
+ "layout": "title_and_content",
155
+ "visual_elements": ["chart", "diagram", "icon"],
156
+ "color_scheme": "professional_blue"
157
+ }
158
+ }
159
+ ],
160
+ "slide_count": 24,
161
+ "estimated_presentation_time": "75 minutes"
162
+ }
163
+
164
+ Create exactly enough slides for the workshop duration. DO NOT include any text outside the JSON structure.`;
165
+
166
+ return await callClaudeAPI(prompt, 4000);
167
+ };
168
+
169
+ // Code Agent - Creates practical exercises
170
+ const codeAgent = async (topicStructure, topic, audience, difficulty) => {
171
+ const prompt = `You are a technical training specialist. Create hands-on coding exercises based on:
172
+
173
+ ${topicStructure}
174
+
175
+ Topic: ${topic}
176
+ Audience: ${audience}
177
+ Difficulty: ${difficulty}
178
+
179
+ Generate practical coding exercises. Respond ONLY with valid JSON:
180
+
181
+ {
182
+ "exercises": [
183
+ {
184
+ "title": "Exercise title",
185
+ "duration": 15,
186
+ "difficulty": "beginner",
187
+ "description": "What participants will build",
188
+ "setup_instructions": "Step-by-step setup",
189
+ "starter_code": "// Starter code template",
190
+ "solution_code": "// Complete solution",
191
+ "learning_objectives": ["objective 1", "objective 2"],
192
+ "common_mistakes": ["mistake 1", "mistake 2"]
193
+ }
194
+ ],
195
+ "code_demos": [
196
+ {
197
+ "title": "Demo title",
198
+ "code": "# Complete demo code",
199
+ "explanation": "Step-by-step explanation"
200
+ }
201
+ ],
202
+ "resources": {
203
+ "documentation_links": ["https://example.com/docs"],
204
+ "additional_tools": ["Tool 1", "Tool 2"]
205
+ }
206
+ }
207
+
208
+ Focus on practical, business-relevant examples. DO NOT include any text outside the JSON structure.`;
209
+
210
+ return await callClaudeAPI(prompt, 3000);
211
+ };
212
+
213
+ const generateWorkshop = async () => {
214
+ setIsGenerating(true);
215
+ setCurrentStep('generating');
216
+ setError('');
217
+
218
+ try {
219
+ // Step 1: Topic Agent
220
+ setActiveAgent('topic');
221
+ const topicResponse = await topicAgent(
222
+ workshopData.topic,
223
+ workshopData.audience,
224
+ workshopData.duration,
225
+ workshopData.difficulty
226
+ );
227
+
228
+ let topicData;
229
+ try {
230
+ // Clean the response to extract JSON
231
+ const cleanResponse = topicResponse.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
232
+ topicData = JSON.parse(cleanResponse);
233
+ } catch (parseError) {
234
+ console.error("Topic parsing error:", parseError);
235
+ throw new Error("Failed to parse topic structure");
236
+ }
237
+
238
+ // Step 2: Content Agent
239
+ setActiveAgent('content');
240
+ const contentResponse = await contentAgent(
241
+ JSON.stringify(topicData),
242
+ workshopData.topic,
243
+ workshopData.audience
244
+ );
245
+
246
+ let contentData;
247
+ try {
248
+ const cleanResponse = contentResponse.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
249
+ contentData = JSON.parse(cleanResponse);
250
+ } catch (parseError) {
251
+ console.error("Content parsing error:", parseError);
252
+ throw new Error("Failed to parse content structure");
253
+ }
254
+
255
+ // Step 3: Slide Agent
256
+ setActiveAgent('slide');
257
+ const slideResponse = await slideAgent(
258
+ JSON.stringify(contentData),
259
+ workshopData.topic
260
+ );
261
+
262
+ let slideData;
263
+ try {
264
+ const cleanResponse = slideResponse.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
265
+ slideData = JSON.parse(cleanResponse);
266
+ } catch (parseError) {
267
+ console.error("Slide parsing error:", parseError);
268
+ throw new Error("Failed to parse slide structure");
269
+ }
270
+
271
+ // Step 4: Code Agent
272
+ setActiveAgent('code');
273
+ const codeResponse = await codeAgent(
274
+ JSON.stringify(topicData),
275
+ workshopData.topic,
276
+ workshopData.audience,
277
+ workshopData.difficulty
278
+ );
279
+
280
+ let codeData;
281
+ try {
282
+ const cleanResponse = codeResponse.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
283
+ codeData = JSON.parse(cleanResponse);
284
+ } catch (parseError) {
285
+ console.error("Code parsing error:", parseError);
286
+ throw new Error("Failed to parse code structure");
287
+ }
288
+
289
+ // Combine all generated content
290
+ setGeneratedContent({
291
+ topic: topicData,
292
+ content: contentData,
293
+ slides: slideData,
294
+ code: codeData,
295
+ metadata: {
296
+ title: topicData.title,
297
+ modules: topicData.modules?.length || 4,
298
+ slideCount: slideData.slide_count || slideData.slides?.length || 0,
299
+ exercises: codeData.exercises?.length || 0,
300
+ estimatedValue: calculateWorkshopValue(workshopData.duration, workshopData.audience),
301
+ generatedAt: new Date().toISOString()
302
+ }
303
+ });
304
+
305
+ setCurrentStep('preview');
306
+ } catch (error) {
307
+ console.error("Workshop generation error:", error);
308
+ setError(`Generation failed: ${error.message}`);
309
+ setCurrentStep('input');
310
+ } finally {
311
+ setIsGenerating(false);
312
+ setActiveAgent('');
313
+ }
314
+ };
315
+
316
+ const calculateWorkshopValue = (duration, audience) => {
317
+ const basePrices = {
318
+ 'executives': 200,
319
+ 'managers': 150,
320
+ 'developers': 120,
321
+ 'analysts': 100,
322
+ 'mixed': 140
323
+ };
324
+
325
+ const basePrice = basePrices[audience] || 120;
326
+ const durationMultiplier = parseInt(duration) / 60;
327
+ const estimatedValue = Math.round(basePrice * durationMultiplier) * 100;
328
+
329
+ return `$${estimatedValue.toLocaleString()}`;
330
+ };
331
+
332
+ const downloadWorkshop = () => {
333
+ if (!generatedContent) return;
334
+
335
+ const workshopPackage = {
336
+ ...generatedContent,
337
+ generated_by: "AI Workshop in a Box",
338
+ export_date: new Date().toISOString(),
339
+ workshop_config: workshopData
340
+ };
341
+
342
+ const dataStr = JSON.stringify(workshopPackage, null, 2);
343
+ const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
344
+
345
+ const exportFileDefaultName = `workshop-${workshopData.topic.replace(/\s+/g, '-').toLowerCase()}.json`;
346
+
347
+ const linkElement = document.createElement('a');
348
+ linkElement.setAttribute('href', dataUri);
349
+ linkElement.setAttribute('download', exportFileDefaultName);
350
+ linkElement.click();
351
+ };
352
+
353
+ const handleInputChange = (field, value) => {
354
+ setWorkshopData(prev => ({ ...prev, [field]: value }));
355
+ };
356
+
357
+ return (
358
+ <div className="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 text-white">
359
+ {/* Header */}
360
+ <div className="bg-black/20 backdrop-blur-lg border-b border-white/10">
361
+ <div className="max-w-7xl mx-auto px-6 py-4">
362
+ <div className="flex items-center justify-between">
363
+ <div className="flex items-center space-x-3">
364
+ <div className="w-10 h-10 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center">
365
+ <Zap className="w-6 h-6 text-white" />
366
+ </div>
367
+ <div>
368
+ <h1 className="text-2xl font-bold">AI Workshop in a Box</h1>
369
+ <p className="text-blue-200 text-sm">Functional Multi-Agent Generator</p>
370
+ </div>
371
+ </div>
372
+ <div className="flex items-center space-x-4">
373
+ <div className="text-right">
374
+ <div className="text-2xl font-bold text-green-400">$10K+</div>
375
+ <div className="text-sm text-gray-300">per workshop</div>
376
+ </div>
377
+ </div>
378
+ </div>
379
+ </div>
380
+ </div>
381
+
382
+ <div className="max-w-7xl mx-auto px-6 py-8">
383
+ {/* Error Display */}
384
+ {error && (
385
+ <div className="max-w-2xl mx-auto mb-6">
386
+ <div className="bg-red-500/20 border border-red-400 rounded-lg p-4 flex items-center space-x-3">
387
+ <AlertCircle className="w-5 h-5 text-red-400" />
388
+ <span className="text-red-200">{error}</span>
389
+ </div>
390
+ </div>
391
+ )}
392
+
393
+ {/* Step 1: Input */}
394
+ {currentStep === 'input' && (
395
+ <div className="max-w-2xl mx-auto">
396
+ <div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 border border-white/20">
397
+ <h2 className="text-3xl font-bold mb-6 text-center">Create Your Workshop</h2>
398
+
399
+ <div className="space-y-6">
400
+ <div>
401
+ <label className="block text-sm font-medium mb-2">Workshop Topic</label>
402
+ <input
403
+ type="text"
404
+ placeholder="e.g., Advanced Prompt Engineering, AI Agents for Business"
405
+ className="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg focus:ring-2 focus:ring-cyan-400 focus:border-transparent text-white placeholder-gray-300"
406
+ value={workshopData.topic}
407
+ onChange={(e) => handleInputChange('topic', e.target.value)}
408
+ />
409
+ </div>
410
+
411
+ <div>
412
+ <label className="block text-sm font-medium mb-2">Target Audience</label>
413
+ <select
414
+ className="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg focus:ring-2 focus:ring-cyan-400 text-white"
415
+ value={workshopData.audience}
416
+ onChange={(e) => handleInputChange('audience', e.target.value)}
417
+ >
418
+ <option value="">Select audience</option>
419
+ <option value="executives">C-Suite Executives</option>
420
+ <option value="managers">Product Managers</option>
421
+ <option value="developers">Developers & Engineers</option>
422
+ <option value="analysts">Data Analysts</option>
423
+ <option value="mixed">Mixed Technical Team</option>
424
+ </select>
425
+ </div>
426
+
427
+ <div className="grid grid-cols-2 gap-4">
428
+ <div>
429
+ <label className="block text-sm font-medium mb-2">Duration (minutes)</label>
430
+ <select
431
+ className="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg focus:ring-2 focus:ring-cyan-400 text-white"
432
+ value={workshopData.duration}
433
+ onChange={(e) => handleInputChange('duration', e.target.value)}
434
+ >
435
+ <option value="60">60 minutes</option>
436
+ <option value="90">90 minutes</option>
437
+ <option value="120">2 hours</option>
438
+ <option value="240">Half day</option>
439
+ </select>
440
+ </div>
441
+
442
+ <div>
443
+ <label className="block text-sm font-medium mb-2">Difficulty Level</label>
444
+ <select
445
+ className="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg focus:ring-2 focus:ring-cyan-400 text-white"
446
+ value={workshopData.difficulty}
447
+ onChange={(e) => handleInputChange('difficulty', e.target.value)}
448
+ >
449
+ <option value="beginner">Beginner</option>
450
+ <option value="intermediate">Intermediate</option>
451
+ <option value="advanced">Advanced</option>
452
+ </select>
453
+ </div>
454
+ </div>
455
+
456
+ <button
457
+ onClick={generateWorkshop}
458
+ disabled={!workshopData.topic || !workshopData.audience || isGenerating}
459
+ className="w-full py-4 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-lg font-semibold text-lg hover:from-cyan-600 hover:to-blue-700 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2"
460
+ >
461
+ <Zap className="w-5 h-5" />
462
+ <span>Generate Workshop</span>
463
+ <ChevronRight className="w-5 h-5" />
464
+ </button>
465
+ </div>
466
+ </div>
467
+ </div>
468
+ )}
469
+
470
+ {/* Step 2: Generation Process */}
471
+ {currentStep === 'generating' && (
472
+ <div className="max-w-4xl mx-auto">
473
+ <div className="text-center mb-8">
474
+ <h2 className="text-3xl font-bold mb-4">Generating Your Workshop</h2>
475
+ <p className="text-blue-200">Multi-agent system creating your complete training package...</p>
476
+ </div>
477
+
478
+ <div className="grid gap-6">
479
+ {agents.map((agent, index) => {
480
+ const isActive = activeAgent === agent.id;
481
+ const isComplete = agents.findIndex(a => a.id === activeAgent) > index;
482
+ const Icon = agent.icon;
483
+
484
+ return (
485
+ <div
486
+ key={agent.id}
487
+ className={`p-6 rounded-xl border transition-all duration-500 ${
488
+ isActive
489
+ ? 'bg-cyan-500/20 border-cyan-400 shadow-lg shadow-cyan-500/25'
490
+ : isComplete
491
+ ? 'bg-green-500/20 border-green-400'
492
+ : 'bg-white/5 border-white/10'
493
+ }`}
494
+ >
495
+ <div className="flex items-center space-x-4">
496
+ <div className={`p-3 rounded-lg ${
497
+ isActive ? 'bg-cyan-500' : isComplete ? 'bg-green-500' : 'bg-white/10'
498
+ }`}>
499
+ {isActive ? (
500
+ <Loader className="w-6 h-6 animate-spin" />
501
+ ) : isComplete ? (
502
+ <CheckCircle className="w-6 h-6" />
503
+ ) : (
504
+ <Icon className="w-6 h-6" />
505
+ )}
506
+ </div>
507
+ <div className="flex-1">
508
+ <h3 className="font-semibold text-lg">{agent.name}</h3>
509
+ <p className="text-gray-300">{agent.description}</p>
510
+ </div>
511
+ {isActive && (
512
+ <div className="flex space-x-1">
513
+ <div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce"></div>
514
+ <div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style={{animationDelay: '0.1s'}}></div>
515
+ <div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style={{animationDelay: '0.2s'}}></div>
516
+ </div>
517
+ )}
518
+ </div>
519
+ </div>
520
+ );
521
+ })}
522
+ </div>
523
+ </div>
524
+ )}
525
+
526
+ {/* Step 3: Preview & Download */}
527
+ {currentStep === 'preview' && generatedContent && (
528
+ <div className="max-w-6xl mx-auto">
529
+ <div className="text-center mb-8">
530
+ <h2 className="text-3xl font-bold mb-4">Workshop Generated Successfully! 🎉</h2>
531
+ <p className="text-blue-200 text-lg">Your complete training package is ready for delivery</p>
532
+ </div>
533
+
534
+ <div className="grid lg:grid-cols-3 gap-8">
535
+ {/* Workshop Overview */}
536
+ <div className="lg:col-span-2">
537
+ <div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 border border-white/20">
538
+ <h3 className="text-2xl font-bold mb-6">{generatedContent.metadata.title}</h3>
539
+
540
+ <div className="grid md:grid-cols-3 gap-6 mb-8">
541
+ <div className="text-center">
542
+ <div className="text-3xl font-bold text-cyan-400">{generatedContent.metadata.slideCount}</div>
543
+ <div className="text-sm text-gray-300">Slides</div>
544
+ </div>
545
+ <div className="text-center">
546
+ <div className="text-3xl font-bold text-green-400">{generatedContent.metadata.exercises}</div>
547
+ <div className="text-sm text-gray-300">Exercises</div>
548
+ </div>
549
+ <div className="text-center">
550
+ <div className="text-3xl font-bold text-purple-400">{workshopData.duration}m</div>
551
+ <div className="text-sm text-gray-300">Duration</div>
552
+ </div>
553
+ </div>
554
+
555
+ <div className="space-y-3">
556
+ <h4 className="font-semibold text-lg mb-3">Workshop Modules:</h4>
557
+ {generatedContent.topic.modules?.map((module, index) => (
558
+ <div key={index} className="flex items-center space-x-3 p-3 bg-white/5 rounded-lg">
559
+ <div className="w-8 h-8 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full flex items-center justify-center text-sm font-bold">
560
+ {index + 1}
561
+ </div>
562
+ <div>
563
+ <div className="font-medium">{module.name}</div>
564
+ <div className="text-sm text-gray-300">{module.duration} minutes</div>
565
+ </div>
566
+ </div>
567
+ )) || <div className="text-gray-400">Modules loaded successfully</div>}
568
+ </div>
569
+ </div>
570
+ </div>
571
+
572
+ {/* Actions & Pricing */}
573
+ <div className="space-y-6">
574
+ <div className="bg-gradient-to-r from-green-500/20 to-emerald-500/20 border border-green-400/30 rounded-2xl p-6 text-center">
575
+ <DollarSign className="w-12 h-12 text-green-400 mx-auto mb-4" />
576
+ <div className="text-3xl font-bold text-green-400 mb-2">{generatedContent.metadata.estimatedValue}</div>
577
+ <div className="text-sm text-green-200">Estimated Workshop Value</div>
578
+ </div>
579
+
580
+ <div className="space-y-4">
581
+ <button
582
+ onClick={downloadWorkshop}
583
+ className="w-full py-3 bg-gradient-to-r from-cyan-500 to-blue-600 rounded-lg font-semibold hover:from-cyan-600 hover:to-blue-700 transition-all duration-200 flex items-center justify-center space-x-2"
584
+ >
585
+ <Download className="w-5 h-5" />
586
+ <span>Download Package</span>
587
+ </button>
588
+
589
+ <button className="w-full py-3 bg-white/10 border border-white/20 rounded-lg font-semibold hover:bg-white/20 transition-all duration-200 flex items-center justify-center space-x-2">
590
+ <Play className="w-5 h-5" />
591
+ <span>Preview Content</span>
592
+ </button>
593
+
594
+ <button className="w-full py-3 bg-purple-600/20 border border-purple-400/30 rounded-lg font-semibold hover:bg-purple-600/30 transition-all duration-200 flex items-center justify-center space-x-2">
595
+ <Users className="w-5 h-5" />
596
+ <span>Book Client Demo</span>
597
+ </button>
598
+ </div>
599
+
600
+ <div className="bg-white/5 rounded-xl p-4">
601
+ <h4 className="font-semibold mb-2">Package Includes:</h4>
602
+ <ul className="text-sm space-y-1 text-gray-300">
603
+ <li>• Complete slide deck</li>
604
+ <li>• Speaker notes & scripts</li>
605
+ <li>• Hands-on exercises</li>
606
+ <li>• Code examples</li>
607
+ <li>• Assessment materials</li>
608
+ <li>• JSON export for customization</li>
609
+ </ul>
610
+ </div>
611
+ </div>
612
+ </div>
613
+
614
+ <div className="mt-8 text-center">
615
+ <button
616
+ onClick={() => {
617
+ setCurrentStep('input');
618
+ setGeneratedContent(null);
619
+ setWorkshopData({ topic: '', audience: '', duration: '90', difficulty: 'intermediate' });
620
+ setError('');
621
+ }}
622
+ className="px-6 py-3 bg-white/10 border border-white/20 rounded-lg hover:bg-white/20 transition-all duration-200"
623
+ >
624
+ Generate Another Workshop
625
+ </button>
626
+ </div>
627
+ </div>
628
+ )}
629
+ </div>
630
+ </div>
631
+ );
632
+ };
633
+
634
+ export default WorkshopGenerator;