slide_duck / presentation_assistant.py
jeremierostan's picture
Update presentation_assistant.py
7d4095f verified
import os
from dotenv import load_dotenv
import google.generativeai as genai
import pdfplumber
import re
class PresentationAssistant:
def __init__(self, api_key=None, template_path="./template.md"):
"""Initialize the assistant with an API key."""
# Get API key from parameter
self.gemini_api_key = api_key
if not self.gemini_api_key:
raise ValueError("Missing Gemini API key. Please provide your API key.")
# Initialize Gemini client
genai.configure(api_key=self.gemini_api_key)
self.model = genai.GenerativeModel('gemini-1.5-pro')
self.template_path = template_path
def read_document(self, file_path):
"""Read content from a document file."""
try:
if file_path.lower().endswith('.pdf'):
return self.read_pdf(file_path)
with open(file_path, 'r', encoding='utf-8') as file:
return file.read()
except Exception as e:
print(f"Error reading file {file_path}: {str(e)}")
return ""
def read_pdf(self, file_path):
"""Extract text from PDF file."""
try:
text = ""
with pdfplumber.open(file_path) as pdf:
for page in pdf.pages:
text += page.extract_text() + "\n"
print(f"Successfully extracted {len(text)} characters from PDF")
return text
except Exception as e:
print(f"Error extracting text from PDF {file_path}: {str(e)}")
return ""
def analyze_documents(self, directory_path, query):
"""Analyze documents in the directory based on the query."""
relevant_content = []
try:
if os.path.isfile(directory_path):
relevant_content.extend(self.process_single_file(directory_path, query))
else:
for root, _, files in os.walk(directory_path):
for file in files:
if file.endswith(('.txt', '.md', '.pdf', '.doc', '.docx')):
file_path = os.path.join(root, file)
relevant_content.extend(self.process_single_file(file_path, query))
print(f"Found relevant content: {len(relevant_content)} sections")
return relevant_content
except Exception as e:
print(f"Error analyzing documents: {str(e)}")
return []
def process_single_file(self, file_path, query):
"""Process a single file to extract relevant information."""
relevant_content = []
content = self.read_document(file_path)
if content:
prompt = f"""
Based on this query: {query}
Extract relevant information from this content: {content}
Focus on key points and important details that would be suitable for a presentation.
Return only the relevant information in a concise format.
"""
try:
response = self.model.generate_content(prompt)
if response.text.strip():
relevant_content.append(response.text)
print(f"Successfully processed file: {file_path}")
except Exception as e:
print(f"Error processing file {file_path}: {str(e)}")
return relevant_content
def generate_presentation_content(self, relevant_content, num_slides):
"""Generate presentation content including title, subtitle, and bullet points."""
if not relevant_content:
raise ValueError("No content provided for presentation generation")
prompt = f"""
Create a presentation with {num_slides} slides based on this content: {relevant_content}
Important: For each bullet point, start with a relevant emoji that matches the content of that point.
Choose emojis that enhance understanding and engagement.
Follow this format exactly:
Title: [Emoji] [Overall presentation title]
(Example - Title: 🌟 Innovation in Education)
Slide 1: [Slide title]
- 🎯 [Key point 1 with relevant emoji]
- πŸ’‘ [Key point 2 with relevant emoji]
- πŸ”„ [Key point 3 with relevant emoji]
Slide 2: [Slide title]
- 🌱 [Key point 1 with relevant emoji]
- πŸš€ [Key point 2 with relevant emoji]
- 🀝 [Key point 3 with relevant emoji]
[Continue for remaining slides...]
Make sure to proof-read all text for possible errors such as missing hyphens.
(Example: 'solutionoriented' instead of 'solution-oriented')
Make each slide focused and concise. Use engaging titles and ensure each emoji meaningfully relates to its point.
For example:
- Use πŸ“š for learning/education points
- Use 🎯 for goals/objectives
- Use πŸ’‘ for ideas/insights
- Use 🀝 for collaboration/partnership
- Use 🌱 for growth/development
- Use πŸš€ for progress/advancement
"""
try:
response = self.model.generate_content(prompt)
print("Generated presentation content successfully")
return response.text
except Exception as e:
print(f"Error generating presentation content: {str(e)}")
return None
def parse_presentation_content(self, content):
"""Parse generated presentation content into slides."""
lines = content.split('\n')
title, subtitle = "", ""
slides = []
current_slide = None
for line in lines:
line = line.strip()
if not line:
continue
if line.startswith("Title:"):
title = line.replace("Title:", "").strip()
elif line.startswith("Subtitle:"):
subtitle = line.replace("Subtitle:", "").strip()
elif line.startswith("Slide"):
if current_slide:
slides.append(current_slide)
slide_title = line.split(":", 1)[1].strip() if ":" in line else line
current_slide = {"title": slide_title, "points": []}
elif line.startswith("-"):
if current_slide:
point_text = line.replace("-", "").strip()
# Keep emojis but remove other markdown characters
point_text = ''.join(c for c in point_text if not c in ['*', '_', '`', '#'])
current_slide["points"].append(point_text)
if current_slide:
slides.append(current_slide)
print(f"Parsed content - Title: {title}, Subtitle: {subtitle}, Slides: {len(slides)}")
return title, subtitle, slides
def write_presentation_file(self, title, subtitle, slides):
"""Write the presentation to example.md file."""
try:
# Using Slidev's mint theme with custom settings
header_section = """---
theme: mint
title: Slide Duck Presentation
titleTemplate: '%s'
fonts:
sans: 'Poppins'
weights: '400,500,700'
css: |
.slidev-layout {
background-color: white;
}
.pill {
@apply bg-green-50 text-gray-600 px-4 py-2 rounded-full inline-block mb-2 border border-green-200;
}
.text-bar {
@apply bg-green-50 text-gray-600 px-6 py-3 rounded-full my-4 border border-green-200;
}
.emoji-point {
@apply flex items-center gap-2;
}
.emoji {
@apply text-xl;
}
layout: center
---"""
# Title slide template
title_slide = f"""
# {title}
<div class="text-bar">
{subtitle}
</div>
---"""
# Initialize presentation with header and title slide
presentation_content = [header_section, title_slide]
# Add content slides
for slide in slides:
slide_content = f"""
# {slide['title']}
<div class="grid grid-cols-1 gap-4">
"""
for point in slide['points']:
slide_content += f' <div class="pill">{point}</div>\n'
slide_content += """
</div>
---"""
presentation_content.append(slide_content)
# Write to file
with open('example.md', 'w', encoding='utf-8') as f:
f.write('\n'.join(presentation_content))
print(f"Successfully wrote presentation with {len(slides)} slides to example.md")
except Exception as e:
print(f"Error writing presentation file: {str(e)}")
raise
def create_presentation(self, directory_path, query, num_slides):
"""Create the full presentation."""
try:
# Analyze documents and extract relevant content
relevant_content = self.analyze_documents(directory_path, query)
if not relevant_content:
raise ValueError("No relevant content found in documents")
# Generate presentation content
presentation_content = self.generate_presentation_content(relevant_content, num_slides)
if not presentation_content:
raise ValueError("Failed to generate presentation content")
# Parse the generated content
title, subtitle, slides = self.parse_presentation_content(presentation_content)
# Write the presentation file
self.write_presentation_file(title, subtitle, slides)
return True
except Exception as e:
print(f"Error creating presentation: {str(e)}")
raise
def export_to_pptx(self, title, subtitle, slides, output_path="presentation.pptx"):
"""Export the presentation to PowerPoint format."""
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
from pptx.dml.color import RGBColor
# Create presentation
prs = Presentation()
# Set slide dimensions to 16:9
prs.slide_width = Inches(16)
prs.slide_height = Inches(9)
# Design constants
TITLE_FONT_SIZE = Pt(44)
SUBTITLE_FONT_SIZE = Pt(32)
BODY_FONT_SIZE = Pt(24)
ISP_BLUE = RGBColor(0, 91, 172) # #005BAC
ISP_LIGHT_BLUE = RGBColor(176, 224, 230) # #B0E0E6
# Title slide
title_slide_layout = prs.slide_layouts[0] # Title slide layout
slide = prs.slides.add_slide(title_slide_layout)
# Set title
title_shape = slide.shapes.title
title_shape.text = title
title_shape.text_frame.paragraphs[0].font.size = TITLE_FONT_SIZE
title_shape.text_frame.paragraphs[0].font.color.rgb = ISP_BLUE
# Set subtitle
subtitle_shape = slide.placeholders[1]
subtitle_shape.text = subtitle
subtitle_shape.text_frame.paragraphs[0].font.size = SUBTITLE_FONT_SIZE
# Content slides
for slide_content in slides:
content_slide_layout = prs.slide_layouts[1] # Content slide layout
slide = prs.slides.add_slide(content_slide_layout)
# Set slide title
title_shape = slide.shapes.title
title_shape.text = slide_content['title']
title_shape.text_frame.paragraphs[0].font.size = TITLE_FONT_SIZE
title_shape.text_frame.paragraphs[0].font.color.rgb = ISP_BLUE
# Add points
body_shape = slide.placeholders[1]
text_frame = body_shape.text_frame
text_frame.clear() # Clear default text
for point in slide_content['points']:
p = text_frame.add_paragraph()
p.text = point
p.font.size = BODY_FONT_SIZE
p.level = 0 # Top level bullet point
# Style the bullet points
p.font.color.rgb = RGBColor(85, 85, 85) # Dark gray for better readability
# Add shape for visual interest
pill_left = Inches(1)
pill_top = Inches(6)
pill_width = Inches(14)
pill_height = Inches(0.5)
pill = slide.shapes.add_shape(
1, pill_left, pill_top, pill_width, pill_height
)
pill.fill.solid()
pill.fill.fore_color.rgb = ISP_LIGHT_BLUE
pill.line.color.rgb = ISP_BLUE
# Save presentation
prs.save(output_path)
return output_path