import streamlit as st import anthropic import requests import base64 import json import time import os from typing import Dict, List, Any import fal_client from dotenv import load_dotenv from IPython.display import Image as IPImage, display # Page config st.set_page_config( page_title="Interactive Course Preview Generator", layout="wide", initial_sidebar_state="expanded" ) # Custom CSS for modern, minimalist design st.markdown(""" """, unsafe_allow_html=True) st.markdown(""" """, unsafe_allow_html=True) # Templates for content generation COURSE_TEMPLATE = """Create a concise course outline for: Topic: {topic} Level: {level} Duration: {duration} Include: 1. Brief course description (2-3 sentences) 2. 4-5 main sections 3. For each section: - Title - Brief description (1 sentence) - 2-3 key concepts Format it clearly and professionally.""" PREVIEW_TEMPLATE = """Create content for two brief preview slides about the concept: {concept} For each slide include EXACTLY in this order: 1. Slide Title 2. Content (3 brief bullet points maximum) 3. Teacher Script (2-3 sentences maximum) 4. Image Description (one sentence describing a minimalist visual) Format each slide clearly with these exact headers: "Slide 1:", "Content:", "Teacher Script:", "Image Description:" "Slide 2:", "Content:", "Teacher Script:", "Image Description:" """ INSTRUCTOR_INTRO_TEMPLATE = """Create a very brief welcome message (2 sentences maximum) for: Instructor: {name} Course: {topic} Style: {style} Keep it natural and concise.""" # API Integration Functions def generate_image(description: str) -> str: """Generate image using verified Fal.ai implementation with proper waiting""" try: with st.spinner(f"Generating image for: {description}"): handler = fal_client.submit( "fal-ai/flux-pro/v1.1-ultra", arguments={ "prompt": f"Professional minimalist educational visual: {description}", "num_images": 1 } ) # Extended wait time with status check for _ in range(30): # Maximum 30 seconds wait time.sleep(1) result = fal_client.result("fal-ai/flux-pro/v1.1-ultra", handler.request_id) if result and "images" in result and result["images"]: return result["images"][0]["url"] return None except Exception as e: st.error(f"Image generation error: {e}") return None def generate_and_get_image(prompt: str) -> str: """Generate and get image using verified Fal.ai implementation""" try: with st.spinner(f"Generating image..."): # Submit request handler = fal_client.submit( "fal-ai/flux-pro/v1.1-ultra", arguments={"prompt": prompt, "num_images": 1} ) request_id = handler.request_id if request_id: time.sleep(10) # Wait for generation # Get result result = fal_client.result("fal-ai/flux-pro/v1.1-ultra", request_id) if result and "images" in result and result["images"]: return result["images"][0]["url"] except Exception as e: st.error(f"Image generation error: {e}") return None def create_voice_preview(text: str, voice_description: str) -> bytes: """Create voice preview using verified ElevenLabs implementation""" url = "https://api.elevenlabs.io/v1/text-to-voice/create-previews" headers = { 'xi-api-key': st.secrets["ELEVENLABS_API_KEY"], 'Content-Type': 'application/json' } payload = { 'voice_description': voice_description, 'text': text } try: response = requests.post(url, headers=headers, json=payload) if response.status_code == 200: return base64.b64decode(response.json()['previews'][0]['audio_base_64']) return None except Exception as e: st.error(f"Voice generation error: {e}") return None def generate_content(topic: str, level: str, duration: str, instructor_name: str, teaching_style: str) -> Dict: """Generate course content using Claude 3.5""" client = anthropic.Anthropic(api_key=st.secrets["ANTHROPIC_API_KEY"]) try: # Generate course outline outline_response = client.messages.create( model="claude-3-5-sonnet-latest", max_tokens=4096, messages=[ { "role": "user", "content": COURSE_TEMPLATE.format( topic=topic, level=level, duration=duration ) } ] ) course_content = outline_response.content[0].text # Parse content to get a concept for preview sections = parse_course_content(course_content) preview_concept = sections[0]['concepts'][0] if sections and sections[0].get('concepts') else topic # Generate preview content preview_response = client.messages.create( model="claude-3-5-sonnet-latest", max_tokens=4096, messages=[ { "role": "user", "content": PREVIEW_TEMPLATE.format(concept=preview_concept) } ] ) # Generate instructor introduction intro_response = client.messages.create( model="claude-3-5-sonnet-latest", max_tokens=4096, messages=[ { "role": "user", "content": INSTRUCTOR_INTRO_TEMPLATE.format( name=instructor_name, style=teaching_style, topic=topic ) } ] ) return { "status": "success", "course_outline": course_content, "preview_content": preview_response.content[0].text, "instructor_intro": intro_response.content[0].text, "sections": sections } except Exception as e: return {"status": "error", "message": str(e)} def parse_outline(content: str) -> List[Dict]: """Parse course outline to get sections and concepts""" sections = [] current_section = None for line in content.split('\n'): line = line.strip() if not line: continue if line.lower().startswith(('section', 'part', 'module')): if current_section: sections.append(current_section) current_section = { 'title': line, 'description': '', 'concepts': [] } elif current_section: if not current_section['description']: current_section['description'] = line elif line.startswith(('-', '•', '*')): current_section['concepts'].append(line.lstrip('-•* ')) if current_section: sections.append(current_section) return sections def parse_course_content(content: str) -> List[Dict]: """Parse course content into structured format""" sections = [] current_section = None for line in content.split('\n'): line = line.strip() if not line: continue if line.lower().startswith(('section', 'part', 'module')): if current_section: sections.append(current_section) current_section = { 'title': line, 'description': '', 'concepts': [] } elif current_section: if not current_section['description']: current_section['description'] = line elif line.startswith(('-', '•', '*')): current_section['concepts'].append(line.lstrip('-•* ')) if current_section: sections.append(current_section) return sections def generate_course_outline(topic: str, level: str, duration: str) -> Dict: """Generate initial course outline and get first concept""" client = anthropic.Anthropic(api_key=st.secrets["ANTHROPIC_API_KEY"]) prompt = f"""Create a course outline for: Topic: {topic} Level: {level} Duration: {duration} Include: 1. Brief course description (2-3 sentences) 2. 4-5 main sections with: - Clear title - 2-3 key concepts per section - Brief description Format clearly with sections and concepts.""" try: response = client.messages.create( model="claude-3-5-sonnet-latest", max_tokens=4096, messages=[{"role": "user", "content": prompt}] ) outline = response.content[0].text # Parse to get first concept sections = parse_outline(outline) first_concept = sections[0]['concepts'][0] if sections and sections[0].get('concepts') else topic return { "status": "success", "outline": outline, "sections": sections, "first_concept": first_concept } except Exception as e: return {"status": "error", "message": str(e)} def generate_preview_content(topic: str, concept: str) -> str: """Generate preview content using Claude""" client = anthropic.Anthropic(api_key=st.secrets["ANTHROPIC_API_KEY"]) prompt = f"""Create TWO preview slides about {concept} for a course on {topic}. For EACH slide, provide EXACTLY in this format: SLIDE [number]: - Title: [slide title] - Content: [3 clear bullet points] - Teaching Script: [2-3 sentences explaining the slide content] - Visual Description: [clear description for image generation] Keep all content clear and concise.""" try: response = client.messages.create( model="claude-3-5-sonnet-latest", max_tokens=4096, messages=[{"role": "user", "content": prompt}] ) return response.content[0].text except Exception as e: st.error(f"Content generation error: {e}") return None def parse_preview_content(content: str) -> List[Dict]: """Parse preview content into structured format""" slides = [] current_slide = None for line in content.split('\n'): line = line.strip() if not line: continue if line.startswith('SLIDE'): if current_slide: slides.append(current_slide) current_slide = { 'title': '', 'content': [], 'script': '', 'visual': '' } elif current_slide: if line.startswith('- Title:'): current_slide['title'] = line.replace('- Title:', '').strip() elif line.startswith('- Content:'): # Next lines will be content until next section continue elif line.startswith('- Teaching Script:'): current_slide['script'] = line.replace('- Teaching Script:', '').strip() elif line.startswith('- Visual Description:'): current_slide['visual'] = line.replace('- Visual Description:', '').strip() elif line.startswith('-') or line.startswith('•'): current_slide['content'].append(line.lstrip('-• ').strip()) if current_slide: slides.append(current_slide) return slides def display_preview_content(preview_slides: List[Dict]): """Display preview content with proper styling""" for i, slide in enumerate(preview_slides, 1): st.markdown(f"### Preview Slide {i}") # Content section st.markdown('
', unsafe_allow_html=True) for point in slide['content']: st.markdown(f"• {point}") st.markdown('
', unsafe_allow_html=True) # Script section st.markdown('
', unsafe_allow_html=True) st.markdown("**Teaching Script:**") st.markdown(slide['script']) st.markdown('
', unsafe_allow_html=True) # Generate and display image if slide['image_description']: image_url = generate_image(slide['image_description']) if image_url: st.image(image_url, use_column_width=True) def display_preview_slides(slides: List[Dict]): """Display preview slides with proper styling""" for i, slide in enumerate(slides, 1): st.markdown(f"### {slide['title']}") col1, col2 = st.columns([2, 1]) with col1: # Content st.markdown('
', unsafe_allow_html=True) for point in slide['content']: st.markdown(f"• {point}") st.markdown('
', unsafe_allow_html=True) # Teaching Script st.markdown('
', unsafe_allow_html=True) st.markdown("**Teaching Script:**") st.markdown(slide['script']) st.markdown('
', unsafe_allow_html=True) with col2: # Generate and display image if slide['visual']: image_url = generate_and_get_image(slide['visual']) if image_url: st.image(image_url, use_column_width=True) def main(): st.title("Interactive Course Preview Generator") st.markdown("Generate professional course previews with content, visuals, and voice narration") # Input Section with st.container(): st.markdown('
Course Configuration
', unsafe_allow_html=True) col1, col2 = st.columns(2) with col1: topic = st.text_input("Course Topic", placeholder="e.g., Machine Learning Fundamentals") level = st.selectbox("Course Level", ["Beginner", "Intermediate", "Advanced"]) duration = st.selectbox("Course Duration", ["2 Hours", "4 Hours", "8 Hours", "Full Day"]) with col2: instructor_name = st.text_input("Instructor Name", placeholder="e.g., Dr. Sarah Johnson") teaching_style = st.selectbox("Teaching Style", ["Interactive", "Lecture-Based", "Project-Based", "Discussion-Led"]) instructor_gender = st.selectbox("Instructor Voice", ["Male", "Female"]) if st.button("Generate Preview", type="primary"): with st.spinner("Creating your course preview..."): try: # Step 1: Generate course outline and get first concept outline_result = generate_course_outline(topic, level, duration) if outline_result["status"] != "success": st.error(f"Error generating outline: {outline_result.get('message')}") return # Step 2: Generate instructor introduction intro_audio = create_voice_preview( f"Hello! I'm {instructor_name}, and I'll be your instructor for {topic}. Let's explore this exciting subject together!", f"Professional {instructor_gender.lower()} instructor, {teaching_style.lower()} style" ) # Step 3: Generate preview content using first concept preview_content = generate_preview_content(topic, outline_result["first_concept"]) if not preview_content: st.error("Failed to generate preview content") return # Step 4: Parse and display content slides = parse_preview_content(preview_content) # Display results # Course Outline st.markdown("## Course Overview") st.markdown(outline_result["outline"]) # Instructor Introduction st.markdown("## Instructor Introduction") if intro_audio: st.audio(intro_audio) # Preview Slides st.markdown("## Preview Slides") display_preview_slides(slides) except Exception as e: st.error(f"An error occurred: {str(e)}") return if __name__ == "__main__": main()