import streamlit as st import base64 from io import BytesIO import json import time import os from utils import create_slide_preview, TEMPLATES, generate_slide_content from visual_elements import search_stock_images, download_image, analyze_slide_for_visuals from slide_editor_enhancements import render_enhanced_slide_editor # Check for API keys api_key = os.getenv("ANTHROPIC_API_KEY") openai_key = os.getenv("OPENAI_API_KEY") deepseek_key = os.getenv("DEEPSEEK_API_KEY") perplexity_key = os.getenv("PERPLEXITY_API_KEY") pexels_key = os.getenv("PEXELS_API_KEY") def nav_button(label, stage, icon=None, primary=False): """Create a navigation button with improved styling""" button_style = "primary" if primary else "secondary" icon_text = f"{icon} " if icon else "" if st.button(f"{icon_text}{label}", key=f"nav_{stage}", type=button_style, use_container_width=True): st.session_state.current_stage = stage st.rerun() def display_navigation_bar(): """Display navigation bar at the top of the page""" stages = [ {"name": "ideation", "label": "Ideation", "icon": "đĄ"}, {"name": "storyboard", "label": "Storyboard", "icon": "đ"}, {"name": "template", "label": "Template", "icon": "đ¨"}, {"name": "slides", "label": "Edit Slides", "icon": "đŧī¸"}, {"name": "export", "label": "Export", "icon": "đ¤"} ] cols = st.columns(len(stages)) for i, stage in enumerate(stages): with cols[i]: is_current = st.session_state.current_stage == stage["name"] # Create a clickable button with custom CSS if st.button( f"{stage['icon']} {stage['label']}", key=f"nav_top_{stage['name']}", disabled=is_current, use_container_width=True, type="primary" if is_current else "secondary" ): # If we've already completed previous stages, allow direct navigation if stage["name"] in ["ideation"] or ( stage["name"] == "storyboard" and st.session_state.storyboard ) or ( stage["name"] == "template" and st.session_state.storyboard ) or ( stage["name"] == "slides" and st.session_state.slides_content ) or ( stage["name"] == "export" and st.session_state.slides_content ): st.session_state.current_stage = stage["name"] st.rerun() else: st.warning(f"Please complete the previous stages before going to {stage['label']}") # Progress bar stages_order = ["ideation", "storyboard", "template", "slides", "export"] current_idx = stages_order.index(st.session_state.current_stage) progress = current_idx / (len(stages_order) - 1) st.progress(progress) def render_ai_settings(): """Render AI settings sidebar section""" st.sidebar.write("## đ§ AI Settings") # Get AI manager try: from multi_llm_provider import get_ai_manager ai_manager = get_ai_manager() available_models = ai_manager.get_available_models() except ImportError: available_models = { "claude-3-sonnet-20250219": "Claude 3 Sonnet", "claude-3-haiku-20250319": "Claude 3 Haiku", "claude-3-opus-20250229": "Claude 3 Opus" } if not available_models: st.sidebar.warning("No AI providers configured. Add API keys in secrets.") default_model = "claude-3-sonnet-20250219" else: default_model = list(available_models.keys())[0] if available_models else "claude-3-sonnet-20250219" # Select default model for the whole presentation selected_model = st.sidebar.selectbox( "Default AI Model", options=list(available_models.keys()), format_func=lambda x: available_models.get(x, x), index=0 if default_model in available_models else 0 ) st.session_state.default_model = selected_model # Temperature setting temperature = st.sidebar.slider( "AI Creativity", min_value=0.0, max_value=1.0, value=0.7, step=0.1, help="Higher values make output more creative but less predictable" ) st.session_state.ai_temperature = temperature # Web search integration enable_search = st.sidebar.checkbox( "Enable Web Search", value=st.session_state.get("enable_web_search", False), help="Use Perplexity to search for up-to-date information", disabled=not perplexity_key ) st.session_state.enable_web_search = enable_search if enable_search and not perplexity_key: st.sidebar.warning("Perplexity API key required for web search") # Stock image settings st.sidebar.write("## đŧī¸ Image Settings") if not pexels_key: st.sidebar.warning("Pexels API key required for better stock images") def render_ideation_stage(): """Render the ideation stage UI""" display_navigation_bar() # Add AI settings sidebar render_ai_settings() st.header("đĄ Step 1: Presentation Ideation") st.write("Let's start by defining the purpose and scope of your presentation.") col1, col2 = st.columns(2) with col1: st.session_state.presentation_title = st.text_input( "Presentation Title", value=st.session_state.presentation_title ) st.session_state.presentation_purpose = st.text_area( "What's the purpose of this presentation?", value=st.session_state.presentation_purpose, help="E.g., Inform, persuade, pitch a product, update stakeholders, etc." ) with col2: st.session_state.target_audience = st.text_area( "Who is your target audience?", value=st.session_state.target_audience, help="E.g., Executives, customers, technical team, general public, etc." ) # Add example suggestions st.write("đ **Example presentations:**") examples = [ "Quarterly Business Review", "Product Launch Presentation", "Team Project Update", "Investor Pitch Deck" ] # Create two columns for examples ex_col1, ex_col2 = st.columns(2) for i, example in enumerate(examples): with ex_col1 if i % 2 == 0 else ex_col2: if st.button(f"đ {example}", key=f"example_{example}"): st.session_state.presentation_title = example # Set appropriate purpose and audience based on example if "Quarterly" in example: st.session_state.presentation_purpose = "Review business performance for the past quarter and outline goals for the next quarter" st.session_state.target_audience = "Executives and department heads" elif "Product" in example: st.session_state.presentation_purpose = "Introduce a new product to customers and highlight its key features and benefits" st.session_state.target_audience = "Potential customers and sales team" elif "Project" in example: st.session_state.presentation_purpose = "Update team members on project progress, achievements, and next steps" st.session_state.target_audience = "Project stakeholders and team members" elif "Investor" in example: st.session_state.presentation_purpose = "Pitch our business to potential investors to secure funding" st.session_state.target_audience = "Venture capitalists and angel investors" st.rerun() # AI model selection for storyboard generation st.write("### đ§ AI Engine Selection") # Get available models try: from multi_llm_provider import get_ai_manager ai_manager = get_ai_manager() available_models = ai_manager.get_available_models() except (ImportError, Exception): available_models = { "claude-3-sonnet-20250219": "Claude 3 Sonnet", "claude-3-haiku-20250319": "Claude 3 Haiku", "claude-3-opus-20250229": "Claude 3 Opus" } cols = st.columns([2, 1]) with cols[0]: selected_model = st.selectbox( "AI Model for Storyboard Generation", options=list(available_models.keys()), format_func=lambda x: available_models.get(x, x), index=0, key="storyboard_model" ) with cols[1]: web_research = st.checkbox( "Include Web Research", value=st.session_state.get("enable_web_search", False), disabled=not perplexity_key, help="Search the web for the latest information related to your presentation topic" ) # Generate button with loading indicator st.markdown("---") if st.button("đ Generate Storyboard", type="primary", use_container_width=True): if not st.session_state.presentation_title or not st.session_state.presentation_purpose: st.warning("Please provide at least a title and purpose to proceed.") else: with st.spinner("đ§ SlideGator.AI is chomping on your presentation ideas..."): from utils import generate_storyboard # Generate storyboard storyboard = generate_storyboard( st.session_state.presentation_title, st.session_state.presentation_purpose, st.session_state.target_audience, model=selected_model ) if storyboard: st.session_state.storyboard = storyboard st.session_state.current_stage = "storyboard" st.success("Storyboard created successfully!") st.rerun() def render_storyboard_stage(): """Render the storyboard review stage UI (simplified version without AI features)""" display_navigation_bar() st.header("đ Step 2: Review and Edit Storyboard") st.write(f"Storyboard for: **{st.session_state.presentation_title}**") st.write("Expand each slide to edit its content or add notes.") # Display storyboard for review with edit options edited_storyboard = [] # Simple regenerate button if st.button("đ Regenerate All", help="Create a new storyboard with current title/purpose"): st.info("Storyboard regeneration feature coming soon") # Add slide button with options col1, col2 = st.columns([3, 1]) with col1: slide_position = st.radio( "Add new slide:", ["At End", "After Current", "Before Current"], horizontal=True ) with col2: if st.button("â Add New Slide", use_container_width=True): # Create a new slide new_slide = { 'title': 'New Slide', 'purpose': 'Additional content', 'key_points': ['Add your content here'], 'visual_elements': ['Suggested visuals will appear here'] } # Insert at the selected position if slide_position == "At End": st.session_state.storyboard.append(new_slide) elif slide_position == "After Current": # Get current slide index from session state or default to last current_idx = st.session_state.get("current_storyboard_slide", len(st.session_state.storyboard) - 1) st.session_state.storyboard.insert(current_idx + 1, new_slide) else: # Before Current current_idx = st.session_state.get("current_storyboard_slide", 0) st.session_state.storyboard.insert(max(0, current_idx), new_slide) st.rerun() # Display storyboard slides for i, slide in enumerate(st.session_state.storyboard): # Set current slide in session state when expander is opened is_expanded = i == 0 or st.session_state.get("current_storyboard_slide") == i with st.expander(f"Slide {i+1}: {slide.get('title', 'Untitled')}", expanded=is_expanded): if is_expanded: st.session_state.current_storyboard_slide = i # Main slide content cols = st.columns([3, 1]) with cols[0]: slide_title = st.text_input(f"Title ###{i}", value=slide.get('title', 'Untitled')) with cols[1]: # Slide reordering and deletion cols2 = st.columns([1, 1, 1]) with cols2[0]: if i > 0 and st.button("âŦī¸", key=f"up_{i}"): st.session_state.storyboard[i], st.session_state.storyboard[i-1] = st.session_state.storyboard[i-1], st.session_state.storyboard[i] st.session_state.current_storyboard_slide = i - 1 st.rerun() with cols2[1]: if i < len(st.session_state.storyboard) - 1 and st.button("âŦī¸", key=f"down_{i}"): st.session_state.storyboard[i], st.session_state.storyboard[i+1] = st.session_state.storyboard[i+1], st.session_state.storyboard[i] st.session_state.current_storyboard_slide = i + 1 st.rerun() with cols2[2]: if st.button("đī¸", key=f"delete_{i}"): if len(st.session_state.storyboard) > 1: # Prevent deleting the last slide st.session_state.storyboard.pop(i) st.session_state.current_storyboard_slide = min(i, len(st.session_state.storyboard) - 1) st.rerun() else: st.error("Cannot delete the last slide") slide_purpose = st.text_area(f"Purpose ###{i}", value=slide.get('purpose', '')) # Handle key points (could be string or list) if isinstance(slide.get('key_points', ""), list): key_points_text = "\n".join(slide['key_points']) else: key_points_text = slide.get('key_points', "") key_points = st.text_area(f"Key Points (one per line) ###{i}", value=key_points_text) # Handle visual elements (could be string or list) if isinstance(slide.get('visual_elements', ""), list): visual_elements_text = "\n".join(slide['visual_elements']) else: visual_elements_text = slide.get('visual_elements', "") visual_elements = st.text_area(f"Visual Elements (one per line) ###{i}", value=visual_elements_text) # Update storyboard with edits edited_slide = { 'title': slide_title, 'purpose': slide_purpose, 'key_points': key_points.split("\n") if "\n" in key_points else [key_points] if key_points else [], 'visual_elements': visual_elements.split("\n") if "\n" in visual_elements else [visual_elements] if visual_elements else [] } edited_storyboard.append(edited_slide) # Update the storyboard in session state st.session_state.storyboard = edited_storyboard st.markdown("---") col1, col2 = st.columns(2) with col1: nav_button("Back to Ideation", "ideation", icon="âŦ ī¸") with col2: nav_button("Continue to Template Selection", "template", icon="âĄī¸", primary=True) def render_template_stage(): """Render the template selection stage UI with enhanced options""" display_navigation_bar() # Add AI settings sidebar render_ai_settings() st.header("đ¨ Step 3: Select a Template") st.write("Choose a visual style for your presentation:") # Preview section preview_col, options_col = st.columns([2, 1]) with options_col: # Add template upload option st.subheader("Custom Template") st.write("Upload your own PowerPoint template:") uploaded_template = st.file_uploader("Upload PPTX template", type=["pptx"]) if uploaded_template: # Save the uploaded template from utils import add_custom_template success, message = add_custom_template(uploaded_template) if success: st.success(message) st.session_state.selected_template = "custom" # Store template file name for display st.session_state.custom_template_name = uploaded_template.name else: st.error(message) # Template color customization st.subheader("Color Customization") # Get current template current_template = st.session_state.selected_template template_info = TEMPLATES.get(current_template, TEMPLATES["professional"]) colors = template_info["colors"] # Allow color customization primary_color = st.color_picker( "Primary Color", value=colors.get("primary", "#0F52BA"), key="primary_color" ) accent_color = st.color_picker( "Accent Color", value=colors.get("accent", "#D4AF37"), key="accent_color" ) # Store customized colors if "custom_colors" not in st.session_state: st.session_state.custom_colors = {} st.session_state.custom_colors[current_template] = { "primary": primary_color, "accent": accent_color } # Apply customized colors if st.button("Apply Custom Colors"): # Update the template colors if current_template in TEMPLATES: TEMPLATES[current_template]["colors"]["primary"] = primary_color TEMPLATES[current_template]["colors"]["accent"] = accent_color st.success("Custom colors applied!") with preview_col: # Template preview - show a sample slide with the selected template st.subheader("Template Preview") # Create a sample slide for preview sample_slide = { "title": "Sample Slide", "content": [ "This is how your slides will look", "With the selected template style", "You can customize colors and fonts" ], "visual_elements": ["Chart showing data trends", "Icon representing growth"] } # Get current template current_template = st.session_state.selected_template # Show preview based on template try: from visual_elements import generate_html_preview_with_visuals preview_html = generate_html_preview_with_visuals(sample_slide, current_template) st.components.v1.html(preview_html, height=300) except Exception as e: st.error(f"Error generating preview: {str(e)}") # Fall back to standard preview preview_html = create_slide_preview(sample_slide, current_template) st.components.v1.html(preview_html, height=300) # Display current template name if current_template == "custom" and "custom_template_name" in st.session_state: st.info(f"Using custom template: {st.session_state.custom_template_name}") else: st.info(f"Currently using: {current_template.title()} template") st.markdown("---") # Create template cards in a grid st.subheader("đ SlideGator Template Gallery") st.write("Select from our professionally designed templates:") # Use columns to create a grid cols = st.columns(3) # Add Professional template with cols[0]: st.subheader("Professional") st.write("Clean, corporate style with blue and gray.") st.markdown("""