Update app.py via AI Editor
Browse files
app.py
CHANGED
|
@@ -1,24 +1,23 @@
|
|
| 1 |
import dash
|
| 2 |
from dash import dcc, html, Input, Output, State, no_update
|
| 3 |
import dash_bootstrap_components as dbc
|
|
|
|
| 4 |
import base64
|
| 5 |
import io
|
| 6 |
-
import re
|
| 7 |
-
import os
|
| 8 |
-
import requests
|
| 9 |
import logging
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
from pptx import Presentation
|
| 11 |
from pptx.util import Inches, Pt
|
| 12 |
-
from concurrent.futures import ThreadPoolExecutor
|
| 13 |
-
from dash.exceptions import PreventUpdate
|
| 14 |
-
from PIL import Image
|
| 15 |
-
import google.generativeai as genai
|
| 16 |
-
import time
|
| 17 |
import docx
|
| 18 |
import PyPDF2
|
|
|
|
|
|
|
|
|
|
| 19 |
from flask import request as flask_request, after_this_request
|
| 20 |
-
import
|
| 21 |
-
import uuid
|
| 22 |
|
| 23 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 24 |
logger = logging.getLogger(__name__)
|
|
@@ -29,14 +28,15 @@ genai.configure(api_key=GOOGLE_API_KEY)
|
|
| 29 |
model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25')
|
| 30 |
|
| 31 |
app = dash.Dash(__name__, external_stylesheets=[
|
| 32 |
-
dbc.themes.BOOTSTRAP,
|
| 33 |
'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap'
|
| 34 |
])
|
| 35 |
app.config.suppress_callback_exceptions = True
|
| 36 |
server = app.server
|
| 37 |
|
| 38 |
-
SESSION_LOCK = threading.Lock()
|
| 39 |
SESSION_DATA = {}
|
|
|
|
|
|
|
| 40 |
|
| 41 |
def get_session_id():
|
| 42 |
sid = flask_request.cookies.get('session_id')
|
|
@@ -62,6 +62,8 @@ def get_session_dict():
|
|
| 62 |
"generated_pptx": None,
|
| 63 |
"ppt_ready": False,
|
| 64 |
}
|
|
|
|
|
|
|
| 65 |
return SESSION_DATA[sid]
|
| 66 |
|
| 67 |
def add_log(message):
|
|
@@ -121,7 +123,7 @@ def process_document(text):
|
|
| 121 |
Generate slides with titles and content. Format the output as markdown,
|
| 122 |
with each slide starting with '# Slide X:' where X is the slide number.
|
| 123 |
Use '##' for subtitles and '-' for bullet points. Provide only the
|
| 124 |
-
slides in markdown and nothing else, to intro, no
|
| 125 |
do not use any other markdown like * or **
|
| 126 |
There can only be one slide heading # and one sub heading ## and no more than 5 bullets with "-" per slide
|
| 127 |
Document:
|
|
@@ -161,7 +163,7 @@ def generate_image(prompt):
|
|
| 161 |
}
|
| 162 |
files = {"none": ''}
|
| 163 |
try:
|
| 164 |
-
response = requests.post(url, headers=headers, data=data, files=files)
|
| 165 |
response.raise_for_status()
|
| 166 |
add_log("Image generated successfully.")
|
| 167 |
return response.content
|
|
@@ -174,55 +176,60 @@ def markdown_to_pptx(md_text):
|
|
| 174 |
prs = Presentation()
|
| 175 |
slides = re.split(r'# Slide \d+:', md_text)
|
| 176 |
slides = [slide.strip() for slide in slides if slide.strip()]
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
futures
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6))
|
| 221 |
-
placeholder.text_frame.text = "Image
|
| 222 |
-
else:
|
| 223 |
-
add_log(f"Failed to generate image for slide {i+1}: {title}")
|
| 224 |
-
placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6))
|
| 225 |
-
placeholder.text_frame.text = "Image generation failed"
|
| 226 |
add_log("PowerPoint generation completed.")
|
| 227 |
output = io.BytesIO()
|
| 228 |
prs.save(output)
|
|
@@ -263,65 +270,84 @@ def get_pptx_download_area(session_dict):
|
|
| 263 |
pptx_link
|
| 264 |
]) if pptx_link else ""
|
| 265 |
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
dbc.
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
dcc.Download(id="download-pptx"),
|
| 323 |
dcc.Interval(id='interval-component', interval=10*1000, n_intervals=0)
|
| 324 |
-
])
|
| 325 |
|
| 326 |
@app.callback(
|
| 327 |
Output('uploaded-file-area', 'children'),
|
|
@@ -330,7 +356,7 @@ app.layout = html.Div([
|
|
| 330 |
Output('document-text', 'value'),
|
| 331 |
Output('slides-content', 'value'),
|
| 332 |
Output("download-pptx", "data"),
|
| 333 |
-
Output('
|
| 334 |
Input('upload-file', 'filename'),
|
| 335 |
Input('upload-file', 'contents'),
|
| 336 |
Input('generate-button', 'n_clicks'),
|
|
@@ -346,15 +372,14 @@ app.layout = html.Div([
|
|
| 346 |
State('upload-file', 'contents'),
|
| 347 |
prevent_initial_call=True
|
| 348 |
)
|
| 349 |
-
def
|
| 350 |
-
|
| 351 |
-
|
| 352 |
ctx = dash.callback_context
|
| 353 |
session_dict = get_session_dict()
|
| 354 |
trigger = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
|
| 355 |
download_data = no_update
|
| 356 |
|
| 357 |
-
# Restore session state on page intervals/refresh
|
| 358 |
if trigger == "interval-component" or trigger is None:
|
| 359 |
return (
|
| 360 |
get_uploaded_file_area(session_dict),
|
|
@@ -363,38 +388,31 @@ def unified_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_fil
|
|
| 363 |
session_dict.get("document_text", ""),
|
| 364 |
session_dict.get("slides_markdown", ""),
|
| 365 |
no_update,
|
| 366 |
-
|
| 367 |
)
|
| 368 |
|
| 369 |
-
# File upload logic
|
| 370 |
if trigger == "upload-file" or (upload_filename and upload_contents):
|
| 371 |
session_dict["uploaded_filename"] = upload_filename
|
| 372 |
session_dict["uploaded_file"] = upload_contents
|
| 373 |
add_log(f"File uploaded: {upload_filename}")
|
| 374 |
|
| 375 |
-
# File delete logic
|
| 376 |
if trigger == "delete-file-btn":
|
| 377 |
session_dict["uploaded_filename"] = None
|
| 378 |
session_dict["uploaded_file"] = None
|
| 379 |
add_log("File deleted by user.")
|
| 380 |
|
| 381 |
-
# PPTX delete logic
|
| 382 |
if trigger == "delete-pptx-btn":
|
| 383 |
session_dict["generated_pptx"] = None
|
| 384 |
session_dict["ppt_ready"] = False
|
| 385 |
add_log("PowerPoint deleted by user.")
|
| 386 |
|
| 387 |
-
# Document text entry or edit
|
| 388 |
if trigger == "document-text":
|
| 389 |
session_dict["document_text"] = doc_text_val
|
| 390 |
|
| 391 |
-
# Slides markdown edit
|
| 392 |
if trigger == "slides-content":
|
| 393 |
session_dict["slides_markdown"] = slides_content_val
|
| 394 |
|
| 395 |
-
# Generate Slides
|
| 396 |
if trigger == "generate-button":
|
| 397 |
-
# Merge uploaded file and/or manual text
|
| 398 |
file_text = ""
|
| 399 |
if session_dict.get("uploaded_file") and session_dict.get("uploaded_filename"):
|
| 400 |
try:
|
|
@@ -410,7 +428,7 @@ def unified_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_fil
|
|
| 410 |
session_dict.get("document_text", ""),
|
| 411 |
msg,
|
| 412 |
no_update,
|
| 413 |
-
|
| 414 |
)
|
| 415 |
user_text = session_dict.get("document_text", "")
|
| 416 |
combined = (file_text + "\n\n" + user_text).strip() if file_text and user_text else (file_text or user_text)
|
|
@@ -424,7 +442,7 @@ def unified_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_fil
|
|
| 424 |
session_dict.get("document_text", ""),
|
| 425 |
msg,
|
| 426 |
no_update,
|
| 427 |
-
|
| 428 |
)
|
| 429 |
try:
|
| 430 |
add_log("Sending document to Gemini for slide generation...")
|
|
@@ -442,13 +460,11 @@ def unified_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_fil
|
|
| 442 |
session_dict.get("document_text", ""),
|
| 443 |
session_dict.get("slides_markdown", ""),
|
| 444 |
no_update,
|
| 445 |
-
|
| 446 |
)
|
| 447 |
|
| 448 |
-
# Generate PowerPoint
|
| 449 |
if trigger == "generate-ppt-button":
|
| 450 |
slides_md = session_dict.get("slides_markdown", "")
|
| 451 |
-
# Always take the latest textarea if changed
|
| 452 |
if slides_content_val is not None and slides_content_val.strip() != "":
|
| 453 |
slides_md = slides_content_val
|
| 454 |
session_dict["slides_markdown"] = slides_md
|
|
@@ -461,7 +477,7 @@ def unified_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_fil
|
|
| 461 |
session_dict.get("document_text", ""),
|
| 462 |
slides_md,
|
| 463 |
no_update,
|
| 464 |
-
|
| 465 |
)
|
| 466 |
try:
|
| 467 |
add_log("Starting PowerPoint generation from slides markdown...")
|
|
@@ -481,10 +497,9 @@ def unified_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_fil
|
|
| 481 |
session_dict.get("document_text", ""),
|
| 482 |
session_dict.get("slides_markdown", ""),
|
| 483 |
download_data,
|
| 484 |
-
|
| 485 |
)
|
| 486 |
|
| 487 |
-
# If editing/entering text/markdown, just update session
|
| 488 |
if trigger in ["document-text", "slides-content"]:
|
| 489 |
return (
|
| 490 |
get_uploaded_file_area(session_dict),
|
|
@@ -493,10 +508,9 @@ def unified_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_fil
|
|
| 493 |
session_dict.get("document_text", ""),
|
| 494 |
session_dict.get("slides_markdown", ""),
|
| 495 |
no_update,
|
| 496 |
-
|
| 497 |
)
|
| 498 |
|
| 499 |
-
# Default - restore
|
| 500 |
return (
|
| 501 |
get_uploaded_file_area(session_dict),
|
| 502 |
get_pptx_download_area(session_dict),
|
|
@@ -504,7 +518,7 @@ def unified_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_fil
|
|
| 504 |
session_dict.get("document_text", ""),
|
| 505 |
session_dict.get("slides_markdown", ""),
|
| 506 |
no_update,
|
| 507 |
-
|
| 508 |
)
|
| 509 |
|
| 510 |
@app.server.route("/download-pptx")
|
|
@@ -543,5 +557,5 @@ def serve_uploaded_file():
|
|
| 543 |
|
| 544 |
if __name__ == '__main__':
|
| 545 |
print("Starting the Dash application...")
|
| 546 |
-
app.run(debug=True, host='0.0.0.0', port=7860)
|
| 547 |
print("Dash application has finished running.")
|
|
|
|
| 1 |
import dash
|
| 2 |
from dash import dcc, html, Input, Output, State, no_update
|
| 3 |
import dash_bootstrap_components as dbc
|
| 4 |
+
import os
|
| 5 |
import base64
|
| 6 |
import io
|
|
|
|
|
|
|
|
|
|
| 7 |
import logging
|
| 8 |
+
import uuid
|
| 9 |
+
import threading
|
| 10 |
+
import time
|
| 11 |
+
import re
|
| 12 |
from pptx import Presentation
|
| 13 |
from pptx.util import Inches, Pt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
import docx
|
| 15 |
import PyPDF2
|
| 16 |
+
from PIL import Image
|
| 17 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 18 |
+
import requests
|
| 19 |
from flask import request as flask_request, after_this_request
|
| 20 |
+
import google.generativeai as genai
|
|
|
|
| 21 |
|
| 22 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 23 |
logger = logging.getLogger(__name__)
|
|
|
|
| 28 |
model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25')
|
| 29 |
|
| 30 |
app = dash.Dash(__name__, external_stylesheets=[
|
| 31 |
+
dbc.themes.BOOTSTRAP,
|
| 32 |
'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap'
|
| 33 |
])
|
| 34 |
app.config.suppress_callback_exceptions = True
|
| 35 |
server = app.server
|
| 36 |
|
|
|
|
| 37 |
SESSION_DATA = {}
|
| 38 |
+
SESSION_LOCK = threading.Lock()
|
| 39 |
+
SESSION_THREAD_LOCKS = {}
|
| 40 |
|
| 41 |
def get_session_id():
|
| 42 |
sid = flask_request.cookies.get('session_id')
|
|
|
|
| 62 |
"generated_pptx": None,
|
| 63 |
"ppt_ready": False,
|
| 64 |
}
|
| 65 |
+
if sid not in SESSION_THREAD_LOCKS:
|
| 66 |
+
SESSION_THREAD_LOCKS[sid] = threading.Lock()
|
| 67 |
return SESSION_DATA[sid]
|
| 68 |
|
| 69 |
def add_log(message):
|
|
|
|
| 123 |
Generate slides with titles and content. Format the output as markdown,
|
| 124 |
with each slide starting with '# Slide X:' where X is the slide number.
|
| 125 |
Use '##' for subtitles and '-' for bullet points. Provide only the
|
| 126 |
+
slides in markdown and nothing else, to intro, no acknowledgements, no outro, no summary, just the powerpoint.
|
| 127 |
do not use any other markdown like * or **
|
| 128 |
There can only be one slide heading # and one sub heading ## and no more than 5 bullets with "-" per slide
|
| 129 |
Document:
|
|
|
|
| 163 |
}
|
| 164 |
files = {"none": ''}
|
| 165 |
try:
|
| 166 |
+
response = requests.post(url, headers=headers, data=data, files=files, timeout=60)
|
| 167 |
response.raise_for_status()
|
| 168 |
add_log("Image generated successfully.")
|
| 169 |
return response.content
|
|
|
|
| 176 |
prs = Presentation()
|
| 177 |
slides = re.split(r'# Slide \d+:', md_text)
|
| 178 |
slides = [slide.strip() for slide in slides if slide.strip()]
|
| 179 |
+
session_dict = get_session_dict()
|
| 180 |
+
sid = get_session_id()
|
| 181 |
+
thread_lock = SESSION_THREAD_LOCKS[sid]
|
| 182 |
+
with thread_lock:
|
| 183 |
+
with ThreadPoolExecutor(max_workers=2) as executor:
|
| 184 |
+
futures = []
|
| 185 |
+
for i, slide_content in enumerate(slides):
|
| 186 |
+
add_log(f"Processing slide {i+1}/{len(slides)}...")
|
| 187 |
+
image_prompt = generate_image_prompt(slide_content)
|
| 188 |
+
futures.append(executor.submit(generate_image, image_prompt))
|
| 189 |
+
for i, (slide_content, future) in enumerate(zip(slides, futures)):
|
| 190 |
+
add_log(f"Creating slide {i+1}/{len(slides)}...")
|
| 191 |
+
lines = slide_content.split('\n')
|
| 192 |
+
title = lines[0].strip() if lines else f"Slide {i+1}"
|
| 193 |
+
current_slide = prs.slides.add_slide(prs.slide_layouts[6])
|
| 194 |
+
title_box = current_slide.shapes.add_textbox(Inches(0.5), Inches(0.5), Inches(9), Inches(1))
|
| 195 |
+
title_box.text_frame.text = title
|
| 196 |
+
title_box.text_frame.paragraphs[0].font.size = Pt(32)
|
| 197 |
+
title_box.text_frame.paragraphs[0].font.bold = True
|
| 198 |
+
content_box = current_slide.shapes.add_textbox(Inches(0.5), Inches(1.5), Inches(4.5), Inches(6))
|
| 199 |
+
text_frame = content_box.text_frame
|
| 200 |
+
text_frame.word_wrap = True
|
| 201 |
+
for line in lines[1:]:
|
| 202 |
+
if line.startswith('## '):
|
| 203 |
+
p = text_frame.add_paragraph()
|
| 204 |
+
p.text = line[3:].strip()
|
| 205 |
+
p.font.size = Pt(24)
|
| 206 |
+
p.font.bold = True
|
| 207 |
+
elif line.startswith('- '):
|
| 208 |
+
p = text_frame.add_paragraph()
|
| 209 |
+
p.text = line[2:].strip()
|
| 210 |
+
p.font.size = Pt(18)
|
| 211 |
+
p.level = 1
|
| 212 |
+
p.bullet = True
|
| 213 |
+
add_log(f"Adding image to slide {i+1}...")
|
| 214 |
+
image_data = future.result()
|
| 215 |
+
if image_data:
|
| 216 |
+
image_stream = io.BytesIO(image_data)
|
| 217 |
+
try:
|
| 218 |
+
img = Image.open(image_stream)
|
| 219 |
+
aspect_ratio = img.width / img.height
|
| 220 |
+
img_width = Inches(4)
|
| 221 |
+
img_height = img_width / aspect_ratio
|
| 222 |
+
image_stream.seek(0)
|
| 223 |
+
current_slide.shapes.add_picture(image_stream, Inches(5.5), Inches(1.5), width=img_width, height=img_height)
|
| 224 |
+
add_log(f"Image added successfully to slide {i+1}.")
|
| 225 |
+
except Exception as e:
|
| 226 |
+
add_log(f"Error adding image to slide {i+1}: {str(e)}")
|
| 227 |
+
placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6))
|
| 228 |
+
placeholder.text_frame.text = "Image addition failed"
|
| 229 |
+
else:
|
| 230 |
+
add_log(f"Failed to generate image for slide {i+1}: {title}")
|
| 231 |
placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6))
|
| 232 |
+
placeholder.text_frame.text = "Image generation failed"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
add_log("PowerPoint generation completed.")
|
| 234 |
output = io.BytesIO()
|
| 235 |
prs.save(output)
|
|
|
|
| 270 |
pptx_link
|
| 271 |
]) if pptx_link else ""
|
| 272 |
|
| 273 |
+
def get_log_area(session_dict):
|
| 274 |
+
logs = session_dict.get("log_messages", [])
|
| 275 |
+
return html.Div(
|
| 276 |
+
[html.Div(log, style={'fontSize': '0.8em'}) for log in logs[:10]],
|
| 277 |
+
style={'maxHeight': '180px', 'overflowY': 'auto', "fontFamily": "monospace", "background": "#f8f9fa", "padding": "8px", "borderRadius": "5px"}
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
app.layout = dbc.Container([
|
| 281 |
+
dbc.Row([
|
| 282 |
+
dbc.Col([
|
| 283 |
+
dbc.Card([
|
| 284 |
+
dbc.CardHeader("PowerPoint Generator", className="text-center"),
|
| 285 |
+
], className="mb-3"),
|
| 286 |
+
], width=12)
|
| 287 |
+
], className="mb-2"),
|
| 288 |
+
dbc.Row([
|
| 289 |
+
dbc.Col([
|
| 290 |
+
dbc.Card([
|
| 291 |
+
dbc.CardHeader("Upload & Controls", className="bg-light"),
|
| 292 |
+
dbc.CardBody([
|
| 293 |
+
dcc.Upload(
|
| 294 |
+
id='upload-file',
|
| 295 |
+
children=html.Div([
|
| 296 |
+
html.I(className="fas fa-cloud-upload-alt fa-2x mb-2"),
|
| 297 |
+
html.Div("Drag and Drop or Select File")
|
| 298 |
+
], className="text-center"),
|
| 299 |
+
style={
|
| 300 |
+
'borderWidth': '2px',
|
| 301 |
+
'borderStyle': 'dashed',
|
| 302 |
+
'borderRadius': '5px',
|
| 303 |
+
'padding': '20px',
|
| 304 |
+
'marginBottom': '16px'
|
| 305 |
+
}
|
| 306 |
+
),
|
| 307 |
+
html.Div(id="uploaded-file-area", className="mb-2"),
|
| 308 |
+
dbc.Button("Delete Uploaded File", id='delete-file-btn', color="secondary", size="sm", className="mb-3 w-100"),
|
| 309 |
+
html.Hr(),
|
| 310 |
+
dbc.Textarea(
|
| 311 |
+
id='document-text',
|
| 312 |
+
placeholder='Paste your document here',
|
| 313 |
+
style={'height': '140px', 'whiteSpace': 'pre-wrap', 'wordBreak': 'break-word'},
|
| 314 |
+
className="mb-2"
|
| 315 |
+
),
|
| 316 |
+
dbc.Button("Generate Slides", id='generate-button', color="primary", className="w-100 mb-2", size="md"),
|
| 317 |
+
html.Hr(),
|
| 318 |
+
html.Div(id="pptx-download-area", className="mb-2"),
|
| 319 |
+
dbc.Button("Delete Generated PowerPoint", id='delete-pptx-btn', color="secondary", size="sm", className="mb-3 w-100"),
|
| 320 |
+
html.Hr(),
|
| 321 |
+
html.Div("Recent Logs:", className="mb-1", style={"fontWeight": "bold"}),
|
| 322 |
+
html.Div(id="log-area", className="mb-2"),
|
| 323 |
+
])
|
| 324 |
+
])
|
| 325 |
+
], width=4, style={'background': '#f7f7f7', 'height': '100vh', 'position': 'fixed', 'left':0, 'top':0, 'bottom':0, 'paddingTop': '15px', 'zIndex': 2}),
|
| 326 |
+
dbc.Col([
|
| 327 |
+
dbc.Card([
|
| 328 |
+
dbc.CardHeader("Slides Markdown (Editable)", className="bg-light"),
|
| 329 |
+
dbc.CardBody([
|
| 330 |
+
dcc.Loading(
|
| 331 |
+
id="loading",
|
| 332 |
+
type="default",
|
| 333 |
+
fullscreen=False,
|
| 334 |
+
children=[
|
| 335 |
+
dbc.Textarea(
|
| 336 |
+
id='slides-content',
|
| 337 |
+
placeholder='Generated slide markdown will appear here. You can edit before generating PowerPoint.',
|
| 338 |
+
style={'height': 'calc(90vh - 120px)', 'whiteSpace': 'pre-wrap', 'wordBreak': 'break-word'},
|
| 339 |
+
className="mb-3"
|
| 340 |
+
),
|
| 341 |
+
dbc.Button("Generate PowerPoint", id='generate-ppt-button', color="primary", className="w-100 mt-1", size="lg"),
|
| 342 |
+
]
|
| 343 |
+
),
|
| 344 |
+
], style={'height': '100%'})
|
| 345 |
+
], className="mb-4 shadow-sm", style={'height': '93vh'})
|
| 346 |
+
], width={"size":8, "offset":4}, style={'paddingLeft': '34%', 'background': '#fff', 'height': '100vh'})
|
| 347 |
+
], style={"height": "100vh"}),
|
| 348 |
dcc.Download(id="download-pptx"),
|
| 349 |
dcc.Interval(id='interval-component', interval=10*1000, n_intervals=0)
|
| 350 |
+
], fluid=True, style={"padding":0, "overflowX": "hidden", "minHeight": "100vh"})
|
| 351 |
|
| 352 |
@app.callback(
|
| 353 |
Output('uploaded-file-area', 'children'),
|
|
|
|
| 356 |
Output('document-text', 'value'),
|
| 357 |
Output('slides-content', 'value'),
|
| 358 |
Output("download-pptx", "data"),
|
| 359 |
+
Output('log-area', 'children'),
|
| 360 |
Input('upload-file', 'filename'),
|
| 361 |
Input('upload-file', 'contents'),
|
| 362 |
Input('generate-button', 'n_clicks'),
|
|
|
|
| 372 |
State('upload-file', 'contents'),
|
| 373 |
prevent_initial_call=True
|
| 374 |
)
|
| 375 |
+
def main_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_file_btn, del_pptx_btn,
|
| 376 |
+
doc_text_val, slides_content_val, interval,
|
| 377 |
+
state_doc_text, state_slides_content, state_upload_filename, state_upload_contents):
|
| 378 |
ctx = dash.callback_context
|
| 379 |
session_dict = get_session_dict()
|
| 380 |
trigger = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
|
| 381 |
download_data = no_update
|
| 382 |
|
|
|
|
| 383 |
if trigger == "interval-component" or trigger is None:
|
| 384 |
return (
|
| 385 |
get_uploaded_file_area(session_dict),
|
|
|
|
| 388 |
session_dict.get("document_text", ""),
|
| 389 |
session_dict.get("slides_markdown", ""),
|
| 390 |
no_update,
|
| 391 |
+
get_log_area(session_dict)
|
| 392 |
)
|
| 393 |
|
|
|
|
| 394 |
if trigger == "upload-file" or (upload_filename and upload_contents):
|
| 395 |
session_dict["uploaded_filename"] = upload_filename
|
| 396 |
session_dict["uploaded_file"] = upload_contents
|
| 397 |
add_log(f"File uploaded: {upload_filename}")
|
| 398 |
|
|
|
|
| 399 |
if trigger == "delete-file-btn":
|
| 400 |
session_dict["uploaded_filename"] = None
|
| 401 |
session_dict["uploaded_file"] = None
|
| 402 |
add_log("File deleted by user.")
|
| 403 |
|
|
|
|
| 404 |
if trigger == "delete-pptx-btn":
|
| 405 |
session_dict["generated_pptx"] = None
|
| 406 |
session_dict["ppt_ready"] = False
|
| 407 |
add_log("PowerPoint deleted by user.")
|
| 408 |
|
|
|
|
| 409 |
if trigger == "document-text":
|
| 410 |
session_dict["document_text"] = doc_text_val
|
| 411 |
|
|
|
|
| 412 |
if trigger == "slides-content":
|
| 413 |
session_dict["slides_markdown"] = slides_content_val
|
| 414 |
|
|
|
|
| 415 |
if trigger == "generate-button":
|
|
|
|
| 416 |
file_text = ""
|
| 417 |
if session_dict.get("uploaded_file") and session_dict.get("uploaded_filename"):
|
| 418 |
try:
|
|
|
|
| 428 |
session_dict.get("document_text", ""),
|
| 429 |
msg,
|
| 430 |
no_update,
|
| 431 |
+
get_log_area(session_dict)
|
| 432 |
)
|
| 433 |
user_text = session_dict.get("document_text", "")
|
| 434 |
combined = (file_text + "\n\n" + user_text).strip() if file_text and user_text else (file_text or user_text)
|
|
|
|
| 442 |
session_dict.get("document_text", ""),
|
| 443 |
msg,
|
| 444 |
no_update,
|
| 445 |
+
get_log_area(session_dict)
|
| 446 |
)
|
| 447 |
try:
|
| 448 |
add_log("Sending document to Gemini for slide generation...")
|
|
|
|
| 460 |
session_dict.get("document_text", ""),
|
| 461 |
session_dict.get("slides_markdown", ""),
|
| 462 |
no_update,
|
| 463 |
+
get_log_area(session_dict)
|
| 464 |
)
|
| 465 |
|
|
|
|
| 466 |
if trigger == "generate-ppt-button":
|
| 467 |
slides_md = session_dict.get("slides_markdown", "")
|
|
|
|
| 468 |
if slides_content_val is not None and slides_content_val.strip() != "":
|
| 469 |
slides_md = slides_content_val
|
| 470 |
session_dict["slides_markdown"] = slides_md
|
|
|
|
| 477 |
session_dict.get("document_text", ""),
|
| 478 |
slides_md,
|
| 479 |
no_update,
|
| 480 |
+
get_log_area(session_dict)
|
| 481 |
)
|
| 482 |
try:
|
| 483 |
add_log("Starting PowerPoint generation from slides markdown...")
|
|
|
|
| 497 |
session_dict.get("document_text", ""),
|
| 498 |
session_dict.get("slides_markdown", ""),
|
| 499 |
download_data,
|
| 500 |
+
get_log_area(session_dict)
|
| 501 |
)
|
| 502 |
|
|
|
|
| 503 |
if trigger in ["document-text", "slides-content"]:
|
| 504 |
return (
|
| 505 |
get_uploaded_file_area(session_dict),
|
|
|
|
| 508 |
session_dict.get("document_text", ""),
|
| 509 |
session_dict.get("slides_markdown", ""),
|
| 510 |
no_update,
|
| 511 |
+
get_log_area(session_dict)
|
| 512 |
)
|
| 513 |
|
|
|
|
| 514 |
return (
|
| 515 |
get_uploaded_file_area(session_dict),
|
| 516 |
get_pptx_download_area(session_dict),
|
|
|
|
| 518 |
session_dict.get("document_text", ""),
|
| 519 |
session_dict.get("slides_markdown", ""),
|
| 520 |
no_update,
|
| 521 |
+
get_log_area(session_dict)
|
| 522 |
)
|
| 523 |
|
| 524 |
@app.server.route("/download-pptx")
|
|
|
|
| 557 |
|
| 558 |
if __name__ == '__main__':
|
| 559 |
print("Starting the Dash application...")
|
| 560 |
+
app.run(debug=True, host='0.0.0.0', port=7860, threaded=True)
|
| 561 |
print("Dash application has finished running.")
|