cryogenic22 commited on
Commit
7a2243c
Β·
verified Β·
1 Parent(s): d5e697f

Create slide_editor_enhancements.py

Browse files
Files changed (1) hide show
  1. slide_editor_enhancements.py +449 -0
slide_editor_enhancements.py ADDED
@@ -0,0 +1,449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import base64
3
+ from io import BytesIO
4
+ import json
5
+ import os
6
+ try:
7
+ from utils import create_slide_preview, TEMPLATES
8
+ except ImportError:
9
+ # Fallback templates if utils is not importable
10
+ TEMPLATES = {
11
+ "professional": {
12
+ "name": "Professional",
13
+ "colors": {"primary": "#0F52BA", "secondary": "#E6E6E6", "accent": "#D4AF37", "text": "#333333"},
14
+ "fonts": {"title": "Arial, sans-serif", "body": "Calibri, sans-serif"}
15
+ }
16
+ }
17
+
18
+ def create_slide_preview(slide, template_name):
19
+ """Fallback slide preview function"""
20
+ return f"<div style='border:1px solid gray; padding:20px;'><h3>{slide.get('title', 'Untitled')}</h3><p>Preview not available</p></div>"
21
+ try:
22
+ from visual_elements import (
23
+ search_stock_images,
24
+ download_image,
25
+ analyze_slide_for_visuals,
26
+ generate_html_preview_with_visuals,
27
+ STANDARD_SHAPES,
28
+ ICON_CATEGORIES
29
+ )
30
+ except ImportError:
31
+ # Fallback implementations if visual_elements cannot be imported
32
+ def search_stock_images(query, per_page=10):
33
+ """Fallback for search_stock_images"""
34
+ return [{"url": f"https://via.placeholder.com/800x600?text={query}_{i}",
35
+ "thumbnail": f"https://via.placeholder.com/100x100?text={query}_{i}"}
36
+ for i in range(1, 6)]
37
+
38
+ def download_image(url, filename=None):
39
+ """Fallback for download_image"""
40
+ return BytesIO()
41
+
42
+ def analyze_slide_for_visuals(slide_content):
43
+ """Fallback for analyze_slide_for_visuals"""
44
+ return {
45
+ 'need_chart': False,
46
+ 'chart_type': 'column',
47
+ 'need_image': False,
48
+ 'image_query': slide_content.get('title', ''),
49
+ 'icon_suggestions': [],
50
+ 'suggested_layout': 'Standard'
51
+ }
52
+
53
+ def generate_html_preview_with_visuals(slide, template_name):
54
+ """Fallback for generate_html_preview_with_visuals"""
55
+ return create_slide_preview(slide, template_name)
56
+
57
+ # Fallback shape and icon categories
58
+ STANDARD_SHAPES = {"rectangle": 1, "oval": 2, "triangle": 3}
59
+ ICON_CATEGORIES = {
60
+ "business": ["chart", "gear", "cube"],
61
+ "arrows": ["arrow", "up_arrow", "down_arrow"]
62
+ }
63
+ import os
64
+ import requests
65
+ from PIL import Image
66
+
67
+ def render_enhanced_slide_editor(slide_index, slide, template_name="professional"):
68
+ """Render the enhanced editor for a single slide with visual elements"""
69
+ from utils import TEMPLATES
70
+
71
+ # Get template info
72
+ template = TEMPLATES.get(template_name, TEMPLATES["professional"])
73
+ colors = template["colors"]
74
+
75
+ # Split into preview and editor columns
76
+ col1, col2 = st.columns([2, 3])
77
+
78
+ with col1:
79
+ st.subheader("Preview")
80
+ # Generate and display HTML preview with visuals
81
+ try:
82
+ # Use enhanced preview with visuals
83
+ preview_html = generate_html_preview_with_visuals(slide, template_name)
84
+ st.components.v1.html(preview_html, height=400)
85
+ except Exception as e:
86
+ st.error(f"Error generating preview: {str(e)}")
87
+ # Fall back to standard preview
88
+ preview_html = create_slide_preview(slide, template_name)
89
+ st.components.v1.html(preview_html, height=350)
90
+
91
+ # Visual elements analysis
92
+ analysis = analyze_slide_for_visuals(slide)
93
+
94
+ # Quick styling options
95
+ with st.expander("🎨 Layout & Style"):
96
+ # Smart layout suggestion
97
+ suggested_layout = analysis['suggested_layout']
98
+ layout_options = ["Standard", "Two Column", "Title Only", "Quote", "Picture with Caption"]
99
+
100
+ st.write(f"πŸ’‘ **Suggested layout:** {suggested_layout}")
101
+
102
+ selected_layout = st.selectbox(
103
+ "Layout",
104
+ options=layout_options,
105
+ index=layout_options.index(suggested_layout) if suggested_layout in layout_options else 0,
106
+ key=f"layout_{slide_index}"
107
+ )
108
+
109
+ # Color scheme options
110
+ st.write("🎭 **Color Accent**")
111
+ color_options = {
112
+ "Default": colors["accent"],
113
+ "Blue": "#0066CC",
114
+ "Green": "#00AA55",
115
+ "Red": "#CC3300",
116
+ "Purple": "#9933CC",
117
+ "Orange": "#FF6600"
118
+ }
119
+ selected_color = st.color_picker(
120
+ "Accent Color",
121
+ value=colors["accent"],
122
+ key=f"color_{slide_index}"
123
+ )
124
+
125
+ # Apply button
126
+ if st.button("Apply Style", key=f"apply_style_{slide_index}"):
127
+ if "design" not in slide:
128
+ slide["design"] = {}
129
+ slide["design"]["layout"] = selected_layout
130
+ slide["design"]["accent_color"] = selected_color
131
+ st.success("Style applied!")
132
+
133
+ # Visual elements section
134
+ with st.expander("πŸ–ΌοΈ Visual Elements"):
135
+ # Chart options if analysis suggests a chart
136
+ if analysis['need_chart']:
137
+ st.write("πŸ“Š **Chart Recommendation**")
138
+ st.write(f"The content suggests a {analysis['chart_type']} chart would be appropriate.")
139
+
140
+ chart_types = ["column", "bar", "line", "pie", "area", "scatter"]
141
+ selected_chart = st.selectbox(
142
+ "Chart Type",
143
+ options=chart_types,
144
+ index=chart_types.index(analysis['chart_type']) if analysis['chart_type'] in chart_types else 0,
145
+ key=f"chart_{slide_index}"
146
+ )
147
+
148
+ if st.button("Add Chart", key=f"add_chart_{slide_index}"):
149
+ if "visuals" not in slide:
150
+ slide["visuals"] = {}
151
+ slide["visuals"]["chart"] = {
152
+ "type": selected_chart,
153
+ "data": "sample" # Will be replaced with actual data
154
+ }
155
+ st.success(f"Added {selected_chart} chart!")
156
+
157
+ # Image options
158
+ st.write("🏞️ **Images**")
159
+ image_source = st.radio(
160
+ "Image Source",
161
+ ["Stock Images", "Upload Image"],
162
+ key=f"img_source_{slide_index}"
163
+ )
164
+
165
+ if image_source == "Stock Images":
166
+ search_query = st.text_input(
167
+ "Search for images",
168
+ value=analysis['image_query'],
169
+ key=f"img_search_{slide_index}"
170
+ )
171
+
172
+ if st.button("Search", key=f"search_btn_{slide_index}"):
173
+ with st.spinner("Searching for images..."):
174
+ images = search_stock_images(search_query)
175
+
176
+ if not images:
177
+ st.warning("No images found. Try a different search term.")
178
+ else:
179
+ # Store images in session state for this slide
180
+ if "stock_images" not in st.session_state:
181
+ st.session_state.stock_images = {}
182
+ st.session_state.stock_images[slide_index] = images
183
+
184
+ st.success(f"Found {len(images)} images!")
185
+
186
+ # Display stored images if available
187
+ if "stock_images" in st.session_state and slide_index in st.session_state.stock_images:
188
+ images = st.session_state.stock_images[slide_index]
189
+
190
+ # Create a grid of images using columns
191
+ columns = st.columns(3)
192
+
193
+ for i, img in enumerate(images[:6]): # Limit to 6 images
194
+ with columns[i % 3]:
195
+ # Display thumbnail
196
+ st.image(img["thumbnail"], use_column_width=True)
197
+
198
+ if st.button("Select", key=f"select_img_{slide_index}_{i}"):
199
+ # Add image to slide
200
+ if "visuals" not in slide:
201
+ slide["visuals"] = {}
202
+ slide["visuals"]["image"] = {
203
+ "url": img["url"],
204
+ "source": "stock"
205
+ }
206
+ st.success("Image selected!")
207
+ else:
208
+ # Upload image
209
+ uploaded_file = st.file_uploader(
210
+ "Upload an image",
211
+ type=["jpg", "jpeg", "png", "gif"],
212
+ key=f"upload_{slide_index}"
213
+ )
214
+
215
+ if uploaded_file:
216
+ image = Image.open(uploaded_file)
217
+ st.image(image, width=250)
218
+
219
+ if st.button("Use This Image", key=f"use_upload_{slide_index}"):
220
+ # Store image in session state
221
+ if "uploaded_images" not in st.session_state:
222
+ st.session_state.uploaded_images = {}
223
+
224
+ # Convert image to base64 for storage
225
+ buffered = BytesIO()
226
+ image.save(buffered, format="PNG")
227
+ img_str = base64.b64encode(buffered.getvalue()).decode()
228
+
229
+ st.session_state.uploaded_images[slide_index] = img_str
230
+
231
+ # Add to slide
232
+ if "visuals" not in slide:
233
+ slide["visuals"] = {}
234
+ slide["visuals"]["image"] = {
235
+ "data": img_str,
236
+ "source": "upload"
237
+ }
238
+ st.success("Image added to slide!")
239
+
240
+ # Icon options
241
+ st.write("πŸ” **Icons & Shapes**")
242
+
243
+ # Create tabs for different icon categories
244
+ icon_tabs = st.tabs(list(ICON_CATEGORIES.keys()))
245
+
246
+ for i, (category, icons) in enumerate(ICON_CATEGORIES.items()):
247
+ with icon_tabs[i]:
248
+ # Display icons in a grid
249
+ cols = st.columns(4)
250
+ for j, icon in enumerate(icons):
251
+ with cols[j % 4]:
252
+ if st.button(icon, key=f"icon_{slide_index}_{category}_{j}"):
253
+ # Add icon to slide
254
+ if "visuals" not in slide:
255
+ slide["visuals"] = {}
256
+ if "icons" not in slide["visuals"]:
257
+ slide["visuals"]["icons"] = []
258
+
259
+ slide["visuals"]["icons"].append(icon)
260
+ st.success(f"Added {icon} icon!")
261
+
262
+ # AI image generation
263
+ with st.expander("πŸ€– AI Image Generation"):
264
+ st.write("Generate a custom image using AI based on your slide content")
265
+
266
+ # Image prompt
267
+ prompt_base = f"Creating an image for slide '{slide.get('title', '')}'"
268
+ image_prompt = st.text_area(
269
+ "Image description",
270
+ value=prompt_base,
271
+ key=f"img_prompt_{slide_index}",
272
+ height=100
273
+ )
274
+
275
+ image_style = st.selectbox(
276
+ "Style",
277
+ ["Realistic", "Digital Art", "Watercolor", "Minimalist", "Corporate"],
278
+ key=f"img_style_{slide_index}"
279
+ )
280
+
281
+ if st.button("Generate Image", key=f"gen_img_{slide_index}"):
282
+ # In a real implementation, this would call an AI image generation API
283
+ st.info("AI Image generation would be implemented here with your preferred provider (DALL-E, Midjourney, etc.)")
284
+ st.info("For now, we'll use a placeholder to demonstrate the functionality")
285
+
286
+ if "visuals" not in slide:
287
+ slide["visuals"] = {}
288
+ slide["visuals"]["ai_image"] = {
289
+ "prompt": image_prompt,
290
+ "style": image_style
291
+ }
292
+ st.success("AI image generation request saved!")
293
+
294
+ with col2:
295
+ st.subheader("Content Editor")
296
+
297
+ # Title editing with AI assistance
298
+ st.write("##### πŸ“ Slide Title")
299
+ col1, col2 = st.columns([3, 1])
300
+ with col1:
301
+ slide_title = st.text_input(f"Title ###{slide_index}", value=slide.get('title', ''))
302
+ with col2:
303
+ if st.button("πŸͺ„ Improve", key=f"improve_title_{slide_index}"):
304
+ try:
305
+ with st.spinner("🐊 SlideGator.AI is enhancing your title..."):
306
+ from agents import enhance_slide_component
307
+ updated_slide, result = enhance_slide_component(slide, "title", "Make it concise and impactful")
308
+ if "error" not in result:
309
+ st.session_state.slides_content[slide_index] = updated_slide
310
+ st.success("Title improved!")
311
+ st.rerun()
312
+ else:
313
+ st.error(f"Error: {result.get('error')}")
314
+ except Exception as e:
315
+ st.error(f"Error improving title: {str(e)}")
316
+
317
+ # Content editing
318
+ st.write("##### πŸ“„ Content")
319
+ if isinstance(slide.get('content', []), list):
320
+ content_text = "\n".join(slide.get('content', []))
321
+ else:
322
+ content_text = slide.get('content', '')
323
+
324
+ col1, col2 = st.columns([3, 1])
325
+ with col1:
326
+ content = st.text_area(f"Content ###{slide_index}", value=content_text, height=150)
327
+ with col2:
328
+ st.write("")
329
+ st.write("")
330
+ if st.button("πŸͺ„ Improve", key=f"improve_content_{slide_index}"):
331
+ try:
332
+ with st.spinner("🐊 SlideGator.AI is enhancing your content..."):
333
+ from agents import enhance_slide_component
334
+ slide["content"] = content.split("\n") if "\n" in content else [content]
335
+ updated_slide, result = enhance_slide_component(slide, "content", "Make it clear and concise")
336
+ if "error" not in result:
337
+ st.session_state.slides_content[slide_index] = updated_slide
338
+ st.success("Content improved!")
339
+ st.rerun()
340
+ else:
341
+ st.error(f"Error: {result.get('error')}")
342
+ except Exception as e:
343
+ st.error(f"Error improving content: {str(e)}")
344
+
345
+ # Visual elements with AI assistance
346
+ st.write("##### πŸ–ΌοΈ Visual Suggestions")
347
+ if isinstance(slide.get('visual_elements', []), list):
348
+ visuals_text = "\n".join(slide.get('visual_elements', []))
349
+ else:
350
+ visuals_text = slide.get('visual_elements', '')
351
+
352
+ col1, col2 = st.columns([3, 1])
353
+ with col1:
354
+ visuals = st.text_area(f"Visual Elements ###{slide_index}", value=visuals_text, height=100)
355
+ with col2:
356
+ st.write("")
357
+ if st.button("🎨 Suggest", key=f"suggest_visuals_{slide_index}"):
358
+ try:
359
+ with st.spinner("🐊 SlideGator.AI is suggesting visuals..."):
360
+ from agents import enhance_slide_component
361
+ slide["visual_elements"] = visuals.split("\n") if "\n" in visuals else [visuals]
362
+ updated_slide, result = enhance_slide_component(slide, "visuals", "Be specific and creative")
363
+ if "error" not in result:
364
+ st.session_state.slides_content[slide_index] = updated_slide
365
+ st.success("Visual suggestions added!")
366
+ st.rerun()
367
+ else:
368
+ st.error(f"Error: {result.get('error')}")
369
+ except Exception as e:
370
+ st.error(f"Error suggesting visuals: {str(e)}")
371
+
372
+ # Presenter notes
373
+ st.write("##### 🎀 Presenter Notes")
374
+ notes = st.text_area(f"Notes ###{slide_index}", value=slide.get('notes', ''), height=100)
375
+ if st.button("πŸͺ„ Generate Notes", key=f"generate_notes_{slide_index}"):
376
+ try:
377
+ with st.spinner("🐊 SlideGator.AI is writing speaker notes..."):
378
+ from agents import enhance_slide_component
379
+ updated_slide, result = enhance_slide_component(slide, "notes")
380
+ if "error" not in result:
381
+ st.session_state.slides_content[slide_index] = updated_slide
382
+ st.success("Speaker notes generated!")
383
+ st.rerun()
384
+ else:
385
+ st.error(f"Error: {result.get('error')}")
386
+ except Exception as e:
387
+ st.error(f"Error generating notes: {str(e)}")
388
+
389
+ # LLM model selection for this specific slide
390
+ st.write("##### 🧠 AI Enhancement Settings")
391
+ with st.expander("Advanced AI Settings"):
392
+ try:
393
+ from multi_llm_provider import get_ai_manager
394
+ ai_manager = get_ai_manager()
395
+ available_models = ai_manager.get_available_models()
396
+ except (ImportError, Exception):
397
+ available_models = {
398
+ "claude-3-sonnet-20250219": "Claude 3 Sonnet (Default)",
399
+ "claude-3-haiku-20250319": "Claude 3 Haiku (Faster)",
400
+ "claude-3-opus-20250229": "Claude 3 Opus (Highest Quality)"
401
+ }
402
+
403
+ selected_llm = st.selectbox(
404
+ "AI Model for this slide",
405
+ options=list(available_models.keys()),
406
+ format_func=lambda x: available_models.get(x, x),
407
+ key=f"llm_{slide_index}"
408
+ )
409
+
410
+ if "ai_settings" not in slide:
411
+ slide["ai_settings"] = {}
412
+
413
+ slide["ai_settings"]["model"] = selected_llm
414
+
415
+ # Web search integration
416
+ st.write("##### πŸ” Web Search Integration")
417
+ enable_search = st.checkbox(
418
+ "Enable web search for up-to-date content",
419
+ value=slide.get("ai_settings", {}).get("web_search", False),
420
+ key=f"search_{slide_index}"
421
+ )
422
+
423
+ if enable_search:
424
+ search_query = st.text_input(
425
+ "Search query",
426
+ value=slide.get("ai_settings", {}).get("search_query", f"latest {slide.get('title', '')}"),
427
+ key=f"search_query_{slide_index}"
428
+ )
429
+
430
+ slide["ai_settings"]["web_search"] = enable_search
431
+ slide["ai_settings"]["search_query"] = search_query
432
+
433
+ if st.button("Fetch Latest Content", key=f"fetch_{slide_index}"):
434
+ st.info("This would fetch the latest information from the web to update your slide.")
435
+ # In a real implementation, this would call a search API
436
+
437
+ # Update slide with edits
438
+ slide["title"] = slide_title
439
+ if "\n" in content:
440
+ slide["content"] = content.split("\n")
441
+ else:
442
+ slide["content"] = [content] if content else []
443
+ if "\n" in visuals:
444
+ slide["visual_elements"] = visuals.split("\n")
445
+ else:
446
+ slide["visual_elements"] = [visuals] if visuals else []
447
+ slide["notes"] = notes
448
+
449
+ return slide