oussnaji commited on
Commit
bab012b
·
verified ·
1 Parent(s): 163bf63

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +521 -0
app.py ADDED
@@ -0,0 +1,521 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import anthropic
3
+ import requests
4
+ import base64
5
+ import json
6
+ import time
7
+ import os
8
+ from typing import Dict, List, Any
9
+ import fal_client
10
+ from dotenv import load_dotenv
11
+ from IPython.display import Image as IPImage, display
12
+
13
+ # Page config
14
+ st.set_page_config(
15
+ page_title="Interactive Course Preview Generator",
16
+ layout="wide",
17
+ initial_sidebar_state="expanded"
18
+ )
19
+
20
+ # Custom CSS for modern, minimalist design
21
+ st.markdown("""
22
+ <style>
23
+ .main {background-color: #f8f9fa;}
24
+ .stButton>button {
25
+ background-color: #4c6ef5;
26
+ color: white;
27
+ border-radius: 4px;
28
+ border: none;
29
+ padding: 0.5rem 1rem;
30
+ }
31
+ .content-box {
32
+ background-color: white;
33
+ padding: 1.5rem;
34
+ border-radius: 8px;
35
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
36
+ margin: 1rem 0;
37
+ }
38
+ .section-title {
39
+ color: #4c6ef5;
40
+ font-size: 1.2rem;
41
+ font-weight: bold;
42
+ margin-bottom: 1rem;
43
+ }
44
+ </style>
45
+ """, unsafe_allow_html=True)
46
+
47
+ st.markdown("""
48
+ <style>
49
+ .main {background-color: #f8f9fa;}
50
+ .subtitle {
51
+ color: #6c757d;
52
+ font-size: 0.9rem;
53
+ font-style: italic;
54
+ margin-top: 0.5rem;
55
+ }
56
+ .preview-content {
57
+ background-color: #f8f9fa;
58
+ padding: 1rem;
59
+ border-left: 3px solid #4c6ef5;
60
+ margin: 1rem 0;
61
+ }
62
+ .script-content {
63
+ background-color: #e9ecef;
64
+ padding: 1rem;
65
+ border-radius: 4px;
66
+ margin: 1rem 0;
67
+ }
68
+ </style>
69
+ """, unsafe_allow_html=True)
70
+
71
+ # Templates for content generation
72
+ COURSE_TEMPLATE = """Create a concise course outline for:
73
+ Topic: {topic}
74
+ Level: {level}
75
+ Duration: {duration}
76
+
77
+ Include:
78
+ 1. Brief course description (2-3 sentences)
79
+ 2. 4-5 main sections
80
+ 3. For each section:
81
+ - Title
82
+ - Brief description (1 sentence)
83
+ - 2-3 key concepts
84
+
85
+ Format it clearly and professionally."""
86
+
87
+ PREVIEW_TEMPLATE = """Create content for two brief preview slides about the concept: {concept}
88
+ For each slide include EXACTLY in this order:
89
+ 1. Slide Title
90
+ 2. Content (3 brief bullet points maximum)
91
+ 3. Teacher Script (2-3 sentences maximum)
92
+ 4. Image Description (one sentence describing a minimalist visual)
93
+
94
+ Format each slide clearly with these exact headers:
95
+ "Slide 1:", "Content:", "Teacher Script:", "Image Description:"
96
+ "Slide 2:", "Content:", "Teacher Script:", "Image Description:"
97
+ """
98
+
99
+ INSTRUCTOR_INTRO_TEMPLATE = """Create a very brief welcome message (2 sentences maximum) for:
100
+ Instructor: {name}
101
+ Course: {topic}
102
+ Style: {style}
103
+
104
+ Keep it natural and concise."""
105
+
106
+ # API Integration Functions
107
+ def generate_image(description: str) -> str:
108
+ """Generate image using verified Fal.ai implementation with proper waiting"""
109
+ try:
110
+ with st.spinner(f"Generating image for: {description}"):
111
+ handler = fal_client.submit(
112
+ "fal-ai/flux-pro/v1.1-ultra",
113
+ arguments={
114
+ "prompt": f"Professional minimalist educational visual: {description}",
115
+ "num_images": 1
116
+ }
117
+ )
118
+
119
+ # Extended wait time with status check
120
+ for _ in range(30): # Maximum 30 seconds wait
121
+ time.sleep(1)
122
+ result = fal_client.result("fal-ai/flux-pro/v1.1-ultra", handler.request_id)
123
+ if result and "images" in result and result["images"]:
124
+ return result["images"][0]["url"]
125
+ return None
126
+ except Exception as e:
127
+ st.error(f"Image generation error: {e}")
128
+ return None
129
+
130
+ def generate_and_get_image(prompt: str) -> str:
131
+ """Generate and get image using verified Fal.ai implementation"""
132
+ try:
133
+ with st.spinner(f"Generating image..."):
134
+ # Submit request
135
+ handler = fal_client.submit(
136
+ "fal-ai/flux-pro/v1.1-ultra",
137
+ arguments={"prompt": prompt, "num_images": 1}
138
+ )
139
+ request_id = handler.request_id
140
+
141
+ if request_id:
142
+ time.sleep(10) # Wait for generation
143
+ # Get result
144
+ result = fal_client.result("fal-ai/flux-pro/v1.1-ultra", request_id)
145
+ if result and "images" in result and result["images"]:
146
+ return result["images"][0]["url"]
147
+ except Exception as e:
148
+ st.error(f"Image generation error: {e}")
149
+ return None
150
+
151
+ def create_voice_preview(text: str, voice_description: str) -> bytes:
152
+ """Create voice preview using verified ElevenLabs implementation"""
153
+ url = "https://api.elevenlabs.io/v1/text-to-voice/create-previews"
154
+ headers = {
155
+ 'xi-api-key': st.secrets["ELEVENLABS_API_KEY"],
156
+ 'Content-Type': 'application/json'
157
+ }
158
+ payload = {
159
+ 'voice_description': voice_description,
160
+ 'text': text
161
+ }
162
+
163
+ try:
164
+ response = requests.post(url, headers=headers, json=payload)
165
+ if response.status_code == 200:
166
+ return base64.b64decode(response.json()['previews'][0]['audio_base_64'])
167
+ return None
168
+ except Exception as e:
169
+ st.error(f"Voice generation error: {e}")
170
+ return None
171
+
172
+ def generate_content(topic: str, level: str, duration: str,
173
+ instructor_name: str, teaching_style: str) -> Dict:
174
+ """Generate course content using Claude 3.5"""
175
+ client = anthropic.Anthropic(api_key=st.secrets["ANTHROPIC_API_KEY"])
176
+
177
+ try:
178
+ # Generate course outline
179
+ outline_response = client.messages.create(
180
+ model="claude-3-5-sonnet-latest",
181
+ max_tokens=4096,
182
+ messages=[
183
+ {
184
+ "role": "user",
185
+ "content": COURSE_TEMPLATE.format(
186
+ topic=topic,
187
+ level=level,
188
+ duration=duration
189
+ )
190
+ }
191
+ ]
192
+ )
193
+
194
+ course_content = outline_response.content[0].text
195
+
196
+ # Parse content to get a concept for preview
197
+ sections = parse_course_content(course_content)
198
+ preview_concept = sections[0]['concepts'][0] if sections and sections[0].get('concepts') else topic
199
+
200
+ # Generate preview content
201
+ preview_response = client.messages.create(
202
+ model="claude-3-5-sonnet-latest",
203
+ max_tokens=4096,
204
+ messages=[
205
+ {
206
+ "role": "user",
207
+ "content": PREVIEW_TEMPLATE.format(concept=preview_concept)
208
+ }
209
+ ]
210
+ )
211
+
212
+ # Generate instructor introduction
213
+ intro_response = client.messages.create(
214
+ model="claude-3-5-sonnet-latest",
215
+ max_tokens=4096,
216
+ messages=[
217
+ {
218
+ "role": "user",
219
+ "content": INSTRUCTOR_INTRO_TEMPLATE.format(
220
+ name=instructor_name,
221
+ style=teaching_style,
222
+ topic=topic
223
+ )
224
+ }
225
+ ]
226
+ )
227
+
228
+ return {
229
+ "status": "success",
230
+ "course_outline": course_content,
231
+ "preview_content": preview_response.content[0].text,
232
+ "instructor_intro": intro_response.content[0].text,
233
+ "sections": sections
234
+ }
235
+ except Exception as e:
236
+ return {"status": "error", "message": str(e)}
237
+
238
+ def parse_outline(content: str) -> List[Dict]:
239
+ """Parse course outline to get sections and concepts"""
240
+ sections = []
241
+ current_section = None
242
+
243
+ for line in content.split('\n'):
244
+ line = line.strip()
245
+ if not line:
246
+ continue
247
+
248
+ if line.lower().startswith(('section', 'part', 'module')):
249
+ if current_section:
250
+ sections.append(current_section)
251
+ current_section = {
252
+ 'title': line,
253
+ 'description': '',
254
+ 'concepts': []
255
+ }
256
+ elif current_section:
257
+ if not current_section['description']:
258
+ current_section['description'] = line
259
+ elif line.startswith(('-', '•', '*')):
260
+ current_section['concepts'].append(line.lstrip('-•* '))
261
+
262
+ if current_section:
263
+ sections.append(current_section)
264
+
265
+ return sections
266
+
267
+ def parse_course_content(content: str) -> List[Dict]:
268
+ """Parse course content into structured format"""
269
+ sections = []
270
+ current_section = None
271
+
272
+ for line in content.split('\n'):
273
+ line = line.strip()
274
+ if not line:
275
+ continue
276
+
277
+ if line.lower().startswith(('section', 'part', 'module')):
278
+ if current_section:
279
+ sections.append(current_section)
280
+ current_section = {
281
+ 'title': line,
282
+ 'description': '',
283
+ 'concepts': []
284
+ }
285
+ elif current_section:
286
+ if not current_section['description']:
287
+ current_section['description'] = line
288
+ elif line.startswith(('-', '•', '*')):
289
+ current_section['concepts'].append(line.lstrip('-•* '))
290
+
291
+ if current_section:
292
+ sections.append(current_section)
293
+
294
+ return sections
295
+
296
+ def generate_course_outline(topic: str, level: str, duration: str) -> Dict:
297
+ """Generate initial course outline and get first concept"""
298
+ client = anthropic.Anthropic(api_key=st.secrets["ANTHROPIC_API_KEY"])
299
+
300
+ prompt = f"""Create a course outline for:
301
+ Topic: {topic}
302
+ Level: {level}
303
+ Duration: {duration}
304
+
305
+ Include:
306
+ 1. Brief course description (2-3 sentences)
307
+ 2. 4-5 main sections with:
308
+ - Clear title
309
+ - 2-3 key concepts per section
310
+ - Brief description
311
+
312
+ Format clearly with sections and concepts."""
313
+
314
+ try:
315
+ response = client.messages.create(
316
+ model="claude-3-5-sonnet-latest",
317
+ max_tokens=4096,
318
+ messages=[{"role": "user", "content": prompt}]
319
+ )
320
+
321
+ outline = response.content[0].text
322
+ # Parse to get first concept
323
+ sections = parse_outline(outline)
324
+ first_concept = sections[0]['concepts'][0] if sections and sections[0].get('concepts') else topic
325
+
326
+ return {
327
+ "status": "success",
328
+ "outline": outline,
329
+ "sections": sections,
330
+ "first_concept": first_concept
331
+ }
332
+ except Exception as e:
333
+ return {"status": "error", "message": str(e)}
334
+
335
+ def generate_preview_content(topic: str, concept: str) -> str:
336
+ """Generate preview content using Claude"""
337
+ client = anthropic.Anthropic(api_key=st.secrets["ANTHROPIC_API_KEY"])
338
+
339
+ prompt = f"""Create TWO preview slides about {concept} for a course on {topic}.
340
+
341
+ For EACH slide, provide EXACTLY in this format:
342
+ SLIDE [number]:
343
+ - Title: [slide title]
344
+ - Content: [3 clear bullet points]
345
+ - Teaching Script: [2-3 sentences explaining the slide content]
346
+ - Visual Description: [clear description for image generation]
347
+
348
+ Keep all content clear and concise."""
349
+
350
+ try:
351
+ response = client.messages.create(
352
+ model="claude-3-5-sonnet-latest",
353
+ max_tokens=4096,
354
+ messages=[{"role": "user", "content": prompt}]
355
+ )
356
+ return response.content[0].text
357
+ except Exception as e:
358
+ st.error(f"Content generation error: {e}")
359
+ return None
360
+
361
+ def parse_preview_content(content: str) -> List[Dict]:
362
+ """Parse preview content into structured format"""
363
+ slides = []
364
+ current_slide = None
365
+
366
+ for line in content.split('\n'):
367
+ line = line.strip()
368
+ if not line:
369
+ continue
370
+
371
+ if line.startswith('SLIDE'):
372
+ if current_slide:
373
+ slides.append(current_slide)
374
+ current_slide = {
375
+ 'title': '',
376
+ 'content': [],
377
+ 'script': '',
378
+ 'visual': ''
379
+ }
380
+ elif current_slide:
381
+ if line.startswith('- Title:'):
382
+ current_slide['title'] = line.replace('- Title:', '').strip()
383
+ elif line.startswith('- Content:'):
384
+ # Next lines will be content until next section
385
+ continue
386
+ elif line.startswith('- Teaching Script:'):
387
+ current_slide['script'] = line.replace('- Teaching Script:', '').strip()
388
+ elif line.startswith('- Visual Description:'):
389
+ current_slide['visual'] = line.replace('- Visual Description:', '').strip()
390
+ elif line.startswith('-') or line.startswith('•'):
391
+ current_slide['content'].append(line.lstrip('-• ').strip())
392
+
393
+ if current_slide:
394
+ slides.append(current_slide)
395
+
396
+ return slides
397
+
398
+ def display_preview_content(preview_slides: List[Dict]):
399
+ """Display preview content with proper styling"""
400
+ for i, slide in enumerate(preview_slides, 1):
401
+ st.markdown(f"### Preview Slide {i}")
402
+
403
+ # Content section
404
+ st.markdown('<div class="preview-content">', unsafe_allow_html=True)
405
+ for point in slide['content']:
406
+ st.markdown(f"• {point}")
407
+ st.markdown('</div>', unsafe_allow_html=True)
408
+
409
+ # Script section
410
+ st.markdown('<div class="script-content">', unsafe_allow_html=True)
411
+ st.markdown("**Teaching Script:**")
412
+ st.markdown(slide['script'])
413
+ st.markdown('</div>', unsafe_allow_html=True)
414
+
415
+ # Generate and display image
416
+ if slide['image_description']:
417
+ image_url = generate_image(slide['image_description'])
418
+ if image_url:
419
+ st.image(image_url, use_column_width=True)
420
+
421
+
422
+
423
+
424
+
425
+ def display_preview_slides(slides: List[Dict]):
426
+ """Display preview slides with proper styling"""
427
+ for i, slide in enumerate(slides, 1):
428
+ st.markdown(f"### {slide['title']}")
429
+
430
+ col1, col2 = st.columns([2, 1])
431
+
432
+ with col1:
433
+ # Content
434
+ st.markdown('<div class="preview-content">', unsafe_allow_html=True)
435
+ for point in slide['content']:
436
+ st.markdown(f"• {point}")
437
+ st.markdown('</div>', unsafe_allow_html=True)
438
+
439
+ # Teaching Script
440
+ st.markdown('<div class="script-content">', unsafe_allow_html=True)
441
+ st.markdown("**Teaching Script:**")
442
+ st.markdown(slide['script'])
443
+ st.markdown('</div>', unsafe_allow_html=True)
444
+
445
+ with col2:
446
+ # Generate and display image
447
+ if slide['visual']:
448
+ image_url = generate_and_get_image(slide['visual'])
449
+ if image_url:
450
+ st.image(image_url, use_column_width=True)
451
+
452
+ def main():
453
+ st.title("Interactive Course Preview Generator")
454
+ st.markdown("Generate professional course previews with content, visuals, and voice narration")
455
+
456
+ # Input Section
457
+ with st.container():
458
+ st.markdown('<div class="section-title">Course Configuration</div>', unsafe_allow_html=True)
459
+
460
+ col1, col2 = st.columns(2)
461
+ with col1:
462
+ topic = st.text_input("Course Topic",
463
+ placeholder="e.g., Machine Learning Fundamentals")
464
+ level = st.selectbox("Course Level",
465
+ ["Beginner", "Intermediate", "Advanced"])
466
+ duration = st.selectbox("Course Duration",
467
+ ["2 Hours", "4 Hours", "8 Hours", "Full Day"])
468
+
469
+ with col2:
470
+ instructor_name = st.text_input("Instructor Name",
471
+ placeholder="e.g., Dr. Sarah Johnson")
472
+ teaching_style = st.selectbox("Teaching Style",
473
+ ["Interactive", "Lecture-Based", "Project-Based", "Discussion-Led"])
474
+ instructor_gender = st.selectbox("Instructor Voice",
475
+ ["Male", "Female"])
476
+
477
+ if st.button("Generate Preview", type="primary"):
478
+ with st.spinner("Creating your course preview..."):
479
+ try:
480
+ # Step 1: Generate course outline and get first concept
481
+ outline_result = generate_course_outline(topic, level, duration)
482
+ if outline_result["status"] != "success":
483
+ st.error(f"Error generating outline: {outline_result.get('message')}")
484
+ return
485
+
486
+ # Step 2: Generate instructor introduction
487
+ intro_audio = create_voice_preview(
488
+ f"Hello! I'm {instructor_name}, and I'll be your instructor for {topic}. Let's explore this exciting subject together!",
489
+ f"Professional {instructor_gender.lower()} instructor, {teaching_style.lower()} style"
490
+ )
491
+
492
+ # Step 3: Generate preview content using first concept
493
+ preview_content = generate_preview_content(topic, outline_result["first_concept"])
494
+ if not preview_content:
495
+ st.error("Failed to generate preview content")
496
+ return
497
+
498
+ # Step 4: Parse and display content
499
+ slides = parse_preview_content(preview_content)
500
+
501
+ # Display results
502
+
503
+ # Course Outline
504
+ st.markdown("## Course Overview")
505
+ st.markdown(outline_result["outline"])
506
+
507
+ # Instructor Introduction
508
+ st.markdown("## Instructor Introduction")
509
+ if intro_audio:
510
+ st.audio(intro_audio)
511
+
512
+ # Preview Slides
513
+ st.markdown("## Preview Slides")
514
+ display_preview_slides(slides)
515
+
516
+ except Exception as e:
517
+ st.error(f"An error occurred: {str(e)}")
518
+ return
519
+
520
+ if __name__ == "__main__":
521
+ main()