File size: 17,536 Bytes
e610553
70a24ca
0d4525b
70a24ca
 
 
 
 
e610553
70a24ca
 
 
 
 
 
 
 
 
 
 
 
 
 
0d4525b
 
c77e0d4
 
 
 
70a24ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c77e0d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70a24ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37d3b3a
70a24ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c77e0d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70a24ca
c77e0d4
 
 
70a24ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c77e0d4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
import streamlit as st
import json
import openai
import os
import time
import base64
from io import BytesIO
import zipfile

# Set page configuration
st.set_page_config(page_title="AI PowerPoint Maker", layout="wide")

# Initialize session state variables if they don't exist
if 'slides_html' not in st.session_state:
    st.session_state.slides_html = {}
if 'current_slide' not in st.session_state:
    st.session_state.current_slide = None
if 'presentation_plan' not in st.session_state:
    st.session_state.presentation_plan = None
if 'slide_order' not in st.session_state:
    st.session_state.slide_order = []
if 'presentation_title' not in st.session_state:
    st.session_state.presentation_title = ""
if 'openai_api_key' not in st.session_state:
    st.session_state.openai_api_key = ""
if 'style_instructions' not in st.session_state:
    st.session_state.style_instructions = ""
if 'content_instructions' not in st.session_state:
    st.session_state.content_instructions = ""
if 'css_style' not in st.session_state:
    st.session_state.css_style = """
    body {
        font-family: 'Arial', sans-serif;
        margin: 0;
        padding: 0;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        background-color: #f5f5f5;
    }
    .slide {
        width: 900px;
        height: 500px;
        background-color: white;
        box-shadow: 0 4px 8px rgba(0,0,0,0.1);
        padding: 40px;
        box-sizing: border-box;
        position: relative;
        overflow: hidden;
    }
    h1 {
        font-size: 2.5em;
        color: #2c3e50;
        margin-bottom: 20px;
    }
    h2 {
        font-size: 1.8em;
        color: #3498db;
        margin-bottom: 15px;
    }
    p {
        font-size: 1.2em;
        line-height: 1.6;
        color: #34495e;
    }
    ul, ol {
        font-size: 1.2em;
        line-height: 1.6;
        color: #34495e;
        margin-left: 20px;
    }
    li {
        margin-bottom: 10px;
    }
    .slide-number {
        position: absolute;
        bottom: 10px;
        right: 10px;
        font-size: 12px;
        color: #95a5a6;
    }
    """

# Function to create presentation plan using LLM as "Planning Agent"
def create_presentation_plan(topic, num_slides, key_points):
    try:
        if st.session_state.openai_api_key:
            # Use actual OpenAI API
            client = openai.OpenAI(api_key=st.session_state.openai_api_key)
            response = client.chat.completions.create(
                model="gpt-3.5-turbo",  # You can change to gpt-4 if available
                messages=[
                    {"role": "system", "content": "You are a presentation planning expert. Create a detailed presentation outline."},
                    {"role": "user", "content": f"Create a presentation plan on '{topic}' with {num_slides} slides. Key points to include: {key_points}. Return your response as a JSON object with this structure: {{\"title\": \"Presentation Title\", \"num_slides\": number, \"slides\": [{{\"slide_number\": 1, \"title\": \"Slide Title\", \"type\": \"title_slide\", \"content\": \"Content description\"}}]}}"}
                ],
                response_format={"type": "json_object"}
            )
            # Parse the JSON response
            try:
                return json.loads(response.choices[0].message.content)
            except json.JSONDecodeError:
                # Fallback to simulated response if JSON parsing fails
                st.warning("Could not parse LLM response as JSON. Using simulated plan instead.")
                return simulate_presentation_plan(topic, num_slides, key_points)
        else:
            # Fallback to simulated response
            return simulate_presentation_plan(topic, num_slides, key_points)
    except Exception as e:
        st.error(f"Error creating presentation plan: {str(e)}")
        # Fallback to simulated response
        return simulate_presentation_plan(topic, num_slides, key_points)

def simulate_presentation_plan(topic, num_slides, key_points):
    # Simulate an LLM response with a predefined structure
    time.sleep(1)  # Simulate API call time
        
        slides = []
        # Always create a title slide
        slides.append({
            "slide_number": 1,
            "title": topic.title(),
            "type": "title_slide",
            "content": "Presentation overview and introduction"
        })
        
        # Parse key points and distribute across slides
        points = [p.strip() for p in key_points.split(',') if p.strip()]
        points_per_slide = max(1, len(points) // (num_slides - 2))  # Reserve title and conclusion slides
        
        # Content slides
        for i in range(2, num_slides):
            start_idx = (i-2) * points_per_slide
            end_idx = min(start_idx + points_per_slide, len(points))
            current_points = points[start_idx:end_idx] if start_idx < len(points) else ["Further exploration of the topic"]
            
            slides.append({
                "slide_number": i,
                "title": f"Key Point {i-1}" if start_idx < len(points) else "Additional Insights",
                "type": "content_slide",
                "content": ", ".join(current_points)
            })
        
        # Conclusion slide
        slides.append({
            "slide_number": num_slides,
            "title": "Conclusion",
            "type": "conclusion_slide",
            "content": "Summary of key points and final thoughts"
        })
        
        return {
            "title": topic.title(),
            "num_slides": num_slides,
            "slides": slides
        }
    except Exception as e:
        st.error(f"Error creating presentation plan: {str(e)}")
        return None

# Function to generate HTML for a slide using LLM as "HTML Creation Agent"
def generate_slide_html(slide_info, css_style):
    try:
        # Simulating LLM call - In production, you would replace this with an actual LLM API call
        # For demo purposes, we'll create simple HTML based on slide type
        slide_type = slide_info.get("type", "content_slide")
        slide_number = slide_info.get("slide_number", 1)
        slide_title = slide_info.get("title", "Slide")
        slide_content = slide_info.get("content", "")
        
        if slide_type == "title_slide":
            html = f"""<!DOCTYPE html>
<html>
<head>
    <title>{slide_title}</title>
    <style>{css_style}</style>
</head>
<body>
    <div class="slide">
        <h1 style="text-align: center; margin-top: 150px;">{slide_title}</h1>
        <p style="text-align: center; font-size: 1.5em;">AI Generated Presentation</p>
        <div class="slide-number">{slide_number}</div>
    </div>
</body>
</html>"""
        
        elif slide_type == "conclusion_slide":
            html = f"""<!DOCTYPE html>
<html>
<head>
    <title>{slide_title}</title>
    <style>{css_style}</style>
</head>
<body>
    <div class="slide">
        <h1>{slide_title}</h1>
        <p>Thank you for your attention!</p>
        <ul>
            <li>We've covered the key aspects of {st.session_state.presentation_title}</li>
            <li>Remember the main takeaways</li>
            <li>Questions and feedback welcome</li>
        </ul>
        <div class="slide-number">{slide_number}</div>
    </div>
</body>
</html>"""
        
        else:  # content_slide
            # Convert the content to bullet points if it's comma-separated
            if "," in slide_content:
                bullet_points = slide_content.split(",")
                content_html = "<ul>"
                for point in bullet_points:
                    if point.strip():
                        content_html += f"<li>{point.strip()}</li>"
                content_html += "</ul>"
            else:
                content_html = f"<p>{slide_content}</p>"
            
            html = f"""<!DOCTYPE html>
<html>
<head>
    <title>{slide_title}</title>
    <style>{css_style}</style>
</head>
<body>
    <div class="slide">
        <h1>{slide_title}</h1>
        {content_html}
        <div class="slide-number">{slide_number}</div>
    </div>
</body>
</html>"""
        
        return html
    except Exception as e:
        st.error(f"Error generating slide HTML: {str(e)}")
        return None

# Function to download all slides as a ZIP file
def get_slide_download_link():
    buffer = BytesIO()
    with zipfile.ZipFile(buffer, 'w') as zip_file:
        for slide_number in st.session_state.slide_order:
            html_content = st.session_state.slides_html.get(str(slide_number), "")
            if html_content:
                zip_file.writestr(f"slide_{slide_number}.html", html_content)
        
        # Add a CSS file
        zip_file.writestr("styles.css", st.session_state.css_style)
        
        # Add an index.html that links all slides
        index_html = """<!DOCTYPE html>
<html>
<head>
    <title>Presentation Slides</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        h1 {
            color: #2c3e50;
        }
        ul {
            list-style-type: none;
            padding: 0;
        }
        li {
            margin: 10px 0;
        }
        a {
            display: block;
            padding: 10px;
            background-color: #3498db;
            color: white;
            text-decoration: none;
            border-radius: 5px;
        }
        a:hover {
            background-color: #2980b9;
        }
    </style>
</head>
<body>
    <h1>""" + st.session_state.presentation_title + """</h1>
    <ul>"""
        
        for slide_number in st.session_state.slide_order:
            index_html += f"""
        <li><a href="slide_{slide_number}.html" target="_blank">Slide {slide_number}</a></li>"""
            
        index_html += """
    </ul>
</body>
</html>"""
        zip_file.writestr("index.html", index_html)
    
    buffer.seek(0)
    b64 = base64.b64encode(buffer.read()).decode()
    return f'<a href="data:application/zip;base64,{b64}" download="{st.session_state.presentation_title.replace(" ", "_")}_slides.zip">Download All Slides</a>'

# Function to update or edit a slide
def update_slide(slide_number, html_content):
    st.session_state.slides_html[str(slide_number)] = html_content

# Main app layout
st.title("AI PowerPoint Maker")
st.markdown("### Multi-Agent System for Creating Editable HTML Presentations")

tabs = st.tabs(["Create Presentation", "Edit Slides", "Customize Styling", "Preview & Export"])

# Tab 1: Create Presentation
with tabs[0]:
    st.subheader("Planning Agent: Define Your Presentation")
    
    col1, col2 = st.columns(2)
    
    with col1:
        topic = st.text_input("Presentation Topic", value=st.session_state.presentation_title)
        num_slides = st.slider("Number of Slides", min_value=3, max_value=15, value=5)
    
    with col2:
        key_points = st.text_area("Key Points (comma separated)", height=100, 
                                help="Enter the main points you want to cover, separated by commas")
    
    if st.button("Generate Presentation Plan"):
        if not topic:
            st.error("Please enter a presentation topic")
        else:
            with st.spinner("Planning Agent is thinking..."):
                plan = create_presentation_plan(topic, num_slides, key_points)
                if plan:
                    st.session_state.presentation_plan = plan
                    st.session_state.presentation_title = plan["title"]
                    st.session_state.slide_order = [slide["slide_number"] for slide in plan["slides"]]
                    st.success("Presentation plan created successfully!")
                    
                    # Auto-generate all slides
                    with st.spinner("HTML Creation Agent is generating slides..."):
                        for slide in plan["slides"]:
                            html = generate_slide_html(slide, st.session_state.css_style)
                            if html:
                                st.session_state.slides_html[str(slide["slide_number"])] = html
                        st.success("All slides generated! Go to the Edit Slides tab to view and modify them.")

    # Display the presentation plan if it exists
    if st.session_state.presentation_plan:
        st.subheader("Presentation Plan")
        st.json(st.session_state.presentation_plan)

# Tab 2: Edit Slides
with tabs[1]:
    if not st.session_state.slides_html:
        st.info("No slides have been generated yet. Go to the Create Presentation tab to get started.")
    else:
        st.subheader("HTML Creation Agent: Edit Your Slides")
        
        # Select a slide to edit
        slide_numbers = list(map(str, st.session_state.slide_order))
        selected_slide = st.selectbox("Select Slide to Edit", slide_numbers)
        
        if selected_slide:
            st.session_state.current_slide = selected_slide
            slide_html = st.session_state.slides_html.get(selected_slide, "")
            
            # Edit HTML
            new_html = st.text_area("Edit HTML", value=slide_html, height=300)
            
            if st.button("Update Slide"):
                update_slide(selected_slide, new_html)
                st.success(f"Slide {selected_slide} updated successfully!")
            
            # Preview the current slide
            st.subheader("Slide Preview")
            st.components.v1.html(new_html, height=600, scrolling=True)

# Tab 3: Customize Styling
with tabs[2]:
    st.subheader("Customize Presentation Styling")
    
    new_css = st.text_area("Edit CSS Styles", value=st.session_state.css_style, height=400)
    
    if st.button("Update Styling"):
        st.session_state.css_style = new_css
        
        # Update all slides with new CSS
        for slide_number in st.session_state.slide_order:
            html = st.session_state.slides_html.get(str(slide_number), "")
            if html:
                # Replace the CSS in the HTML
                start_tag = "<style>"
                end_tag = "</style>"
                start_idx = html.find(start_tag) + len(start_tag)
                end_idx = html.find(end_tag)
                
                if start_idx > len(start_tag) - 1 and end_idx > -1:
                    new_html = html[:start_idx] + new_css + html[end_idx:]
                    st.session_state.slides_html[str(slide_number)] = new_html
        
        st.success("Styling updated for all slides!")
    
    # Show a sample of how the styling looks
    if st.session_state.slides_html and st.session_state.slide_order:
        st.subheader("Style Preview")
        sample_slide = st.session_state.slides_html.get(str(st.session_state.slide_order[0]), "")
        if sample_slide:
            st.components.v1.html(sample_slide, height=600, scrolling=True)

# Tab 4: Preview & Export
with tabs[3]:
    if not st.session_state.slides_html:
        st.info("No slides have been generated yet. Go to the Create Presentation tab to get started.")
    else:
        st.subheader("Preview All Slides")
        
        for slide_number in st.session_state.slide_order:
            st.markdown(f"### Slide {slide_number}")
            html = st.session_state.slides_html.get(str(slide_number), "")
            if html:
                st.components.v1.html(html, height=600, scrolling=True)
                st.markdown("---")
        
        st.subheader("Export Presentation")
        st.markdown(get_slide_download_link(), unsafe_allow_html=True)
        st.info("This will download a ZIP file containing all your slides as HTML files with consistent styling. You can open each slide in a browser or use them in your presentation software.")

# Sidebar with instructions and settings
st.sidebar.title("AI PowerPoint Maker")

# OpenAI API Key input
st.sidebar.subheader("OpenAI API Settings")
api_key = st.sidebar.text_input("Enter your OpenAI API Key", 
                               value=st.session_state.openai_api_key,
                               type="password", 
                               help="Your API key will be used to generate better presentation content and slides.")

if api_key:
    st.session_state.openai_api_key = api_key
    st.sidebar.success("✅ API Key set successfully!")
else:
    st.sidebar.info("ℹ️ No API Key provided. The app will use simulated responses.")

st.sidebar.markdown("---")
st.sidebar.subheader("How It Works")
st.sidebar.markdown("""
This app uses a multi-agent approach to create presentations:

1. **Planning Agent**: Develops the structure and content flow of your presentation
2. **HTML Creation Agent**: Creates editable HTML slides with consistent styling

### Steps to Use:
1. Define your presentation topic and key points
2. Generate the presentation plan
3. Edit individual slides if needed
4. Customize the styling to match your preferences
5. Preview all slides and export them as HTML files

### Benefits:
- Get a professionally structured presentation quickly
- Edit the HTML directly for complete customization
- Consistent styling across all slides
- Export as individual HTML files for easy use
""")

st.sidebar.markdown("---")
st.sidebar.info("When the OpenAI API key is provided, this app will use the actual OpenAI API for both the planning and HTML creation steps. Without an API key, it will use simulated responses for demonstration purposes.")