cryogenic22 commited on
Commit
8ac952c
·
verified ·
1 Parent(s): 7a22c13

Update utils.py

Browse files
Files changed (1) hide show
  1. utils.py +382 -1
utils.py CHANGED
@@ -21,4 +21,385 @@ TEMPLATES = {
21
  "body": "Calibri, sans-serif"
22
  }
23
  },
24
- "creative": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  "body": "Calibri, sans-serif"
22
  }
23
  },
24
+ "creative": {
25
+ "name": "Creative",
26
+ "description": "Vibrant design with modern elements and bold colors",
27
+ "colors": {
28
+ "primary": "#FF5757", # Coral red
29
+ "secondary": "#FFDE59", # Yellow
30
+ "accent": "#5CE1E6", # Turquoise
31
+ "text": "#333333" # Dark gray
32
+ },
33
+ "fonts": {
34
+ "title": "Montserrat, sans-serif",
35
+ "body": "Roboto, sans-serif"
36
+ }
37
+ },
38
+ "minimalist": {
39
+ "name": "Minimalist",
40
+ "description": "Simple, elegant design with lots of whitespace",
41
+ "colors": {
42
+ "primary": "#FFFFFF", # White
43
+ "secondary": "#F5F5F5", # Off-white
44
+ "accent": "#000000", # Black
45
+ "text": "#333333" # Dark gray
46
+ },
47
+ "fonts": {
48
+ "title": "Helvetica, Arial, sans-serif",
49
+ "body": "Helvetica, Arial, sans-serif"
50
+ }
51
+ },
52
+ "custom": {
53
+ "name": "Custom",
54
+ "description": "Your custom uploaded template",
55
+ "colors": {
56
+ "primary": "#0073E6", # Blue
57
+ "secondary": "#FFFFFF", # White
58
+ "accent": "#FF6D00", # Orange
59
+ "text": "#333333" # Dark gray
60
+ },
61
+ "fonts": {
62
+ "title": "Arial, sans-serif",
63
+ "body": "Calibri, sans-serif"
64
+ }
65
+ }
66
+ }
67
+
68
+ # LLM helper function
69
+ def get_llm():
70
+ """Get the LLM client with API key from environment"""
71
+ return ChatAnthropic(
72
+ model="claude-3-5-sonnet-20240620",
73
+ anthropic_api_key=st.secrets["ANTHROPIC_API_KEY"],
74
+ temperature=0.7
75
+ )
76
+
77
+ def generate_storyboard(title, purpose, audience):
78
+ """Generate a presentation storyboard using Claude"""
79
+ llm = get_llm()
80
+
81
+ prompt = f"""
82
+ You are an expert presentation designer with deep expertise in creating compelling PowerPoint presentations.
83
+
84
+ I need you to create a storyboard for a presentation with the following details:
85
+ - Title: {title}
86
+ - Purpose: {purpose}
87
+ - Target Audience: {audience}
88
+
89
+ For each slide, include:
90
+ 1. Slide title
91
+ 2. Slide purpose
92
+ 3. Key content points
93
+ 4. Suggested visual elements
94
+
95
+ Think deeply about the narrative flow, ensuring each slide builds on the previous one.
96
+ Format your response as a JSON list where each item is a slide with the above elements.
97
+
98
+ Start with an engaging title slide and end with a clear call-to-action or summary slide.
99
+
100
+ The response should be a valid JSON array like this:
101
+ [
102
+ {{
103
+ "title": "Slide Title",
104
+ "purpose": "Purpose of this slide",
105
+ "key_points": ["Key point 1", "Key point 2"],
106
+ "visual_elements": ["Visual element 1", "Visual element 2"]
107
+ }},
108
+ ...
109
+ ]
110
+ """
111
+
112
+ st.sidebar.write("Generating storyboard...")
113
+
114
+ try:
115
+ messages = [
116
+ SystemMessage(content="You are an expert presentation designer specializing in creating logical, compelling PowerPoint storyboards. Always output valid JSON without explanations or extra text."),
117
+ HumanMessage(content=prompt)
118
+ ]
119
+ response = llm.invoke(messages)
120
+ response_text = response.content
121
+
122
+ # Extract the JSON from the response
123
+ json_start = response_text.find("[")
124
+ json_end = response_text.rfind("]") + 1
125
+
126
+ if json_start >= 0 and json_end > 0:
127
+ json_str = response_text[json_start:json_end]
128
+ parsed_json = json.loads(json_str)
129
+ st.sidebar.write(f"Generated storyboard with {len(parsed_json)} slides")
130
+ return parsed_json
131
+ else:
132
+ st.sidebar.error("JSON not found in response")
133
+ # Create a default storyboard as fallback
134
+ return default_storyboard(title)
135
+ except Exception as e:
136
+ st.sidebar.error(f"Error generating storyboard: {str(e)}")
137
+ return default_storyboard(title)
138
+
139
+ def default_storyboard(title):
140
+ """Create a default storyboard when API calls fail"""
141
+ return [
142
+ {
143
+ "title": title,
144
+ "purpose": "Title slide",
145
+ "key_points": ["Introduction"],
146
+ "visual_elements": ["Company logo", "Presenter name"]
147
+ },
148
+ {
149
+ "title": "Agenda",
150
+ "purpose": "Outline the presentation contents",
151
+ "key_points": ["Overview of key topics", "Agenda items", "Presentation goals"],
152
+ "visual_elements": ["Bullet list", "Simple timeline graphic"]
153
+ },
154
+ {
155
+ "title": "Key Points",
156
+ "purpose": "Present main content",
157
+ "key_points": ["Main point 1", "Main point 2", "Main point 3"],
158
+ "visual_elements": ["Relevant icons", "Supporting image"]
159
+ },
160
+ {
161
+ "title": "Conclusion",
162
+ "purpose": "Summarize key points and next steps",
163
+ "key_points": ["Summary of main points", "Call to action", "Next steps"],
164
+ "visual_elements": ["Summary graphic", "Contact information"]
165
+ }
166
+ ]
167
+
168
+ def generate_slide_content(slide_info, template_name):
169
+ """Generate detailed content for a slide using Claude"""
170
+ llm = get_llm()
171
+
172
+ prompt = f"""
173
+ Create detailed content for a PowerPoint slide with the following information:
174
+
175
+ Slide Title: {slide_info.get('title', 'Untitled')}
176
+ Slide Purpose: {slide_info.get('purpose', 'No purpose provided')}
177
+ Key Points: {slide_info.get('key_points', [])}
178
+ Visual Elements: {slide_info.get('visual_elements', [])}
179
+
180
+ The presentation uses the {template_name} template style.
181
+
182
+ Provide the following in your response as JSON:
183
+ 1. Final slide title (refined if needed)
184
+ 2. Main content text (bullet points or paragraphs as appropriate)
185
+ 3. Notes for the presenter
186
+ 4. Specific visual instructions (charts, images, etc.)
187
+
188
+ Your response must be a valid JSON object like this:
189
+ {{
190
+ "title": "Refined Title",
191
+ "content": ["Point 1", "Point 2", "Point 3"],
192
+ "notes": "Notes for the presenter",
193
+ "visual_instructions": "Details about visuals"
194
+ }}
195
+
196
+ Focus on clarity, impact, and alignment with the slide's purpose.
197
+ """
198
+
199
+ try:
200
+ messages = [
201
+ SystemMessage(content="You are an expert presentation content creator. Always output valid JSON without explanations or extra text."),
202
+ HumanMessage(content=prompt)
203
+ ]
204
+ response = llm.invoke(messages)
205
+ response_text = response.content
206
+
207
+ # Extract the JSON from the response
208
+ json_start = response_text.find("{")
209
+ json_end = response_text.rfind("}") + 1
210
+
211
+ if json_start >= 0 and json_end > 0:
212
+ json_str = response_text[json_start:json_end]
213
+ return json.loads(json_str)
214
+ else:
215
+ # Fallback: create a basic slide
216
+ return {
217
+ "title": slide_info.get('title', 'Untitled'),
218
+ "content": slide_info.get('key_points', ["Add content here"]),
219
+ "notes": f"This slide covers: {slide_info.get('purpose', 'No purpose specified')}",
220
+ "visual_instructions": str(slide_info.get('visual_elements', ["No visual elements specified"]))
221
+ }
222
+ except Exception as e:
223
+ st.sidebar.error(f"Error generating slide content: {str(e)}")
224
+ # Return a default structure
225
+ return {
226
+ "title": slide_info.get('title', 'Untitled'),
227
+ "content": slide_info.get('key_points', ["Content to be added"]),
228
+ "notes": "Add presenter notes here",
229
+ "visual_instructions": "Add visual elements here"
230
+ }
231
+
232
+ def create_slide_preview(slide, template_name):
233
+ """Create an HTML preview of a slide"""
234
+ # Get template info
235
+ template = TEMPLATES.get(template_name, TEMPLATES["professional"])
236
+ colors = template["colors"]
237
+ fonts = template["fonts"]
238
+
239
+ # Prepare content
240
+ title = slide.get('title', 'Untitled Slide')
241
+
242
+ if isinstance(slide.get('content', []), list):
243
+ content = slide.get('content', [])
244
+ else:
245
+ content_text = slide.get('content', '')
246
+ content = content_text.split('\n') if content_text else []
247
+
248
+ # Determine layout based on slide design or content
249
+ layout_type = "standard"
250
+ if "design" in slide and "layout" in slide["design"]:
251
+ layout_type = slide["design"]["layout"].lower()
252
+
253
+ # Generate HTML based on layout type
254
+ if "two column" in layout_type or "comparison" in layout_type:
255
+ # Two-column layout
256
+ mid_point = len(content) // 2
257
+ left_content = content[:mid_point]
258
+ right_content = content[mid_point:]
259
+
260
+ content_html = f"""
261
+ <div class="row" style="display: flex; flex-direction: row;">
262
+ <div class="col" style="flex: 1; padding-right: 10px;">
263
+ <ul>
264
+ {"".join(f'<li style="margin-bottom: 8px;">{item}</li>' for item in left_content)}
265
+ </ul>
266
+ </div>
267
+ <div class="col" style="flex: 1; padding-left: 10px;">
268
+ <ul>
269
+ {"".join(f'<li style="margin-bottom: 8px;">{item}</li>' for item in right_content)}
270
+ </ul>
271
+ </div>
272
+ </div>
273
+ """
274
+ elif "quote" in layout_type:
275
+ # Quote layout
276
+ if content:
277
+ quote_text = content[0] if isinstance(content, list) and len(content) > 0 else "Quote text"
278
+ author = content[1] if isinstance(content, list) and len(content) > 1 else ""
279
+
280
+ content_html = f"""
281
+ <div class="quote-container" style="text-align: center; padding: 20px;">
282
+ <blockquote style="font-size: 24px; font-style: italic; color: {colors['accent']};">
283
+ "{quote_text}"
284
+ </blockquote>
285
+ {f'<div style="text-align: right; font-size: 18px;">— {author}</div>' if author else ''}
286
+ </div>
287
+ """
288
+ else:
289
+ content_html = '<div class="quote-container" style="text-align: center;"><p>No quote content</p></div>'
290
+ else:
291
+ # Standard layout with bullet points
292
+ content_html = f"""
293
+ <ul>
294
+ {"".join(f'<li style="margin-bottom: 8px;">{item}</li>' for item in content)}
295
+ </ul>
296
+ """
297
+
298
+ # Create the full HTML preview
299
+ html = f"""
300
+ <div class="slide-preview" style="background-color: {colors['secondary']}; border: 1px solid #dee2e6; border-radius: 4px; padding: 20px; height: 300px; overflow: hidden; font-family: {fonts['body']};">
301
+ <div class="slide-title" style="color: {colors['text']}; font-family: {fonts['title']}; font-size: 24px; margin-bottom: 20px; border-bottom: 2px solid {colors['primary']}; padding-bottom: 10px;">
302
+ {title}
303
+ </div>
304
+ <div class="slide-content" style="color: {colors['text']}; font-family: {fonts['body']}; font-size: 16px;">
305
+ {content_html}
306
+ </div>
307
+ </div>
308
+ """
309
+
310
+ return html
311
+
312
+ def create_ppt(slides_content, template_name):
313
+ """Create a PowerPoint presentation from the slides content"""
314
+ # Create a basic presentation
315
+ prs = Presentation()
316
+
317
+ # Get template info
318
+ template = TEMPLATES.get(template_name, TEMPLATES["professional"])
319
+
320
+ # Title slide
321
+ title_slide_layout = prs.slide_layouts[0]
322
+ slide = prs.slides.add_slide(title_slide_layout)
323
+ title = slide.shapes.title
324
+ subtitle = slide.placeholders[1]
325
+
326
+ title.text = slides_content[0]["title"]
327
+ subtitle.text = "Created with AI PowerPoint Creator"
328
+
329
+ # Content slides
330
+ for slide_content in slides_content[1:]:
331
+ # Determine best layout based on content or explicit design
332
+ layout_type = "standard"
333
+ if "design" in slide_content and "layout" in slide_content["design"]:
334
+ layout_type = slide_content["design"]["layout"].lower()
335
+
336
+ # Select appropriate slide layout
337
+ if "two column" in layout_type or "comparison" in layout_type:
338
+ content_slide_layout = prs.slide_layouts[3] if len(prs.slide_layouts) > 3 else prs.slide_layouts[1]
339
+ elif "title only" in layout_type or "quote" in layout_type:
340
+ content_slide_layout = prs.slide_layouts[5] if len(prs.slide_layouts) > 5 else prs.slide_layouts[0]
341
+ elif "picture" in layout_type:
342
+ content_slide_layout = prs.slide_layouts[6] if len(prs.slide_layouts) > 6 else prs.slide_layouts[1]
343
+ else:
344
+ content_slide_layout = prs.slide_layouts[1] # Default: Title and Content
345
+
346
+ slide = prs.slides.add_slide(content_slide_layout)
347
+
348
+ # Add title
349
+ if slide.shapes.title:
350
+ slide.shapes.title.text = slide_content["title"]
351
+
352
+ # Add content based on layout and available placeholders
353
+ content_placeholders = [shape for shape in slide.placeholders
354
+ if shape.placeholder_format.type != 1] # 1 is title
355
+
356
+ if len(content_placeholders) > 0:
357
+ content_placeholder = content_placeholders[0]
358
+
359
+ # Format content based on what's available and layout type
360
+ if "content" in slide_content:
361
+ if isinstance(slide_content["content"], list):
362
+ if "two column" in layout_type and len(content_placeholders) > 1:
363
+ # Split content for two columns
364
+ left_placeholder = content_placeholders[0]
365
+ right_placeholder = content_placeholders[1]
366
+
367
+ mid_point = len(slide_content["content"]) // 2
368
+ left_content = slide_content["content"][:mid_point]
369
+ right_content = slide_content["content"][mid_point:]
370
+
371
+ left_placeholder.text = "\n".join(f"• {point}" for point in left_content)
372
+ right_placeholder.text = "\n".join(f"• {point}" for point in right_content)
373
+ else:
374
+ # Standard bullet points
375
+ content_placeholder.text = "\n".join(f"• {point}" for point in slide_content["content"])
376
+ else:
377
+ # Plain text
378
+ content_placeholder.text = slide_content["content"]
379
+ else:
380
+ content_placeholder.text = "Content to be added"
381
+
382
+ # Add notes if available
383
+ if "notes" in slide_content and slide_content["notes"]:
384
+ notes_slide = slide.notes_slide
385
+ notes_slide.notes_text_frame.text = slide_content["notes"]
386
+
387
+ # Save to BytesIO
388
+ output = BytesIO()
389
+ prs.save(output)
390
+ output.seek(0)
391
+ return output
392
+
393
+ def add_custom_template(template_file):
394
+ """Add a custom template from an uploaded file"""
395
+ try:
396
+ # Validate it's a proper PowerPoint file
397
+ file_content = template_file.getvalue()
398
+ prs = Presentation(BytesIO(file_content))
399
+
400
+ # Store in session state
401
+ st.session_state.custom_template = file_content
402
+
403
+ return True, f"Custom template '{template_file.name}' uploaded successfully!"
404
+ except Exception as e:
405
+ return False, f"Error with template file: {str(e)}"