Spaces:
Running
Running
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 |