bluenevus commited on
Commit
ffa82ab
·
1 Parent(s): d906ee2

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +158 -144
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 threading
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 acknowlegements, no outro, no summary, just the powerpoint.
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
- with ThreadPoolExecutor() as executor:
178
- futures = []
179
- for i, slide_content in enumerate(slides):
180
- add_log(f"Processing slide {i+1}/{len(slides)}...")
181
- image_prompt = generate_image_prompt(slide_content)
182
- futures.append(executor.submit(generate_image, image_prompt))
183
- for i, (slide_content, future) in enumerate(zip(slides, futures)):
184
- add_log(f"Creating slide {i+1}/{len(slides)}...")
185
- lines = slide_content.split('\n')
186
- title = lines[0].strip()
187
- current_slide = prs.slides.add_slide(prs.slide_layouts[6])
188
- title_box = current_slide.shapes.add_textbox(Inches(0.5), Inches(0.5), Inches(9), Inches(1))
189
- title_box.text_frame.text = title
190
- title_box.text_frame.paragraphs[0].font.size = Pt(32)
191
- title_box.text_frame.paragraphs[0].font.bold = True
192
- content_box = current_slide.shapes.add_textbox(Inches(0.5), Inches(1.5), Inches(4.5), Inches(6))
193
- text_frame = content_box.text_frame
194
- text_frame.word_wrap = True
195
- for line in lines[1:]:
196
- if line.startswith('## '):
197
- p = text_frame.add_paragraph()
198
- p.text = line[3:].strip()
199
- p.font.size = Pt(24)
200
- p.font.bold = True
201
- elif line.startswith('- '):
202
- p = text_frame.add_paragraph()
203
- p.text = line[2:].strip()
204
- p.font.size = Pt(18)
205
- p.level = 1
206
- p.bullet = True
207
- add_log(f"Adding image to slide {i+1}...")
208
- image_data = future.result()
209
- if image_data:
210
- image_stream = io.BytesIO(image_data)
211
- try:
212
- img = Image.open(image_stream)
213
- aspect_ratio = img.width / img.height
214
- img_width = Inches(4)
215
- img_height = img_width / aspect_ratio
216
- current_slide.shapes.add_picture(image_stream, Inches(5.5), Inches(1.5), width=img_width, height=img_height)
217
- add_log(f"Image added successfully to slide {i+1}.")
218
- except Exception as e:
219
- add_log(f"Error adding image to slide {i+1}: {str(e)}")
 
 
 
 
 
 
 
 
 
220
  placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6))
221
- placeholder.text_frame.text = "Image addition failed"
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
- app.layout = html.Div([
267
- dcc.Store(id='session-init', storage_type='session'),
268
- dcc.Store(id='loading-state', storage_type='session'),
269
- html.Div([
270
- dbc.Button("Delete", id='delete-file-btn', color="danger", size="sm", n_clicks=0, style={"display": "none"}),
271
- dbc.Button("Delete", id='delete-pptx-btn', color="danger", size="sm", n_clicks=0, style={"display": "none"}),
272
- ], style={"display": "none"}),
273
- dbc.Container([
274
- dbc.Row([
275
- dbc.Col([
276
- dbc.Card([
277
- dbc.CardHeader("Upload or Paste Document", className="bg-light"),
278
- dbc.CardBody([
279
- dbc.Textarea(
280
- id='document-text',
281
- placeholder='Enter or paste your document here',
282
- style={'height': '200px', 'whiteSpace': 'pre-wrap', 'wordBreak': 'break-word'},
283
- className="mb-3"
284
- ),
285
- dcc.Upload(
286
- id='upload-file',
287
- children=html.Div([
288
- html.I(className="fas fa-cloud-upload-alt fa-3x mb-3"),
289
- html.Div("Drag and Drop or Select File")
290
- ], className="text-center"),
291
- style={
292
- 'borderWidth': '2px',
293
- 'borderStyle': 'dashed',
294
- 'borderRadius': '5px',
295
- 'padding': '20px',
296
- 'transition': 'border .3s ease-in-out'
297
- },
298
- className="mb-3"
299
- ),
300
- html.Div(id="uploaded-file-area", className="mb-2"),
301
- html.Div(id="pptx-download-area", className="mb-2"),
302
- dbc.Button("Generate Slides", id='generate-button', color="primary", className="w-100 mt-3", size="lg"),
303
- ], style={'display': 'flex', 'flexDirection': 'column', 'height': '100%'})
304
- ], className="mb-4 shadow-sm", style={'height': '100%'})
305
- ], md=4, style={'minHeight': '60vh', 'height': '70vh', 'display': 'flex', 'flexDirection': 'column'}),
306
- dbc.Col([
307
- dbc.Card([
308
- dbc.CardHeader("Edit Slide Content", className="bg-light"),
309
- dbc.CardBody([
310
- dbc.Textarea(
311
- id='slides-content',
312
- placeholder='Generated or pasted markdown will appear here. You can edit the content.',
313
- style={'height': 'calc(100vh - 120px)', 'whiteSpace': 'pre-wrap', 'wordBreak': 'break-word'},
314
- className="mb-3"
315
- ),
316
- dbc.Button("Generate PowerPoint", id='generate-ppt-button', color="primary", className="w-100 mt-3", size="lg"),
317
- ], style={'display': 'flex', 'flexDirection': 'column', 'height': '100%'})
318
- ], className="mb-4 shadow-sm", style={'height': '100%'})
319
- ], md=8, style={'minHeight': '70vh', 'height': '80vh', 'display': 'flex', 'flexDirection': 'column'})
320
- ], style={'minHeight': '100vh', 'height': '100vh'})
321
- ], fluid=True, className="mt-4", style={'minHeight': '100vh', 'height': '100vh'}),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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('session-init', 'data'),
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 unified_callback(upload_filename, upload_contents, gen_btn, ppt_btn, del_file_btn, del_pptx_btn,
350
- doc_text_val, slides_content_val, interval,
351
- state_doc_text, state_slides_content, state_upload_filename, state_upload_contents):
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
- True
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
- True
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
- True
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
- True
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
- True
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
- True
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
- True
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
- True
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.")