Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import PyPDF2
|
3 |
+
from pptx import Presentation
|
4 |
+
from PIL import Image
|
5 |
+
import io
|
6 |
+
from google import genai
|
7 |
+
from jinja2 import Template
|
8 |
+
import fitz # PyMuPDF
|
9 |
+
import os
|
10 |
+
import logging
|
11 |
+
import re
|
12 |
+
import time
|
13 |
+
|
14 |
+
# Set up logging
|
15 |
+
logging.basicConfig(level=logging.INFO)
|
16 |
+
|
17 |
+
# Function to extract content from PDF
|
18 |
+
def extract_content_from_pdf(file_path):
|
19 |
+
try:
|
20 |
+
text = ""
|
21 |
+
images = []
|
22 |
+
doc = fitz.open(file_path)
|
23 |
+
for page in doc:
|
24 |
+
text += page.get_text() + "\n"
|
25 |
+
for img in page.get_images():
|
26 |
+
xref = img[0]
|
27 |
+
base_image = doc.extract_image(xref)
|
28 |
+
image_bytes = base_image["image"]
|
29 |
+
image = Image.open(io.BytesIO(image_bytes))
|
30 |
+
images.append(image)
|
31 |
+
return text, images
|
32 |
+
except Exception as e:
|
33 |
+
logging.error(f"Error extracting content from PDF: {e}")
|
34 |
+
return "", []
|
35 |
+
|
36 |
+
# Function to extract content from PPTX
|
37 |
+
def extract_content_from_pptx(file_path):
|
38 |
+
try:
|
39 |
+
text = ""
|
40 |
+
images = []
|
41 |
+
prs = Presentation(file_path)
|
42 |
+
for slide in prs.slides:
|
43 |
+
for shape in slide.shapes:
|
44 |
+
if hasattr(shape, 'text'):
|
45 |
+
text += shape.text + "\n"
|
46 |
+
if shape.shape_type == 13: # Picture
|
47 |
+
image = shape.image
|
48 |
+
image_bytes = image.blob
|
49 |
+
image = Image.open(io.BytesIO(image_bytes))
|
50 |
+
images.append(image)
|
51 |
+
return text, images
|
52 |
+
except Exception as e:
|
53 |
+
logging.error(f"Error extracting content from PPTX: {e}")
|
54 |
+
return "", []
|
55 |
+
|
56 |
+
# Function to process file
|
57 |
+
def process_file(file_path):
|
58 |
+
if file_path is None:
|
59 |
+
return "No file uploaded", []
|
60 |
+
|
61 |
+
try:
|
62 |
+
if file_path.lower().endswith('.pdf'):
|
63 |
+
return extract_content_from_pdf(file_path)
|
64 |
+
elif file_path.lower().endswith('.pptx'):
|
65 |
+
return extract_content_from_pptx(file_path)
|
66 |
+
else:
|
67 |
+
return "Unsupported file format", []
|
68 |
+
except Exception as e:
|
69 |
+
logging.error(f"Error processing file: {e}")
|
70 |
+
return f"An error occurred while processing the file: {str(e)}", []
|
71 |
+
|
72 |
+
# Function to clean response
|
73 |
+
def clean_response(response_text):
|
74 |
+
# Remove code block markers if present
|
75 |
+
cleaned = re.sub(r'```python|```', '', response_text).strip()
|
76 |
+
# Handle newlines and indentation
|
77 |
+
cleaned = re.sub(r'\n\s*\n', '\n\n', cleaned)
|
78 |
+
return cleaned
|
79 |
+
|
80 |
+
# Function to understand text and images using Gemini V2 API
|
81 |
+
def understand_content(api_key, text, images, progress=gr.Progress()):
|
82 |
+
try:
|
83 |
+
# Initialize the Gemini client
|
84 |
+
client = genai.Client(api_key=api_key)
|
85 |
+
|
86 |
+
progress(0.3, desc="Preparing content...")
|
87 |
+
# Prepare content for Gemini
|
88 |
+
content = [text]
|
89 |
+
for image in images[:10]: # Limit to 10 images
|
90 |
+
content.append(image)
|
91 |
+
|
92 |
+
progress(0.5, desc="Generating unit plan...")
|
93 |
+
# Generate response from Gemini
|
94 |
+
prompt = Template("""
|
95 |
+
You are an expert instructional designer.
|
96 |
+
Below are materials shared by a teacher. Your role is to reverse-engineer the unit planner for this content.
|
97 |
+
To do so:
|
98 |
+
1) Read the content carefully
|
99 |
+
2) Create a unit planner for this content that follows this exact structure:
|
100 |
+
{{ unit_plan_structure }}
|
101 |
+
""").render(unit_plan_structure="""
|
102 |
+
# Standards
|
103 |
+
# Transfer Goal
|
104 |
+
# Essential Questions
|
105 |
+
# Enduring Understandings
|
106 |
+
# Students will know
|
107 |
+
# Students will be able to
|
108 |
+
# Formative Assessments
|
109 |
+
# Summative Assessments
|
110 |
+
# Scope and Sequence
|
111 |
+
# Unit Overview
|
112 |
+
# Potential barriers
|
113 |
+
# Connections
|
114 |
+
## ISP Core Values
|
115 |
+
## IB Theory of Knowledge
|
116 |
+
## IB Approaches to Learning
|
117 |
+
# Authentic Assessment
|
118 |
+
""")
|
119 |
+
|
120 |
+
response = client.models.generate_content(
|
121 |
+
model="gemini-2.0-flash-exp",
|
122 |
+
contents=prompt + text
|
123 |
+
)
|
124 |
+
|
125 |
+
progress(0.8, desc="Finalizing output...")
|
126 |
+
# Log the raw response for debugging
|
127 |
+
response_text = response.text
|
128 |
+
logging.info(f"Raw response from Gemini: {response_text}")
|
129 |
+
|
130 |
+
# Clean the response
|
131 |
+
cleaned_response = clean_response(response_text)
|
132 |
+
logging.info(f"Cleaned response: {cleaned_response}")
|
133 |
+
|
134 |
+
progress(1.0, desc="Complete!")
|
135 |
+
return cleaned_response
|
136 |
+
|
137 |
+
except Exception as e:
|
138 |
+
logging.error(f"Error in content understanding: {e}")
|
139 |
+
return f"Error in processing the content: {str(e)}. Please check your API key and try again."
|
140 |
+
|
141 |
+
# Function to reverse engineer unit plan
|
142 |
+
def generate_elt_plan(api_key, file, progress=gr.Progress()):
|
143 |
+
if not api_key:
|
144 |
+
return "Please enter your Gemini API key", None
|
145 |
+
|
146 |
+
try:
|
147 |
+
progress(0.1, desc="Starting file processing...")
|
148 |
+
logging.info(f"Processing file: {file.name}")
|
149 |
+
content, images = process_file(file.name)
|
150 |
+
if isinstance(content, str) and content.startswith("An error occurred"):
|
151 |
+
return content, None
|
152 |
+
logging.info(f"Extracted content length: {len(content)}, Number of images: {len(images)}")
|
153 |
+
progress(0.2, desc="Content extracted, generating plan...")
|
154 |
+
elt_plan = understand_content(api_key, content, images, progress)
|
155 |
+
|
156 |
+
# Create a downloadable text file
|
157 |
+
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
158 |
+
filename = f"unit_plan_{timestamp}.txt"
|
159 |
+
with open(filename, "w", encoding="utf-8") as f:
|
160 |
+
f.write(elt_plan)
|
161 |
+
|
162 |
+
return elt_plan, filename
|
163 |
+
except Exception as e:
|
164 |
+
logging.error(f"Error in generate_elt_plan: {e}")
|
165 |
+
return f"An error occurred: {str(e)}", None
|
166 |
+
|
167 |
+
# Set up Gradio Blocks
|
168 |
+
with gr.Blocks() as demo:
|
169 |
+
gr.Markdown("# π Reverse Unit Planner")
|
170 |
+
|
171 |
+
with gr.Row():
|
172 |
+
with gr.Column():
|
173 |
+
api_key = gr.Textbox(label="Enter your Gemini API key", type="password")
|
174 |
+
file_input = gr.File(label="Upload PPTX or PDF")
|
175 |
+
submit_btn = gr.Button("Reverse engineer a unit plan", variant="primary")
|
176 |
+
|
177 |
+
with gr.Column():
|
178 |
+
output = gr.Markdown(label="Draft Unit Planner")
|
179 |
+
copy_btn = gr.Button("π Copy to Clipboard")
|
180 |
+
download_btn = gr.File(label="Download Unit Plan")
|
181 |
+
|
182 |
+
# Handle the main processing
|
183 |
+
result = submit_btn.click(
|
184 |
+
generate_elt_plan,
|
185 |
+
inputs=[api_key, file_input],
|
186 |
+
outputs=[output, download_btn]
|
187 |
+
)
|
188 |
+
|
189 |
+
# Add copy to clipboard functionality
|
190 |
+
copy_btn.click(
|
191 |
+
fn=None,
|
192 |
+
inputs=output,
|
193 |
+
outputs=None,
|
194 |
+
js="""
|
195 |
+
async (text) => {
|
196 |
+
await navigator.clipboard.writeText(text);
|
197 |
+
await new Promise(resolve => {
|
198 |
+
const notification = document.createElement('div');
|
199 |
+
notification.textContent = 'Copied to clipboard!';
|
200 |
+
notification.style.position = 'fixed';
|
201 |
+
notification.style.bottom = '20px';
|
202 |
+
notification.style.left = '50%';
|
203 |
+
notification.style.transform = 'translateX(-50%)';
|
204 |
+
notification.style.backgroundColor = '#4CAF50';
|
205 |
+
notification.style.color = 'white';
|
206 |
+
notification.style.padding = '10px 20px';
|
207 |
+
notification.style.borderRadius = '5px';
|
208 |
+
notification.style.zIndex = '1000';
|
209 |
+
document.body.appendChild(notification);
|
210 |
+
setTimeout(() => {
|
211 |
+
notification.remove();
|
212 |
+
resolve();
|
213 |
+
}, 2000);
|
214 |
+
});
|
215 |
+
}
|
216 |
+
""")
|
217 |
+
|
218 |
+
# Launch the app
|
219 |
+
demo.launch()
|