Spaces:
Sleeping
Sleeping
update code with QA
Browse files- pages/1_π_Text_to_PPT.py +591 -18
pages/1_π_Text_to_PPT.py
CHANGED
@@ -1,3 +1,441 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
import google.generativeai as genai
|
3 |
from pptx import Presentation
|
@@ -13,6 +451,9 @@ import os
|
|
13 |
|
14 |
# Load the API key securely from environment variable
|
15 |
api_key = os.getenv("GOOGLE_API_KEY")
|
|
|
|
|
|
|
16 |
genai.configure(api_key=api_key)
|
17 |
|
18 |
# Default theme configurations
|
@@ -86,7 +527,7 @@ def extract_theme_from_pptx(uploaded_file):
|
|
86 |
for shape in slide_master.shapes:
|
87 |
if shape.has_text_frame and shape.text.strip():
|
88 |
try:
|
89 |
-
if shape.name.lower()
|
90 |
theme["title_color"] = shape.text_frame.paragraphs[0].font.color.rgb
|
91 |
theme["title_font"] = shape.text_frame.paragraphs[0].font.name
|
92 |
else:
|
@@ -148,16 +589,26 @@ def generate_slide_content(topic, slide_count):
|
|
148 |
- Challenges
|
149 |
- Future Trends
|
150 |
- Conclusion
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
-
|
159 |
-
|
160 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
|
162 |
Begin the content generation now.
|
163 |
"""
|
@@ -168,8 +619,15 @@ def generate_slide_content(topic, slide_count):
|
|
168 |
def parse_slide_content(slide_text):
|
169 |
slides = []
|
170 |
current_slide = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
172 |
-
|
|
|
173 |
line = line.strip()
|
174 |
if not line:
|
175 |
continue
|
@@ -203,9 +661,104 @@ def parse_slide_content(slide_text):
|
|
203 |
if current_slide:
|
204 |
slides.append(current_slide)
|
205 |
|
206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
|
208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
"""Create PowerPoint using the uploaded template"""
|
210 |
# Use the template if one was uploaded
|
211 |
if "template_path" in theme and os.path.exists(theme["template_path"]):
|
@@ -232,6 +785,7 @@ def create_detailed_pptx(slides_data, theme, branding_options=None):
|
|
232 |
|
233 |
default_layout_idx = available_layouts.get('title-content', 0)
|
234 |
|
|
|
235 |
for slide_info in slides_data:
|
236 |
layout = slide_info.get('layout', 'title-content').lower()
|
237 |
layout_idx = available_layouts.get(layout, default_layout_idx)
|
@@ -316,6 +870,10 @@ def create_detailed_pptx(slides_data, theme, branding_options=None):
|
|
316 |
notes_slide = slide.notes_slide
|
317 |
notes_slide.notes_text_frame.text = slide_info['notes']
|
318 |
|
|
|
|
|
|
|
|
|
319 |
pptx_io = io.BytesIO()
|
320 |
prs.save(pptx_io)
|
321 |
pptx_io.seek(0)
|
@@ -352,6 +910,7 @@ def main():
|
|
352 |
["Predefined Theme", "Custom Theme", "Example-Based Theme"])
|
353 |
|
354 |
theme = DEFAULT_THEMES["Professional Blue"] # Default theme
|
|
|
355 |
|
356 |
if theme_option == "Predefined Theme":
|
357 |
theme_name = st.selectbox("Select Theme:", list(DEFAULT_THEMES.keys()))
|
@@ -395,9 +954,13 @@ def main():
|
|
395 |
st.info("Please upload a PowerPoint file to extract its theme")
|
396 |
|
397 |
with col2:
|
398 |
-
|
|
|
|
|
|
|
|
|
399 |
|
400 |
-
|
401 |
if not topic:
|
402 |
st.warning("Please enter a topic first!")
|
403 |
elif theme_option == "Example-Based Theme" and not uploaded_file:
|
@@ -406,7 +969,7 @@ def main():
|
|
406 |
with st.spinner(f"Creating {slide_count}-slide presentation about '{topic}'..."):
|
407 |
try:
|
408 |
slide_text = generate_slide_content(topic, slide_count)
|
409 |
-
slides_data = parse_slide_content(slide_text)
|
410 |
|
411 |
# Show slide overview with detailed content
|
412 |
with st.expander("Slide Overview (Detailed)"):
|
@@ -418,8 +981,17 @@ def main():
|
|
418 |
if slide.get('notes'):
|
419 |
st.markdown(f"**Notes:** {slide['notes']}")
|
420 |
st.markdown("---")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
421 |
|
422 |
-
pptx_file = create_detailed_pptx(slides_data, theme)
|
423 |
|
424 |
st.success("Presentation generated successfully!")
|
425 |
|
@@ -432,5 +1004,6 @@ def main():
|
|
432 |
|
433 |
except Exception as e:
|
434 |
st.error(f"An error occurred: {str(e)}")
|
|
|
435 |
if __name__ == "__main__":
|
436 |
main()
|
|
|
1 |
+
# import streamlit as st
|
2 |
+
# import google.generativeai as genai
|
3 |
+
# from pptx import Presentation
|
4 |
+
# from pptx.util import Inches, Pt
|
5 |
+
# from pptx.dml.color import RGBColor
|
6 |
+
# from pptx.enum.text import PP_ALIGN, PP_PARAGRAPH_ALIGNMENT
|
7 |
+
# from pptx.enum.text import MSO_AUTO_SIZE
|
8 |
+
# from pptx.oxml.xmlchemy import OxmlElement
|
9 |
+
# import io
|
10 |
+
# import re
|
11 |
+
# import tempfile
|
12 |
+
# import os
|
13 |
+
|
14 |
+
# # Load the API key securely from environment variable
|
15 |
+
# api_key = os.getenv("GOOGLE_API_KEY")
|
16 |
+
# genai.configure(api_key=api_key)
|
17 |
+
|
18 |
+
# # Default theme configurations
|
19 |
+
# DEFAULT_THEMES = {
|
20 |
+
# "Professional Blue": {
|
21 |
+
# "background": RGBColor(12, 35, 64),
|
22 |
+
# "title_color": RGBColor(255, 255, 255),
|
23 |
+
# "text_color": RGBColor(200, 200, 200),
|
24 |
+
# "accent": RGBColor(0, 112, 192),
|
25 |
+
# "title_font": "Calibri",
|
26 |
+
# "text_font": "Calibri"
|
27 |
+
# },
|
28 |
+
# "Modern Green": {
|
29 |
+
# "background": RGBColor(22, 82, 66),
|
30 |
+
# "title_color": RGBColor(255, 255, 255),
|
31 |
+
# "text_color": RGBColor(220, 220, 220),
|
32 |
+
# "accent": RGBColor(76, 175, 80),
|
33 |
+
# "title_font": "Arial",
|
34 |
+
# "text_font": "Arial"
|
35 |
+
# },
|
36 |
+
# "Light Corporate": {
|
37 |
+
# "background": RGBColor(255, 255, 255),
|
38 |
+
# "title_color": RGBColor(13, 71, 161),
|
39 |
+
# "text_color": RGBColor(33, 33, 33),
|
40 |
+
# "accent": RGBColor(25, 118, 210),
|
41 |
+
# "title_font": "Segoe UI",
|
42 |
+
# "text_font": "Segoe UI"
|
43 |
+
# },
|
44 |
+
# "Dark Tech": {
|
45 |
+
# "background": RGBColor(33, 33, 33),
|
46 |
+
# "title_color": RGBColor(0, 200, 255),
|
47 |
+
# "text_color": RGBColor(200, 200, 200),
|
48 |
+
# "accent": RGBColor(0, 150, 255),
|
49 |
+
# "title_font": "Consolas",
|
50 |
+
# "text_font": "Consolas"
|
51 |
+
# }
|
52 |
+
# }
|
53 |
+
|
54 |
+
# def hex_to_rgb(hex_color):
|
55 |
+
# """Convert hex color to RGBColor"""
|
56 |
+
# hex_color = hex_color.lstrip('#')
|
57 |
+
# return RGBColor(*tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)))
|
58 |
+
|
59 |
+
# def extract_theme_from_pptx(uploaded_file):
|
60 |
+
# """Extract theme colors and fonts from an uploaded PowerPoint file"""
|
61 |
+
# with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
|
62 |
+
# tmp_file.write(uploaded_file.read())
|
63 |
+
# tmp_file_path = tmp_file.name
|
64 |
+
|
65 |
+
# prs = Presentation(tmp_file_path)
|
66 |
+
# theme = {
|
67 |
+
# "background": RGBColor(255, 255, 255), # Default white if not found
|
68 |
+
# "title_color": RGBColor(0, 0, 0), # Default black
|
69 |
+
# "text_color": RGBColor(0, 0, 0), # Default black
|
70 |
+
# "accent": RGBColor(79, 129, 189), # Default blue
|
71 |
+
# "title_font": "Calibri",
|
72 |
+
# "text_font": "Calibri",
|
73 |
+
# "template_path": tmp_file_path # Store the template path for later use
|
74 |
+
# }
|
75 |
+
|
76 |
+
# try:
|
77 |
+
# # Get colors from master slide
|
78 |
+
# slide_master = prs.slide_master
|
79 |
+
|
80 |
+
# # Handle background color
|
81 |
+
# if hasattr(slide_master.background, 'fill'):
|
82 |
+
# if slide_master.background.fill.type == 1: # Solid fill
|
83 |
+
# theme["background"] = slide_master.background.fill.fore_color.rgb
|
84 |
+
|
85 |
+
# # Try to get title and text colors from placeholders
|
86 |
+
# for shape in slide_master.shapes:
|
87 |
+
# if shape.has_text_frame and shape.text.strip():
|
88 |
+
# try:
|
89 |
+
# if shape.name.lower() == 'title':
|
90 |
+
# theme["title_color"] = shape.text_frame.paragraphs[0].font.color.rgb
|
91 |
+
# theme["title_font"] = shape.text_frame.paragraphs[0].font.name
|
92 |
+
# else:
|
93 |
+
# theme["text_color"] = shape.text_frame.paragraphs[0].font.color.rgb
|
94 |
+
# theme["text_font"] = shape.text_frame.paragraphs[0].font.name
|
95 |
+
# except:
|
96 |
+
# continue
|
97 |
+
|
98 |
+
# # Try to get accent color from first shape with fill
|
99 |
+
# for shape in slide_master.shapes:
|
100 |
+
# if hasattr(shape, 'fill'):
|
101 |
+
# try:
|
102 |
+
# if shape.fill.type == 1: # Solid fill
|
103 |
+
# theme["accent"] = shape.fill.fore_color.rgb
|
104 |
+
# break
|
105 |
+
# except:
|
106 |
+
# continue
|
107 |
+
|
108 |
+
# except Exception as e:
|
109 |
+
# st.warning(f"Couldn't fully extract theme: {str(e)}. Using default colors where needed.")
|
110 |
+
|
111 |
+
# return theme
|
112 |
+
|
113 |
+
# def generate_slide_content(topic, slide_count):
|
114 |
+
# model = genai.GenerativeModel('gemini-2.0-flash')
|
115 |
+
# prompt = f"""Create a comprehensive presentation on '{topic}' with exactly {slide_count} slides.
|
116 |
+
# For each slide, provide:
|
117 |
+
# 1. A clear title in [Title:] format
|
118 |
+
# 2. 3-5 detailed bullet points in [Content:] format (each point should be 2-3 lines/40-60 words)
|
119 |
+
# 3. Optional speaker notes in [Notes:] format
|
120 |
+
# 4. Layout suggestion in [Layout:] format (title-only, title-content, two-column, section-header)
|
121 |
+
|
122 |
+
# Structure your response like this:
|
123 |
+
# [Title:] Slide Title
|
124 |
+
# [Layout:] title-content
|
125 |
+
# [Content:]
|
126 |
+
# - Main Point 1: Detailed explanation spanning 1-2 lines with supporting information that provides context and value to the audience. This makes each point substantial.
|
127 |
+
# - Main Point 2: Another complete thought with sufficient detail to stand alone as a mini-paragraph, giving the audience concrete information they can use.
|
128 |
+
# - Main Point 3: Final point with enough depth to be meaningful, typically consisting of 2-3 sentences that develop a complete idea.
|
129 |
+
# [Notes:] Additional notes here
|
130 |
+
|
131 |
+
# Important guidelines:
|
132 |
+
# - Do not give only placeholders or labels. Write full, rich content for each bullet point.
|
133 |
+
# - Apply the same for the Questionnaire slide too, don't just keep placeholders, generate 10 questions.
|
134 |
+
# - Avoid general outlines β generate fully written content as if it's going directly into a slide.
|
135 |
+
# - Each bullet point should be 2-3 lines (30-50 words)
|
136 |
+
# - Provide complete thoughts with supporting details
|
137 |
+
# - Maintain parallel structure across points
|
138 |
+
# - Avoid single-sentence bullet points
|
139 |
+
# - Focus on substance over brevity
|
140 |
+
|
141 |
+
# Include these sections (adjust based on requested slide count):
|
142 |
+
# - Title slide
|
143 |
+
# - Introduction/Overview
|
144 |
+
# - Key Concepts
|
145 |
+
# - Detailed Analysis
|
146 |
+
# - Case Studies/Examples
|
147 |
+
# - Applications
|
148 |
+
# - Challenges
|
149 |
+
# - Future Trends
|
150 |
+
# - Conclusion
|
151 |
+
# - Questionnaire
|
152 |
+
|
153 |
+
# After generating the content for the above presentation sections,titled [Title:], generate a Questionnaire Slide containing:
|
154 |
+
# - 10 multiple-choice questions testing the userβs understanding of the presentation
|
155 |
+
# - Each question should have 4 options (A, B, C, D)
|
156 |
+
# - Clearly indicate the correct answer after each question using the format: [Correct Answer: C]
|
157 |
+
# - Make the questions relevant and cover all key areas: introduction, concepts, analysis, case studies, applications, challenges, and future trends.
|
158 |
+
# - Make sure the questions test the user's comprehension of the previous slides' content and are well-distributed across different topics in the presentation.
|
159 |
+
|
160 |
+
# Please ensure the presentation content is presented first, followed by a clear separator, and then the questionnaire.
|
161 |
+
|
162 |
+
# Begin the content generation now.
|
163 |
+
# """
|
164 |
+
|
165 |
+
# response = model.generate_content(prompt)
|
166 |
+
# return response.text
|
167 |
+
|
168 |
+
# def parse_slide_content(slide_text):
|
169 |
+
# slides = []
|
170 |
+
# current_slide = {}
|
171 |
+
|
172 |
+
# for line in slide_text.split('\n'):
|
173 |
+
# line = line.strip()
|
174 |
+
# if not line:
|
175 |
+
# continue
|
176 |
+
|
177 |
+
# if line.startswith('[Title:]'):
|
178 |
+
# if current_slide:
|
179 |
+
# slides.append(current_slide)
|
180 |
+
# current_slide = {
|
181 |
+
# 'title': line.replace('[Title:]', '').strip(),
|
182 |
+
# 'content': [],
|
183 |
+
# 'notes': '',
|
184 |
+
# 'layout': 'title-content'
|
185 |
+
# }
|
186 |
+
# elif line.startswith('[Content:]'):
|
187 |
+
# content = line.replace('[Content:]', '').strip()
|
188 |
+
# if content:
|
189 |
+
# current_slide['content'].append(content)
|
190 |
+
# elif line.startswith('[Notes:]'):
|
191 |
+
# current_slide['notes'] = line.replace('[Notes:]', '').strip()
|
192 |
+
# elif line.startswith('[Layout:]'):
|
193 |
+
# layout = line.replace('[Layout:]', '').strip().lower()
|
194 |
+
# valid_layouts = ['title-only', 'title-content', 'two-column', 'section-header']
|
195 |
+
# current_slide['layout'] = layout if layout in valid_layouts else 'title-content'
|
196 |
+
# elif current_slide.get('content') is not None and line.startswith('-'):
|
197 |
+
# # Simplify bullet points - remove any explanations after colons
|
198 |
+
# point = line[1:].strip()
|
199 |
+
# if ':' in point:
|
200 |
+
# point = point.split(':')[0].strip()
|
201 |
+
# current_slide['content'].append(point)
|
202 |
+
|
203 |
+
# if current_slide:
|
204 |
+
# slides.append(current_slide)
|
205 |
+
|
206 |
+
# return slides
|
207 |
+
|
208 |
+
# def create_detailed_pptx(slides_data, theme, branding_options=None):
|
209 |
+
# """Create PowerPoint using the uploaded template"""
|
210 |
+
# # Use the template if one was uploaded
|
211 |
+
# if "template_path" in theme and os.path.exists(theme["template_path"]):
|
212 |
+
# prs = Presentation(theme["template_path"])
|
213 |
+
# else:
|
214 |
+
# prs = Presentation()
|
215 |
+
|
216 |
+
# # Set widescreen layout (16:9 aspect ratio)
|
217 |
+
# prs.slide_width = Inches(13.33)
|
218 |
+
# prs.slide_height = Inches(7.5)
|
219 |
+
|
220 |
+
# # Layout mapping
|
221 |
+
# layout_indices = {
|
222 |
+
# 'title-only': 0,
|
223 |
+
# 'title-content': 1,
|
224 |
+
# 'section-header': 2,
|
225 |
+
# 'two-column': 3
|
226 |
+
# }
|
227 |
+
|
228 |
+
# available_layouts = {}
|
229 |
+
# for name, idx in layout_indices.items():
|
230 |
+
# if idx < len(prs.slide_layouts):
|
231 |
+
# available_layouts[name] = idx
|
232 |
+
|
233 |
+
# default_layout_idx = available_layouts.get('title-content', 0)
|
234 |
+
|
235 |
+
# for slide_info in slides_data:
|
236 |
+
# layout = slide_info.get('layout', 'title-content').lower()
|
237 |
+
# layout_idx = available_layouts.get(layout, default_layout_idx)
|
238 |
+
|
239 |
+
# try:
|
240 |
+
# slide = prs.slides.add_slide(prs.slide_layouts[layout_idx])
|
241 |
+
# except IndexError:
|
242 |
+
# slide = prs.slides.add_slide(prs.slide_layouts[default_layout_idx])
|
243 |
+
|
244 |
+
# # Set title
|
245 |
+
# title = slide.shapes.title
|
246 |
+
# title.text = slide_info['title']
|
247 |
+
# title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
|
248 |
+
|
249 |
+
# # Only apply custom formatting if not using a template
|
250 |
+
# if "template_path" not in theme:
|
251 |
+
# # Apply background
|
252 |
+
# background = slide.background
|
253 |
+
# fill = background.fill
|
254 |
+
# fill.solid()
|
255 |
+
# fill.fore_color.rgb = theme["background"]
|
256 |
+
|
257 |
+
# # Format title
|
258 |
+
# title.text_frame.paragraphs[0].font.color.rgb = theme["title_color"]
|
259 |
+
# title.text_frame.paragraphs[0].font.size = Pt(36)
|
260 |
+
# title.text_frame.paragraphs[0].font.bold = True
|
261 |
+
# if "title_font" in theme:
|
262 |
+
# title.text_frame.paragraphs[0].font.name = theme["title_font"]
|
263 |
+
|
264 |
+
# # Add logo if provided
|
265 |
+
# if branding_options and branding_options.get('logo_path'):
|
266 |
+
# add_logo_to_slide(slide, branding_options['logo_path'],
|
267 |
+
# branding_options.get('logo_position', 'top-right'))
|
268 |
+
|
269 |
+
# # Set content based on layout
|
270 |
+
# if layout_idx == 3: # Two column layout
|
271 |
+
# content = slide_info.get('content', [])
|
272 |
+
# mid_point = len(content) // 2
|
273 |
+
# left_content = content[:mid_point]
|
274 |
+
# right_content = content[mid_point:]
|
275 |
+
|
276 |
+
# left_body = slide.placeholders[1]
|
277 |
+
# left_tf = left_body.text_frame
|
278 |
+
# left_tf.clear()
|
279 |
+
|
280 |
+
# right_body = slide.placeholders[2]
|
281 |
+
# right_tf = right_body.text_frame
|
282 |
+
# right_tf.clear()
|
283 |
+
|
284 |
+
# for content_part, tf in [(left_content, left_tf), (right_content, right_tf)]:
|
285 |
+
# for point in content_part:
|
286 |
+
# p = tf.add_paragraph()
|
287 |
+
# point_text = point.replace('- ', '').strip()
|
288 |
+
# p.text = point_text
|
289 |
+
# p.level = 0
|
290 |
+
# p.alignment = PP_ALIGN.JUSTIFY
|
291 |
+
# if "template_path" not in theme: # Only apply custom formatting if no template
|
292 |
+
# p.font.color.rgb = theme["text_color"]
|
293 |
+
# p.font.size = Pt(18)
|
294 |
+
# if "text_font" in theme:
|
295 |
+
# p.font.name = theme["text_font"]
|
296 |
+
|
297 |
+
# elif layout_idx != 0: # Not title-only
|
298 |
+
# body = slide.placeholders[1]
|
299 |
+
# tf = body.text_frame
|
300 |
+
# tf.clear()
|
301 |
+
|
302 |
+
# for point in slide_info.get('content', []):
|
303 |
+
# p = tf.add_paragraph()
|
304 |
+
# point_text = point.replace('- ', '').strip()
|
305 |
+
# p.text = point_text
|
306 |
+
# p.level = 0
|
307 |
+
# p.alignment = PP_ALIGN.JUSTIFY
|
308 |
+
# if "template_path" not in theme: # Only apply custom formatting if no template
|
309 |
+
# p.font.color.rgb = theme["text_color"]
|
310 |
+
# p.font.size = Pt(18)
|
311 |
+
# if "text_font" in theme:
|
312 |
+
# p.font.name = theme["text_font"]
|
313 |
+
|
314 |
+
# # Add notes if available
|
315 |
+
# if slide_info.get('notes'):
|
316 |
+
# notes_slide = slide.notes_slide
|
317 |
+
# notes_slide.notes_text_frame.text = slide_info['notes']
|
318 |
+
|
319 |
+
# pptx_io = io.BytesIO()
|
320 |
+
# prs.save(pptx_io)
|
321 |
+
# pptx_io.seek(0)
|
322 |
+
|
323 |
+
# # Clean up temporary template file
|
324 |
+
# if "template_path" in theme and os.path.exists(theme["template_path"]):
|
325 |
+
# os.unlink(theme["template_path"])
|
326 |
+
|
327 |
+
# return pptx_io
|
328 |
+
|
329 |
+
# def main():
|
330 |
+
# st.set_page_config(page_title="Advanced PPTX Generator", layout="wide")
|
331 |
+
|
332 |
+
# st.title("Advanced PowerPoint Generator")
|
333 |
+
# st.markdown("Create professional presentations with AI")
|
334 |
+
|
335 |
+
# # Initialize session state for custom themes if not exists
|
336 |
+
# if 'custom_themes' not in st.session_state:
|
337 |
+
# st.session_state.custom_themes = {}
|
338 |
+
|
339 |
+
# # Combine default and custom themes
|
340 |
+
# ALL_THEMES = {**DEFAULT_THEMES, **st.session_state.custom_themes}
|
341 |
+
# col1, col2 = st.columns([3, 1])
|
342 |
+
|
343 |
+
# with col1:
|
344 |
+
# topic = st.text_input("Presentation Topic:",
|
345 |
+
# placeholder="Enter your topic (e.g., 'AI in Healthcare')")
|
346 |
+
|
347 |
+
# with st.expander("Advanced Options"):
|
348 |
+
# slide_count = st.slider("Number of Slides:", 5, 20, 10)
|
349 |
+
|
350 |
+
# # Theme selection with example-based option
|
351 |
+
# theme_option = st.radio("Theme Selection Method:",
|
352 |
+
# ["Predefined Theme", "Custom Theme", "Example-Based Theme"])
|
353 |
+
|
354 |
+
# theme = DEFAULT_THEMES["Professional Blue"] # Default theme
|
355 |
+
|
356 |
+
# if theme_option == "Predefined Theme":
|
357 |
+
# theme_name = st.selectbox("Select Theme:", list(DEFAULT_THEMES.keys()))
|
358 |
+
# theme = DEFAULT_THEMES[theme_name]
|
359 |
+
# elif theme_option == "Custom Theme":
|
360 |
+
# theme_name = st.selectbox("Select Custom Theme:",
|
361 |
+
# ["Create New..."] + list(st.session_state.custom_themes.keys()))
|
362 |
+
|
363 |
+
# if theme_name == "Create New...":
|
364 |
+
# with st.form("custom_theme_form"):
|
365 |
+
# new_theme_name = st.text_input("Theme Name")
|
366 |
+
# bg_color = st.color_picker("Background Color", "#0C2340")
|
367 |
+
# title_color = st.color_picker("Title Color", "#FFFFFF")
|
368 |
+
# text_color = st.color_picker("Text Color", "#C8C8C8")
|
369 |
+
# accent_color = st.color_picker("Accent Color", "#0070C0")
|
370 |
+
# title_font = st.text_input("Title Font", "Calibri")
|
371 |
+
# text_font = st.text_input("Text Font", "Calibri")
|
372 |
+
|
373 |
+
# if st.form_submit_button("Save Custom Theme"):
|
374 |
+
# if new_theme_name:
|
375 |
+
# st.session_state.custom_themes[new_theme_name] = {
|
376 |
+
# "background": hex_to_rgb(bg_color),
|
377 |
+
# "title_color": hex_to_rgb(title_color),
|
378 |
+
# "text_color": hex_to_rgb(text_color),
|
379 |
+
# "accent": hex_to_rgb(accent_color),
|
380 |
+
# "title_font": title_font,
|
381 |
+
# "text_font": text_font
|
382 |
+
# }
|
383 |
+
# st.success(f"Theme '{new_theme_name}' saved successfully!")
|
384 |
+
# else:
|
385 |
+
# st.warning("Please enter a theme name")
|
386 |
+
# elif theme_name in st.session_state.custom_themes:
|
387 |
+
# theme = st.session_state.custom_themes[theme_name]
|
388 |
+
# else: # Example-Based Theme
|
389 |
+
# uploaded_file = st.file_uploader("Upload PowerPoint Template", type=["pptx"])
|
390 |
+
# if uploaded_file:
|
391 |
+
# with st.spinner("Extracting theme from template..."):
|
392 |
+
# theme = extract_theme_from_pptx(uploaded_file)
|
393 |
+
# st.success("Theme extracted from template!")
|
394 |
+
# else:
|
395 |
+
# st.info("Please upload a PowerPoint file to extract its theme")
|
396 |
+
|
397 |
+
# with col2:
|
398 |
+
# # st.image("https://huggingface.co/spaces/YashMK89/PPTX_generativeai/resolve/main/SVNIT_IS_DS_Projects.png", width=150)
|
399 |
+
|
400 |
+
# if st.button("Generate Presentation", type="primary"):
|
401 |
+
# if not topic:
|
402 |
+
# st.warning("Please enter a topic first!")
|
403 |
+
# elif theme_option == "Example-Based Theme" and not uploaded_file:
|
404 |
+
# st.warning("Please upload a PowerPoint template file first")
|
405 |
+
# else:
|
406 |
+
# with st.spinner(f"Creating {slide_count}-slide presentation about '{topic}'..."):
|
407 |
+
# try:
|
408 |
+
# slide_text = generate_slide_content(topic, slide_count)
|
409 |
+
# slides_data = parse_slide_content(slide_text)
|
410 |
+
|
411 |
+
# # Show slide overview with detailed content
|
412 |
+
# with st.expander("Slide Overview (Detailed)"):
|
413 |
+
# for i, slide in enumerate(slides_data, 1):
|
414 |
+
# st.subheader(f"Slide {i}: {slide['title']}")
|
415 |
+
# st.markdown("**Content:**")
|
416 |
+
# for point in slide.get('content', []):
|
417 |
+
# st.markdown(f"- {point}")
|
418 |
+
# if slide.get('notes'):
|
419 |
+
# st.markdown(f"**Notes:** {slide['notes']}")
|
420 |
+
# st.markdown("---")
|
421 |
+
|
422 |
+
# pptx_file = create_detailed_pptx(slides_data, theme)
|
423 |
+
|
424 |
+
# st.success("Presentation generated successfully!")
|
425 |
+
|
426 |
+
# st.download_button(
|
427 |
+
# label="Download PowerPoint",
|
428 |
+
# data=pptx_file,
|
429 |
+
# file_name=f"{topic.replace(' ', '_')}_presentation.pptx",
|
430 |
+
# mime="application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
431 |
+
# )
|
432 |
+
|
433 |
+
# except Exception as e:
|
434 |
+
# st.error(f"An error occurred: {str(e)}")
|
435 |
+
# if __name__ == "__main__":
|
436 |
+
# main()
|
437 |
+
|
438 |
+
|
439 |
import streamlit as st
|
440 |
import google.generativeai as genai
|
441 |
from pptx import Presentation
|
|
|
451 |
|
452 |
# Load the API key securely from environment variable
|
453 |
api_key = os.getenv("GOOGLE_API_KEY")
|
454 |
+
if not api_key:
|
455 |
+
st.error("GOOGLE_API_KEY environment variable not set!")
|
456 |
+
st.stop()
|
457 |
genai.configure(api_key=api_key)
|
458 |
|
459 |
# Default theme configurations
|
|
|
527 |
for shape in slide_master.shapes:
|
528 |
if shape.has_text_frame and shape.text.strip():
|
529 |
try:
|
530 |
+
if 'title' in shape.name.lower():
|
531 |
theme["title_color"] = shape.text_frame.paragraphs[0].font.color.rgb
|
532 |
theme["title_font"] = shape.text_frame.paragraphs[0].font.name
|
533 |
else:
|
|
|
589 |
- Challenges
|
590 |
- Future Trends
|
591 |
- Conclusion
|
592 |
+
|
593 |
+
After generating the content for the above presentation sections, add a clear separator:
|
594 |
+
--- QUESTIONNAIRE ---
|
595 |
+
|
596 |
+
Then generate 10 multiple-choice questions testing the user's understanding of the presentation:
|
597 |
+
Format each question like:
|
598 |
+
[Question: Your question text here?]
|
599 |
+
- [A] Option A
|
600 |
+
- [B] Option B
|
601 |
+
- [C] Option C
|
602 |
+
- [D] Option D
|
603 |
+
[Correct: C]
|
604 |
+
|
605 |
+
Ensure:
|
606 |
+
- Questions cover all key areas: introduction, concepts, analysis, case studies, applications, challenges, and future trends
|
607 |
+
- Each question has exactly 4 options
|
608 |
+
- Clearly indicate the correct answer
|
609 |
+
- Questions test comprehension of the presentation content
|
610 |
+
|
611 |
+
Please ensure the presentation content is presented first, followed by the separator, and then the questionnaire.
|
612 |
|
613 |
Begin the content generation now.
|
614 |
"""
|
|
|
619 |
def parse_slide_content(slide_text):
|
620 |
slides = []
|
621 |
current_slide = {}
|
622 |
+
questionnaire = []
|
623 |
+
|
624 |
+
# Split into main content and questionnaire
|
625 |
+
parts = slide_text.split('--- QUESTIONNAIRE ---')
|
626 |
+
main_content = parts[0].strip()
|
627 |
+
questionnaire_content = parts[1].strip() if len(parts) > 1 else ""
|
628 |
|
629 |
+
# Parse main slides
|
630 |
+
for line in main_content.split('\n'):
|
631 |
line = line.strip()
|
632 |
if not line:
|
633 |
continue
|
|
|
661 |
if current_slide:
|
662 |
slides.append(current_slide)
|
663 |
|
664 |
+
# Parse questionnaire if exists
|
665 |
+
if questionnaire_content:
|
666 |
+
current_question = {}
|
667 |
+
for line in questionnaire_content.split('\n'):
|
668 |
+
line = line.strip()
|
669 |
+
if not line:
|
670 |
+
continue
|
671 |
+
|
672 |
+
if line.startswith('[Question:'):
|
673 |
+
if current_question:
|
674 |
+
questionnaire.append(current_question)
|
675 |
+
question_text = line.replace('[Question:', '').replace(']', '').strip()
|
676 |
+
current_question = {
|
677 |
+
'text': question_text,
|
678 |
+
'options': [],
|
679 |
+
'correct': ''
|
680 |
+
}
|
681 |
+
elif line.startswith('- [A]'):
|
682 |
+
current_question['options'].append(line.replace('- [A]', '').strip())
|
683 |
+
elif line.startswith('- [B]'):
|
684 |
+
current_question['options'].append(line.replace('- [B]', '').strip())
|
685 |
+
elif line.startswith('- [C]'):
|
686 |
+
current_question['options'].append(line.replace('- [C]', '').strip())
|
687 |
+
elif line.startswith('- [D]'):
|
688 |
+
current_question['options'].append(line.replace('- [D]', '').strip())
|
689 |
+
elif line.startswith('[Correct:'):
|
690 |
+
current_question['correct'] = line.replace('[Correct:', '').replace(']', '').strip()
|
691 |
+
|
692 |
+
if current_question:
|
693 |
+
questionnaire.append(current_question)
|
694 |
+
|
695 |
+
return slides, questionnaire
|
696 |
+
|
697 |
+
def create_questionnaire_slide(prs, questions, theme):
|
698 |
+
"""Create dedicated questionnaire slide"""
|
699 |
+
slide = prs.slides.add_slide(prs.slide_layouts[1]) # Title + Content layout
|
700 |
+
|
701 |
+
# Set title
|
702 |
+
title = slide.shapes.title
|
703 |
+
title.text = "Knowledge Check"
|
704 |
+
|
705 |
+
# Only apply custom formatting if not using a template
|
706 |
+
if "template_path" not in theme:
|
707 |
+
title.text_frame.paragraphs[0].font.color.rgb = theme["accent"]
|
708 |
+
title.text_frame.paragraphs[0].font.size = Pt(36)
|
709 |
+
title.text_frame.paragraphs[0].font.bold = True
|
710 |
+
if "title_font" in theme:
|
711 |
+
title.text_frame.paragraphs[0].font.name = theme["title_font"]
|
712 |
+
|
713 |
+
# Create content
|
714 |
+
body = slide.placeholders[1]
|
715 |
+
tf = body.text_frame
|
716 |
+
tf.clear()
|
717 |
+
|
718 |
+
for i, q in enumerate(questions):
|
719 |
+
# Add question
|
720 |
+
p = tf.add_paragraph()
|
721 |
+
p.text = f"{i+1}. {q['text']}"
|
722 |
+
p.level = 0
|
723 |
+
p.font.bold = True
|
724 |
+
p.space_after = Pt(8)
|
725 |
+
|
726 |
+
# Only apply custom formatting if not using a template
|
727 |
+
if "template_path" not in theme:
|
728 |
+
p.font.color.rgb = theme["text_color"]
|
729 |
+
p.font.size = Pt(20)
|
730 |
+
if "text_font" in theme:
|
731 |
+
p.font.name = theme["text_font"]
|
732 |
+
|
733 |
+
# Add options
|
734 |
+
for j, opt in enumerate(q['options']):
|
735 |
+
p = tf.add_paragraph()
|
736 |
+
p.text = f" {chr(65+j)}. {opt}"
|
737 |
+
p.level = 1
|
738 |
+
p.space_after = Pt(4)
|
739 |
+
|
740 |
+
# Only apply custom formatting if not using a template
|
741 |
+
if "template_path" not in theme:
|
742 |
+
p.font.color.rgb = theme["text_color"]
|
743 |
+
p.font.size = Pt(18)
|
744 |
+
if "text_font" in theme:
|
745 |
+
p.font.name = theme["text_font"]
|
746 |
+
|
747 |
+
# Add space between questions
|
748 |
+
p = tf.add_paragraph()
|
749 |
+
p.text = ""
|
750 |
+
p.space_after = Pt(16)
|
751 |
|
752 |
+
# Add correct answers to speaker notes
|
753 |
+
notes_slide = slide.notes_slide
|
754 |
+
notes_text = "Correct Answers:\n\n"
|
755 |
+
for i, q in enumerate(questions):
|
756 |
+
notes_text += f"{i+1}. {q['correct']}\n"
|
757 |
+
notes_slide.notes_text_frame.text = notes_text
|
758 |
+
|
759 |
+
return slide
|
760 |
+
|
761 |
+
def create_detailed_pptx(slides_data, questions, theme, branding_options=None):
|
762 |
"""Create PowerPoint using the uploaded template"""
|
763 |
# Use the template if one was uploaded
|
764 |
if "template_path" in theme and os.path.exists(theme["template_path"]):
|
|
|
785 |
|
786 |
default_layout_idx = available_layouts.get('title-content', 0)
|
787 |
|
788 |
+
# Create main slides
|
789 |
for slide_info in slides_data:
|
790 |
layout = slide_info.get('layout', 'title-content').lower()
|
791 |
layout_idx = available_layouts.get(layout, default_layout_idx)
|
|
|
870 |
notes_slide = slide.notes_slide
|
871 |
notes_slide.notes_text_frame.text = slide_info['notes']
|
872 |
|
873 |
+
# Add questionnaire slide if questions exist
|
874 |
+
if questions:
|
875 |
+
create_questionnaire_slide(prs, questions, theme)
|
876 |
+
|
877 |
pptx_io = io.BytesIO()
|
878 |
prs.save(pptx_io)
|
879 |
pptx_io.seek(0)
|
|
|
910 |
["Predefined Theme", "Custom Theme", "Example-Based Theme"])
|
911 |
|
912 |
theme = DEFAULT_THEMES["Professional Blue"] # Default theme
|
913 |
+
uploaded_file = None
|
914 |
|
915 |
if theme_option == "Predefined Theme":
|
916 |
theme_name = st.selectbox("Select Theme:", list(DEFAULT_THEMES.keys()))
|
|
|
954 |
st.info("Please upload a PowerPoint file to extract its theme")
|
955 |
|
956 |
with col2:
|
957 |
+
st.markdown("### Instructions")
|
958 |
+
st.markdown("1. Enter your presentation topic")
|
959 |
+
st.markdown("2. Select slide count and theme")
|
960 |
+
st.markdown("3. Click 'Generate Presentation'")
|
961 |
+
st.markdown("4. Download your PowerPoint file")
|
962 |
|
963 |
+
if st.button("Generate Presentation", type="primary", key="generate_btn"):
|
964 |
if not topic:
|
965 |
st.warning("Please enter a topic first!")
|
966 |
elif theme_option == "Example-Based Theme" and not uploaded_file:
|
|
|
969 |
with st.spinner(f"Creating {slide_count}-slide presentation about '{topic}'..."):
|
970 |
try:
|
971 |
slide_text = generate_slide_content(topic, slide_count)
|
972 |
+
slides_data, questionnaire = parse_slide_content(slide_text)
|
973 |
|
974 |
# Show slide overview with detailed content
|
975 |
with st.expander("Slide Overview (Detailed)"):
|
|
|
981 |
if slide.get('notes'):
|
982 |
st.markdown(f"**Notes:** {slide['notes']}")
|
983 |
st.markdown("---")
|
984 |
+
|
985 |
+
if questionnaire:
|
986 |
+
st.subheader("Questionnaire")
|
987 |
+
for i, q in enumerate(questionnaire, 1):
|
988 |
+
st.markdown(f"**{i}. {q['text']}**")
|
989 |
+
for j, opt in enumerate(q['options']):
|
990 |
+
st.markdown(f"- {chr(65+j)}. {opt}")
|
991 |
+
st.markdown(f"*Correct: {q['correct']}*")
|
992 |
+
st.markdown("---")
|
993 |
|
994 |
+
pptx_file = create_detailed_pptx(slides_data, questionnaire, theme)
|
995 |
|
996 |
st.success("Presentation generated successfully!")
|
997 |
|
|
|
1004 |
|
1005 |
except Exception as e:
|
1006 |
st.error(f"An error occurred: {str(e)}")
|
1007 |
+
|
1008 |
if __name__ == "__main__":
|
1009 |
main()
|