Spaces:
Runtime error
Runtime error
Create utils.py
Browse files
utils.py
CHANGED
|
@@ -4,20 +4,7 @@ from io import BytesIO
|
|
| 4 |
import json
|
| 5 |
import anthropic
|
| 6 |
import os
|
| 7 |
-
|
| 8 |
-
# Initialize the Claude client with API key from environment (Huggingface secrets)
|
| 9 |
-
api_key = os.getenv("ANTHROPIC_API_KEY")
|
| 10 |
-
if not api_key:
|
| 11 |
-
st.sidebar.error("ANTHROPIC_API_KEY not found in environment variables")
|
| 12 |
-
client = None
|
| 13 |
-
else:
|
| 14 |
-
# Create the client
|
| 15 |
-
try:
|
| 16 |
-
client = anthropic.Anthropic(api_key=api_key)
|
| 17 |
-
st.sidebar.success("Anthropic API client initialized successfully")
|
| 18 |
-
except Exception as e:
|
| 19 |
-
st.sidebar.error(f"Error initializing Anthropic API: {str(e)}")
|
| 20 |
-
client = None
|
| 21 |
|
| 22 |
# Define available templates
|
| 23 |
TEMPLATES = {
|
|
@@ -79,12 +66,32 @@ TEMPLATES = {
|
|
| 79 |
}
|
| 80 |
}
|
| 81 |
|
| 82 |
-
def generate_storyboard(title, purpose, audience):
|
| 83 |
-
"""Generate a presentation storyboard using
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
prompt = f"""
|
| 89 |
You are an expert presentation designer with deep expertise in creating compelling PowerPoint presentations.
|
| 90 |
|
|
@@ -93,6 +100,8 @@ def generate_storyboard(title, purpose, audience):
|
|
| 93 |
- Purpose: {purpose}
|
| 94 |
- Target Audience: {audience}
|
| 95 |
|
|
|
|
|
|
|
| 96 |
For each slide, include:
|
| 97 |
1. Slide title
|
| 98 |
2. Slide purpose
|
|
@@ -116,21 +125,21 @@ def generate_storyboard(title, purpose, audience):
|
|
| 116 |
]
|
| 117 |
"""
|
| 118 |
|
|
|
|
|
|
|
| 119 |
with st.sidebar:
|
| 120 |
progress_text = st.empty()
|
| 121 |
-
progress_text.write("Generating storyboard...")
|
| 122 |
|
| 123 |
try:
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
]
|
| 132 |
)
|
| 133 |
-
response_text = response.content[0].text
|
| 134 |
|
| 135 |
# For debugging, show raw response
|
| 136 |
st.sidebar.write("Raw response: " + response_text[:200] + "...")
|
|
@@ -143,6 +152,13 @@ def generate_storyboard(title, purpose, audience):
|
|
| 143 |
json_str = response_text[json_start:json_end]
|
| 144 |
parsed_json = json.loads(json_str)
|
| 145 |
progress_text.write(f"Generated storyboard with {len(parsed_json)} slides")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
return parsed_json
|
| 147 |
else:
|
| 148 |
progress_text.error("JSON not found in response")
|
|
@@ -185,11 +201,15 @@ def default_storyboard(title):
|
|
| 185 |
}
|
| 186 |
]
|
| 187 |
|
| 188 |
-
def generate_slide_content(slide_info, template_name):
|
| 189 |
-
"""Generate detailed content for a slide using
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
prompt = f"""
|
| 195 |
Create detailed content for a PowerPoint slide with the following information:
|
|
@@ -218,17 +238,17 @@ def generate_slide_content(slide_info, template_name):
|
|
| 218 |
Focus on clarity, impact, and alignment with the slide's purpose.
|
| 219 |
"""
|
| 220 |
|
|
|
|
|
|
|
| 221 |
try:
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
]
|
| 230 |
)
|
| 231 |
-
response_text = response.content[0].text
|
| 232 |
|
| 233 |
# Extract the JSON from the response
|
| 234 |
json_start = response_text.find("{")
|
|
@@ -236,7 +256,14 @@ def generate_slide_content(slide_info, template_name):
|
|
| 236 |
|
| 237 |
if json_start >= 0 and json_end > 0:
|
| 238 |
json_str = response_text[json_start:json_end]
|
| 239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
else:
|
| 241 |
# Fallback: create a basic slide
|
| 242 |
st.sidebar.error(f"JSON not found in response: {response_text}")
|
|
@@ -264,6 +291,12 @@ def create_slide_preview(slide, template_name):
|
|
| 264 |
colors = template["colors"]
|
| 265 |
fonts = template["fonts"]
|
| 266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
# Prepare content
|
| 268 |
title = slide.get('title', 'Untitled Slide')
|
| 269 |
|
|
@@ -337,87 +370,6 @@ def create_slide_preview(slide, template_name):
|
|
| 337 |
|
| 338 |
return html
|
| 339 |
|
| 340 |
-
def create_ppt(slides_content, template_name):
|
| 341 |
-
"""Create a PowerPoint presentation from the slides content"""
|
| 342 |
-
# Create a basic presentation
|
| 343 |
-
prs = Presentation()
|
| 344 |
-
|
| 345 |
-
# Get template info
|
| 346 |
-
template = TEMPLATES.get(template_name, TEMPLATES["professional"])
|
| 347 |
-
|
| 348 |
-
# Title slide
|
| 349 |
-
title_slide_layout = prs.slide_layouts[0]
|
| 350 |
-
slide = prs.slides.add_slide(title_slide_layout)
|
| 351 |
-
title = slide.shapes.title
|
| 352 |
-
subtitle = slide.placeholders[1]
|
| 353 |
-
|
| 354 |
-
title.text = slides_content[0]["title"]
|
| 355 |
-
subtitle.text = "Created with AI PowerPoint Creator"
|
| 356 |
-
|
| 357 |
-
# Content slides
|
| 358 |
-
for slide_content in slides_content[1:]:
|
| 359 |
-
# Determine best layout based on content or explicit design
|
| 360 |
-
layout_type = "standard"
|
| 361 |
-
if "design" in slide_content and "layout" in slide_content["design"]:
|
| 362 |
-
layout_type = slide_content["design"]["layout"].lower()
|
| 363 |
-
|
| 364 |
-
# Select appropriate slide layout
|
| 365 |
-
if "two column" in layout_type or "comparison" in layout_type:
|
| 366 |
-
content_slide_layout = prs.slide_layouts[3] if len(prs.slide_layouts) > 3 else prs.slide_layouts[1]
|
| 367 |
-
elif "title only" in layout_type or "quote" in layout_type:
|
| 368 |
-
content_slide_layout = prs.slide_layouts[5] if len(prs.slide_layouts) > 5 else prs.slide_layouts[0]
|
| 369 |
-
elif "picture" in layout_type:
|
| 370 |
-
content_slide_layout = prs.slide_layouts[6] if len(prs.slide_layouts) > 6 else prs.slide_layouts[1]
|
| 371 |
-
else:
|
| 372 |
-
content_slide_layout = prs.slide_layouts[1] # Default: Title and Content
|
| 373 |
-
|
| 374 |
-
slide = prs.slides.add_slide(content_slide_layout)
|
| 375 |
-
|
| 376 |
-
# Add title
|
| 377 |
-
if slide.shapes.title:
|
| 378 |
-
slide.shapes.title.text = slide_content["title"]
|
| 379 |
-
|
| 380 |
-
# Add content based on layout and available placeholders
|
| 381 |
-
content_placeholders = [shape for shape in slide.placeholders
|
| 382 |
-
if shape.placeholder_format.type != 1] # 1 is title
|
| 383 |
-
|
| 384 |
-
if len(content_placeholders) > 0:
|
| 385 |
-
content_placeholder = content_placeholders[0]
|
| 386 |
-
|
| 387 |
-
# Format content based on what's available and layout type
|
| 388 |
-
if "content" in slide_content:
|
| 389 |
-
if isinstance(slide_content["content"], list):
|
| 390 |
-
if "two column" in layout_type and len(content_placeholders) > 1:
|
| 391 |
-
# Split content for two columns
|
| 392 |
-
left_placeholder = content_placeholders[0]
|
| 393 |
-
right_placeholder = content_placeholders[1]
|
| 394 |
-
|
| 395 |
-
mid_point = len(slide_content["content"]) // 2
|
| 396 |
-
left_content = slide_content["content"][:mid_point]
|
| 397 |
-
right_content = slide_content["content"][mid_point:]
|
| 398 |
-
|
| 399 |
-
left_placeholder.text = "\n".join(f"• {point}" for point in left_content)
|
| 400 |
-
right_placeholder.text = "\n".join(f"• {point}" for point in right_content)
|
| 401 |
-
else:
|
| 402 |
-
# Standard bullet points
|
| 403 |
-
content_placeholder.text = "\n".join(f"• {point}" for point in slide_content["content"])
|
| 404 |
-
else:
|
| 405 |
-
# Plain text
|
| 406 |
-
content_placeholder.text = slide_content["content"]
|
| 407 |
-
else:
|
| 408 |
-
content_placeholder.text = "Content to be added"
|
| 409 |
-
|
| 410 |
-
# Add notes if available
|
| 411 |
-
if "notes" in slide_content and slide_content["notes"]:
|
| 412 |
-
notes_slide = slide.notes_slide
|
| 413 |
-
notes_slide.notes_text_frame.text = slide_content["notes"]
|
| 414 |
-
|
| 415 |
-
# Save to BytesIO
|
| 416 |
-
output = BytesIO()
|
| 417 |
-
prs.save(output)
|
| 418 |
-
output.seek(0)
|
| 419 |
-
return output
|
| 420 |
-
|
| 421 |
def add_custom_template(template_file):
|
| 422 |
"""Add a custom template from an uploaded file"""
|
| 423 |
try:
|
|
@@ -427,7 +379,38 @@ def add_custom_template(template_file):
|
|
| 427 |
|
| 428 |
# Store in session state
|
| 429 |
st.session_state.custom_template = file_content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
|
| 431 |
-
return True, f"Custom template '{template_file.name}' uploaded successfully!"
|
| 432 |
except Exception as e:
|
| 433 |
return False, f"Error with template file: {str(e)}"
|
|
|
|
| 4 |
import json
|
| 5 |
import anthropic
|
| 6 |
import os
|
| 7 |
+
from multi_llm_provider import get_ai_manager
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
# Define available templates
|
| 10 |
TEMPLATES = {
|
|
|
|
| 66 |
}
|
| 67 |
}
|
| 68 |
|
| 69 |
+
def generate_storyboard(title, purpose, audience, model=None):
|
| 70 |
+
"""Generate a presentation storyboard using the specified AI model"""
|
| 71 |
+
|
| 72 |
+
# Get AI manager
|
| 73 |
+
ai_manager = get_ai_manager()
|
| 74 |
+
|
| 75 |
+
# Use default model if none specified
|
| 76 |
+
if not model:
|
| 77 |
+
model = st.session_state.get("default_model", "claude-3-sonnet-20250219")
|
| 78 |
|
| 79 |
+
# Check for web research data
|
| 80 |
+
web_research = ""
|
| 81 |
+
if hasattr(st.session_state, 'web_research') and st.session_state.web_research:
|
| 82 |
+
research_data = st.session_state.web_research
|
| 83 |
+
web_research = "Recent information from web research:\n\n"
|
| 84 |
+
|
| 85 |
+
for i, item in enumerate(research_data[:3]): # Limit to 3 results
|
| 86 |
+
title = item.get("title", "No title")
|
| 87 |
+
snippet = item.get("snippet", "No snippet")
|
| 88 |
+
url = item.get("url", "No URL")
|
| 89 |
+
|
| 90 |
+
web_research += f"{i+1}. {title}\n"
|
| 91 |
+
web_research += f" {snippet}\n"
|
| 92 |
+
web_research += f" Source: {url}\n\n"
|
| 93 |
+
|
| 94 |
+
# Create the prompt
|
| 95 |
prompt = f"""
|
| 96 |
You are an expert presentation designer with deep expertise in creating compelling PowerPoint presentations.
|
| 97 |
|
|
|
|
| 100 |
- Purpose: {purpose}
|
| 101 |
- Target Audience: {audience}
|
| 102 |
|
| 103 |
+
{web_research if web_research else ""}
|
| 104 |
+
|
| 105 |
For each slide, include:
|
| 106 |
1. Slide title
|
| 107 |
2. Slide purpose
|
|
|
|
| 125 |
]
|
| 126 |
"""
|
| 127 |
|
| 128 |
+
system_prompt = "You are an expert presentation designer specializing in creating logical, compelling PowerPoint storyboards. Always output valid JSON without explanations or extra text."
|
| 129 |
+
|
| 130 |
with st.sidebar:
|
| 131 |
progress_text = st.empty()
|
| 132 |
+
progress_text.write(f"Generating storyboard using {model}...")
|
| 133 |
|
| 134 |
try:
|
| 135 |
+
# Generate storyboard using the selected model
|
| 136 |
+
response_text = ai_manager.generate_text(
|
| 137 |
+
prompt=prompt,
|
| 138 |
+
model=model,
|
| 139 |
+
system_prompt=system_prompt,
|
| 140 |
+
temperature=st.session_state.get("ai_temperature", 0.7),
|
| 141 |
+
max_tokens=4000
|
|
|
|
| 142 |
)
|
|
|
|
| 143 |
|
| 144 |
# For debugging, show raw response
|
| 145 |
st.sidebar.write("Raw response: " + response_text[:200] + "...")
|
|
|
|
| 152 |
json_str = response_text[json_start:json_end]
|
| 153 |
parsed_json = json.loads(json_str)
|
| 154 |
progress_text.write(f"Generated storyboard with {len(parsed_json)} slides")
|
| 155 |
+
|
| 156 |
+
# Add AI model used to each slide for future reference
|
| 157 |
+
for slide in parsed_json:
|
| 158 |
+
if "ai_settings" not in slide:
|
| 159 |
+
slide["ai_settings"] = {}
|
| 160 |
+
slide["ai_settings"]["model"] = model
|
| 161 |
+
|
| 162 |
return parsed_json
|
| 163 |
else:
|
| 164 |
progress_text.error("JSON not found in response")
|
|
|
|
| 201 |
}
|
| 202 |
]
|
| 203 |
|
| 204 |
+
def generate_slide_content(slide_info, template_name, model=None):
|
| 205 |
+
"""Generate detailed content for a slide using the specified AI model"""
|
| 206 |
+
|
| 207 |
+
# Get AI manager
|
| 208 |
+
ai_manager = get_ai_manager()
|
| 209 |
+
|
| 210 |
+
# Use default model if none specified
|
| 211 |
+
if not model:
|
| 212 |
+
model = slide_info.get("ai_settings", {}).get("model", st.session_state.get("default_model", "claude-3-sonnet-20250219"))
|
| 213 |
|
| 214 |
prompt = f"""
|
| 215 |
Create detailed content for a PowerPoint slide with the following information:
|
|
|
|
| 238 |
Focus on clarity, impact, and alignment with the slide's purpose.
|
| 239 |
"""
|
| 240 |
|
| 241 |
+
system_prompt = "You are an expert presentation content creator. Always output valid JSON without explanations or extra text."
|
| 242 |
+
|
| 243 |
try:
|
| 244 |
+
# Generate content using the selected model
|
| 245 |
+
response_text = ai_manager.generate_text(
|
| 246 |
+
prompt=prompt,
|
| 247 |
+
model=model,
|
| 248 |
+
system_prompt=system_prompt,
|
| 249 |
+
temperature=st.session_state.get("ai_temperature", 0.5),
|
| 250 |
+
max_tokens=2000
|
|
|
|
| 251 |
)
|
|
|
|
| 252 |
|
| 253 |
# Extract the JSON from the response
|
| 254 |
json_start = response_text.find("{")
|
|
|
|
| 256 |
|
| 257 |
if json_start >= 0 and json_end > 0:
|
| 258 |
json_str = response_text[json_start:json_end]
|
| 259 |
+
content = json.loads(json_str)
|
| 260 |
+
|
| 261 |
+
# Store AI model used
|
| 262 |
+
if "ai_settings" not in content:
|
| 263 |
+
content["ai_settings"] = {}
|
| 264 |
+
content["ai_settings"]["model"] = model
|
| 265 |
+
|
| 266 |
+
return content
|
| 267 |
else:
|
| 268 |
# Fallback: create a basic slide
|
| 269 |
st.sidebar.error(f"JSON not found in response: {response_text}")
|
|
|
|
| 291 |
colors = template["colors"]
|
| 292 |
fonts = template["fonts"]
|
| 293 |
|
| 294 |
+
# Apply custom colors if available
|
| 295 |
+
if "custom_colors" in st.session_state and template_name in st.session_state.custom_colors:
|
| 296 |
+
custom_colors = st.session_state.custom_colors[template_name]
|
| 297 |
+
colors["primary"] = custom_colors.get("primary", colors["primary"])
|
| 298 |
+
colors["accent"] = custom_colors.get("accent", colors["accent"])
|
| 299 |
+
|
| 300 |
# Prepare content
|
| 301 |
title = slide.get('title', 'Untitled Slide')
|
| 302 |
|
|
|
|
| 370 |
|
| 371 |
return html
|
| 372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
def add_custom_template(template_file):
|
| 374 |
"""Add a custom template from an uploaded file"""
|
| 375 |
try:
|
|
|
|
| 379 |
|
| 380 |
# Store in session state
|
| 381 |
st.session_state.custom_template = file_content
|
| 382 |
+
st.session_state.custom_template_name = template_file.name
|
| 383 |
+
|
| 384 |
+
# Extract template information if possible
|
| 385 |
+
try:
|
| 386 |
+
# Try to extract color scheme from the template
|
| 387 |
+
background_color = "#FFFFFF" # Default
|
| 388 |
+
accent_color = "#0073E6" # Default
|
| 389 |
+
|
| 390 |
+
# Look for theme colors in the first slide if available
|
| 391 |
+
if len(prs.slides) > 0:
|
| 392 |
+
slide = prs.slides[0]
|
| 393 |
+
|
| 394 |
+
# Look for background color
|
| 395 |
+
for shape in slide.shapes:
|
| 396 |
+
if hasattr(shape, 'fill') and shape.fill.type != 0: # 0 is None
|
| 397 |
+
if hasattr(shape.fill, 'fore_color') and hasattr(shape.fill.fore_color, 'rgb'):
|
| 398 |
+
rgb = shape.fill.fore_color.rgb
|
| 399 |
+
if rgb:
|
| 400 |
+
background_color = f"#{rgb.r:02x}{rgb.g:02x}{rgb.b:02x}"
|
| 401 |
+
break
|
| 402 |
+
|
| 403 |
+
# Update custom template with extracted colors
|
| 404 |
+
TEMPLATES["custom"]["colors"]["primary"] = accent_color
|
| 405 |
+
TEMPLATES["custom"]["colors"]["secondary"] = background_color
|
| 406 |
+
|
| 407 |
+
# Get the number of slide layouts available
|
| 408 |
+
num_layouts = len(prs.slide_layouts)
|
| 409 |
+
st.session_state.custom_template_layouts = num_layouts
|
| 410 |
+
|
| 411 |
+
except Exception as extract_error:
|
| 412 |
+
st.warning(f"Could not extract all template details: {str(extract_error)}")
|
| 413 |
|
| 414 |
+
return True, f"Custom template '{template_file.name}' uploaded successfully with {len(prs.slides)} master layouts!"
|
| 415 |
except Exception as e:
|
| 416 |
return False, f"Error with template file: {str(e)}"
|