bluenevus commited on
Commit
7ff0849
·
1 Parent(s): 4bc4fe1

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +99 -102
app.py CHANGED
@@ -47,18 +47,19 @@ def get_or_create_session_data(session_id):
47
  SESSION_DATA[session_id] = {
48
  'log_messages': [],
49
  'generated_pptx': None,
50
- 'uploaded_file': None,
51
- 'uploaded_filename': None,
52
  'lock': threading.Lock(),
53
  'slide_markdowns': [],
54
  'slide_markdown_names': [],
55
  'current_slide_md': "",
 
56
  }
57
  return SESSION_DATA[session_id]
58
 
59
  app.layout = html.Div([
60
  dcc.Store(id='session-id', storage_type='local'),
61
  dcc.Store(id='current-slide-index', storage_type='session'),
 
62
  html.Script('''
63
  (function() {
64
  function uuidv4() {
@@ -79,9 +80,6 @@ app.layout = html.Div([
79
  window.dash_clientside.sessionid = sessionid;
80
  })();
81
  '''),
82
- html.A(id='uploaded-file-download-link', href="", style={'display': 'none'}),
83
- html.A(id='pptx-file-download-link', href="", style={'display': 'none'}),
84
- dcc.Dropdown(id='slide-generation-list', style={'display': 'none'}),
85
  dcc.Download(id="download-dynamic"),
86
  dbc.Container([
87
  dbc.Row([
@@ -110,18 +108,11 @@ app.layout = html.Div([
110
  },
111
  className="mb-3"
112
  ),
113
- html.Div(id="upload-file-link", className="text-muted mb-2"),
 
 
 
114
  dbc.Button("Generate Slides", id='generate-button', color="primary", className="w-100 mt-2", size="lg"),
115
- html.Div(
116
- dbc.Card([
117
- dbc.CardHeader("Generated PowerPoint", className="bg-light"),
118
- dbc.CardBody(
119
- id="pptx-file-link",
120
- className="text-muted mb-2"
121
- )
122
- ], className="mt-3 shadow-sm"),
123
- style={'width': '100%'}
124
- ),
125
  html.Div(id="slide-generation-list-container", className="mt-3"),
126
  html.Div([
127
  html.Div("Status / Log:", className="fw-bold mb-1"),
@@ -286,47 +277,74 @@ def store_session_id(n_intervals):
286
  return session_id
287
 
288
  @app.callback(
289
- Output('upload-file-link', 'children'),
290
- Output('pptx-file-link', 'children'),
291
  Output('slides-content', 'value'),
292
  Output('log-output', 'children'),
293
  Output('slide-generation-list-container', 'children'),
294
  Output('current-slide-index', 'data'),
 
295
  Input('upload-file', 'contents'),
296
  Input('generate-button', 'n_clicks'),
297
  Input('generate-ppt-button', 'n_clicks'),
298
  Input('slide-generation-list', 'value'),
 
 
299
  State('upload-file', 'filename'),
300
  State('document-text', 'value'),
301
  State('slides-content', 'value'),
302
  State('session-id', 'data'),
303
  State('current-slide-index', 'data'),
 
 
304
  prevent_initial_call=True
305
  )
306
- def unified_callback(upload_contents, gen_btn, gen_ppt_btn, selected_slide_idx, upload_filename, document_text, slides_content, session_id, current_slide_idx):
 
 
 
 
307
  ctx = callback_context
308
  if not ctx.triggered or not session_id:
309
  raise PreventUpdate
310
  button_id = ctx.triggered[0]['prop_id'].split('.')[0]
311
  session = get_or_create_session_data(session_id)
312
- upload_file_link = ""
313
  slides_md = slides_content
314
  log_str = ""
315
  slide_list = session.get('slide_markdown_names', [])
316
  slide_md_list = session.get('slide_markdowns', [])
317
  slide_dropdown = None
318
  slide_idx = current_slide_idx if current_slide_idx is not None else None
 
319
 
 
320
  with session['lock']:
321
- if session['uploaded_file'] and session['uploaded_filename']:
322
- upload_file_link = html.A(
323
- session['uploaded_filename'],
324
- id='uploaded-file-download-link-visible',
325
- href="#",
326
- n_clicks=0,
327
- style={'display': 'block', 'cursor': 'pointer', 'color': '#116F70', 'textDecoration': 'underline'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  )
329
- log_str = '\n'.join(session['log_messages'][:100])
 
 
330
 
331
  dropdown_options = [
332
  {'label': slide_name, 'value': idx}
@@ -344,52 +362,41 @@ def unified_callback(upload_contents, gen_btn, gen_ppt_btn, selected_slide_idx,
344
  )
345
  ]) if slide_list else html.Div("No slides generated yet.", className="text-muted")
346
 
347
- pptx_file_link = ""
348
- if session.get('generated_pptx'):
349
- pptx_file_link = html.A(
350
- "presentation.pptx",
351
- id='pptx-file-download-link-visible',
352
- href="#",
353
- n_clicks=0,
354
- style={'display': 'block', 'cursor': 'pointer', 'color': '#116F70', 'textDecoration': 'underline'}
355
- )
356
 
 
357
  if button_id == 'upload-file':
358
  if upload_contents and upload_filename:
359
  try:
360
  header, content_string = upload_contents.split(',')
361
  file_bytes = base64.b64decode(content_string)
362
- session = get_or_create_session_data(session_id)
363
  with session['lock']:
364
- session['uploaded_file'] = file_bytes
365
- session['uploaded_filename'] = upload_filename
366
  session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Uploaded file '{upload_filename}' saved for session.")
367
- upload_file_link = html.A(
368
- upload_filename,
369
- id='uploaded-file-download-link-visible',
370
- href="#",
371
- n_clicks=0,
372
- style={'display': 'block', 'cursor': 'pointer', 'color': '#116F70', 'textDecoration': 'underline'}
373
- )
374
  log_str = '\n'.join(session['log_messages'][:100])
375
  except Exception as e:
376
  with session['lock']:
377
  session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Error uploading file: {str(e)}")
378
  log_str = '\n'.join(session['log_messages'][:100])
379
- return upload_file_link, pptx_file_link, slides_content, log_str, slide_dropdown, slide_idx
 
 
380
  elif button_id == 'generate-button':
381
  decoded_file_text = None
382
- if session.get('uploaded_file'):
 
 
383
  try:
384
- decoded_file_text = session['uploaded_file'].decode('utf-8')
385
  except UnicodeDecodeError:
386
  try:
387
- decoded_file_text = session['uploaded_file'].decode('latin-1')
388
  except Exception:
389
  with session['lock']:
390
  session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Could not decode uploaded file as text. Please upload a UTF-8 or Latin-1 encoded text file.")
391
  log_str = '\n'.join(session['log_messages'][:100])
392
- return upload_file_link, pptx_file_link, "Could not decode uploaded file as text. Please upload a UTF-8 or Latin-1 encoded text file.", log_str, slide_dropdown, slide_idx
393
  combined_text = ""
394
  if decoded_file_text and document_text:
395
  combined_text = decoded_file_text.strip() + "\n\n" + document_text.strip()
@@ -401,7 +408,7 @@ def unified_callback(upload_contents, gen_btn, gen_ppt_btn, selected_slide_idx,
401
  with session['lock']:
402
  session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Please upload a file or enter text.")
403
  log_str = '\n'.join(session['log_messages'][:100])
404
- return upload_file_link, pptx_file_link, "Please upload a file or enter text.", log_str, slide_dropdown, slide_idx
405
  try:
406
  add_log("Starting slide generation...", session_id)
407
  slides_markdown = process_document(combined_text, session_id)
@@ -414,27 +421,14 @@ def unified_callback(upload_contents, gen_btn, gen_ppt_btn, selected_slide_idx,
414
  session['current_slide_md'] = slides_markdown
415
  slide_idx = len(session['slide_markdowns']) - 1
416
  log_str = '\n'.join(session['log_messages'][:100])
417
- dropdown_options = [
418
- {'label': n, 'value': idx}
419
- for idx, n in enumerate(session['slide_markdown_names'])
420
- ]
421
- slide_dropdown = html.Div([
422
- dbc.Label("Generated Slide Sets", className="fw-bold"),
423
- dcc.Dropdown(
424
- id='slide-generation-list',
425
- options=dropdown_options,
426
- value=slide_idx,
427
- placeholder="No slides generated yet",
428
- clearable=False,
429
- style={'marginBottom': '10px'}
430
- )
431
- ])
432
- return upload_file_link, pptx_file_link, slides_md, log_str, slide_dropdown, slide_idx
433
  except Exception as e:
434
  add_log(f"An error occurred during slide generation: {str(e)}", session_id)
435
  with session['lock']:
436
  log_str = '\n'.join(session['log_messages'][:100])
437
- return upload_file_link, pptx_file_link, f"An error occurred: {str(e)}", log_str, slide_dropdown, slide_idx
 
 
 
438
  elif button_id == 'generate-ppt-button':
439
  if not slides_content or not session_id:
440
  raise PreventUpdate
@@ -442,22 +436,20 @@ def unified_callback(upload_contents, gen_btn, gen_ppt_btn, selected_slide_idx,
442
  add_log("Starting PowerPoint generation...", session_id)
443
  pptx_bytes = markdown_to_pptx(slides_content, session_id)
444
  with session['lock']:
445
- session['generated_pptx'] = pptx_bytes
446
- pptx_file_link = html.A(
447
- "presentation.pptx",
448
- id='pptx-file-download-link-visible',
449
- href="#",
450
- n_clicks=0,
451
- style={'display': 'block', 'cursor': 'pointer', 'color': '#116F70', 'textDecoration': 'underline'}
452
- )
453
  add_log("PowerPoint generation completed.", session_id)
454
  log_str = '\n'.join(session['log_messages'][:100])
455
- return upload_file_link, pptx_file_link, slides_content, log_str, slide_dropdown, slide_idx
456
  except Exception as e:
457
  add_log(f"An error occurred during PowerPoint generation: {str(e)}", session_id)
458
  with session['lock']:
459
  log_str = '\n'.join(session['log_messages'][:100])
460
- return upload_file_link, pptx_file_link, slides_content, log_str, slide_dropdown, slide_idx
 
 
 
461
  elif button_id == 'slide-generation-list':
462
  try:
463
  if selected_slide_idx is not None and 0 <= selected_slide_idx < len(slide_md_list):
@@ -465,34 +457,39 @@ def unified_callback(upload_contents, gen_btn, gen_ppt_btn, selected_slide_idx,
465
  slide_idx = selected_slide_idx
466
  else:
467
  slides_md = ""
468
- return upload_file_link, pptx_file_link, slides_md, log_str, slide_dropdown, slide_idx
469
  except Exception as e:
470
  add_log(f"An error occurred while loading slide set: {str(e)}", session_id)
471
  with session['lock']:
472
  log_str = '\n'.join(session['log_messages'][:100])
473
- return upload_file_link, pptx_file_link, slides_content, log_str, slide_dropdown, slide_idx
474
- else:
475
- raise PreventUpdate
476
 
477
- @app.callback(
478
- Output("download-dynamic", "data"),
479
- Input('uploaded-file-download-link-visible', 'n_clicks'),
480
- Input('pptx-file-download-link-visible', 'n_clicks'),
481
- State('session-id', 'data'),
482
- prevent_initial_call=True
483
- )
484
- def download_dynamic(uploaded_file_nclicks, pptx_nclicks, session_id):
485
- ctx = callback_context
486
- if not ctx.triggered or not session_id:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  raise PreventUpdate
488
- button_id = ctx.triggered[0]['prop_id'].split('.')[0]
489
- session = get_or_create_session_data(session_id)
490
- with session['lock']:
491
- if button_id == 'uploaded-file-download-link-visible' and session.get('uploaded_file') and session.get('uploaded_filename'):
492
- return dcc.send_bytes(session['uploaded_file'], session['uploaded_filename'])
493
- if button_id == 'pptx-file-download-link-visible' and session.get('generated_pptx'):
494
- return dcc.send_bytes(session['generated_pptx'], "presentation.pptx")
495
- raise PreventUpdate
496
 
497
  if __name__ == '__main__':
498
  print("Starting the Dash application...")
 
47
  SESSION_DATA[session_id] = {
48
  'log_messages': [],
49
  'generated_pptx': None,
50
+ 'uploaded_files': [], # List of dicts: {'name':..., 'content':..., 'type':...}
 
51
  'lock': threading.Lock(),
52
  'slide_markdowns': [],
53
  'slide_markdown_names': [],
54
  'current_slide_md': "",
55
+ 'files_box': [], # List of {'name':..., 'content':..., 'type':...}
56
  }
57
  return SESSION_DATA[session_id]
58
 
59
  app.layout = html.Div([
60
  dcc.Store(id='session-id', storage_type='local'),
61
  dcc.Store(id='current-slide-index', storage_type='session'),
62
+ dcc.Store(id='files-box-store', storage_type='session'),
63
  html.Script('''
64
  (function() {
65
  function uuidv4() {
 
80
  window.dash_clientside.sessionid = sessionid;
81
  })();
82
  '''),
 
 
 
83
  dcc.Download(id="download-dynamic"),
84
  dbc.Container([
85
  dbc.Row([
 
108
  },
109
  className="mb-3"
110
  ),
111
+ dbc.Card([
112
+ dbc.CardHeader("All Uploaded/Generated Files", className="bg-light"),
113
+ dbc.CardBody(id="files-box", className="mb-2"),
114
+ ], className="mb-3 shadow-sm"),
115
  dbc.Button("Generate Slides", id='generate-button', color="primary", className="w-100 mt-2", size="lg"),
 
 
 
 
 
 
 
 
 
 
116
  html.Div(id="slide-generation-list-container", className="mt-3"),
117
  html.Div([
118
  html.Div("Status / Log:", className="fw-bold mb-1"),
 
277
  return session_id
278
 
279
  @app.callback(
280
+ Output('files-box', 'children'),
 
281
  Output('slides-content', 'value'),
282
  Output('log-output', 'children'),
283
  Output('slide-generation-list-container', 'children'),
284
  Output('current-slide-index', 'data'),
285
+ Output("download-dynamic", "data"),
286
  Input('upload-file', 'contents'),
287
  Input('generate-button', 'n_clicks'),
288
  Input('generate-ppt-button', 'n_clicks'),
289
  Input('slide-generation-list', 'value'),
290
+ Input({'type': 'file-download', 'index': dash.ALL}, 'n_clicks'),
291
+ Input({'type': 'file-delete', 'index': dash.ALL}, 'n_clicks'),
292
  State('upload-file', 'filename'),
293
  State('document-text', 'value'),
294
  State('slides-content', 'value'),
295
  State('session-id', 'data'),
296
  State('current-slide-index', 'data'),
297
+ State({'type': 'file-download', 'index': dash.ALL}, 'id'),
298
+ State({'type': 'file-delete', 'index': dash.ALL}, 'id'),
299
  prevent_initial_call=True
300
  )
301
+ def unified_callback(
302
+ upload_contents, gen_btn, gen_ppt_btn, selected_slide_idx, file_downloads, file_deletes,
303
+ upload_filename, document_text, slides_content, session_id, current_slide_idx,
304
+ download_ids, delete_ids
305
+ ):
306
  ctx = callback_context
307
  if not ctx.triggered or not session_id:
308
  raise PreventUpdate
309
  button_id = ctx.triggered[0]['prop_id'].split('.')[0]
310
  session = get_or_create_session_data(session_id)
 
311
  slides_md = slides_content
312
  log_str = ""
313
  slide_list = session.get('slide_markdown_names', [])
314
  slide_md_list = session.get('slide_markdowns', [])
315
  slide_dropdown = None
316
  slide_idx = current_slide_idx if current_slide_idx is not None else None
317
+ download_data = None
318
 
319
+ # Ensure files_box is initialized
320
  with session['lock']:
321
+ if 'files_box' not in session:
322
+ session['files_box'] = []
323
+
324
+ def rebuild_files_box(session):
325
+ files_box = []
326
+ for idx, f in enumerate(session['files_box']):
327
+ files_box.append(
328
+ html.Div([
329
+ html.A(
330
+ f['name'],
331
+ href="#",
332
+ id={'type': 'file-download', 'index': idx},
333
+ n_clicks=0,
334
+ style={'marginRight': '16px', 'color': '#116F70', 'textDecoration': 'underline'}
335
+ ),
336
+ dbc.Button(
337
+ "Delete",
338
+ id={'type': 'file-delete', 'index': idx},
339
+ size="sm",
340
+ color="secondary",
341
+ style={'marginLeft': '4px', 'marginBottom': '2px'}
342
+ )
343
+ ], style={'display': 'flex', 'alignItems': 'center', 'marginBottom': '8px'}, id={'type': 'file-row', 'index': idx})
344
  )
345
+ if not files_box:
346
+ return html.Div("No files uploaded or generated yet.", className="text-muted")
347
+ return files_box
348
 
349
  dropdown_options = [
350
  {'label': slide_name, 'value': idx}
 
362
  )
363
  ]) if slide_list else html.Div("No slides generated yet.", className="text-muted")
364
 
365
+ with session['lock']:
366
+ log_str = '\n'.join(session['log_messages'][:100])
 
 
 
 
 
 
 
367
 
368
+ # Handle file upload
369
  if button_id == 'upload-file':
370
  if upload_contents and upload_filename:
371
  try:
372
  header, content_string = upload_contents.split(',')
373
  file_bytes = base64.b64decode(content_string)
 
374
  with session['lock']:
375
+ session['files_box'].append({'name': upload_filename, 'content': file_bytes, 'type': 'uploaded'})
 
376
  session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Uploaded file '{upload_filename}' saved for session.")
 
 
 
 
 
 
 
377
  log_str = '\n'.join(session['log_messages'][:100])
378
  except Exception as e:
379
  with session['lock']:
380
  session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Error uploading file: {str(e)}")
381
  log_str = '\n'.join(session['log_messages'][:100])
382
+ return rebuild_files_box(session), slides_content, log_str, slide_dropdown, slide_idx, None
383
+
384
+ # Handle slide generation
385
  elif button_id == 'generate-button':
386
  decoded_file_text = None
387
+ # Use first uploaded file for text extraction if present
388
+ uploaded_files = [f for f in session['files_box'] if f['type'] == 'uploaded']
389
+ if uploaded_files:
390
  try:
391
+ decoded_file_text = uploaded_files[0]['content'].decode('utf-8')
392
  except UnicodeDecodeError:
393
  try:
394
+ decoded_file_text = uploaded_files[0]['content'].decode('latin-1')
395
  except Exception:
396
  with session['lock']:
397
  session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Could not decode uploaded file as text. Please upload a UTF-8 or Latin-1 encoded text file.")
398
  log_str = '\n'.join(session['log_messages'][:100])
399
+ return rebuild_files_box(session), "Could not decode uploaded file as text. Please upload a UTF-8 or Latin-1 encoded text file.", log_str, slide_dropdown, slide_idx, None
400
  combined_text = ""
401
  if decoded_file_text and document_text:
402
  combined_text = decoded_file_text.strip() + "\n\n" + document_text.strip()
 
408
  with session['lock']:
409
  session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Please upload a file or enter text.")
410
  log_str = '\n'.join(session['log_messages'][:100])
411
+ return rebuild_files_box(session), "Please upload a file or enter text.", log_str, slide_dropdown, slide_idx, None
412
  try:
413
  add_log("Starting slide generation...", session_id)
414
  slides_markdown = process_document(combined_text, session_id)
 
421
  session['current_slide_md'] = slides_markdown
422
  slide_idx = len(session['slide_markdowns']) - 1
423
  log_str = '\n'.join(session['log_messages'][:100])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  except Exception as e:
425
  add_log(f"An error occurred during slide generation: {str(e)}", session_id)
426
  with session['lock']:
427
  log_str = '\n'.join(session['log_messages'][:100])
428
+ return rebuild_files_box(session), f"An error occurred: {str(e)}", log_str, slide_dropdown, slide_idx, None
429
+ return rebuild_files_box(session), slides_md, log_str, slide_dropdown, slide_idx, None
430
+
431
+ # Handle PowerPoint generation
432
  elif button_id == 'generate-ppt-button':
433
  if not slides_content or not session_id:
434
  raise PreventUpdate
 
436
  add_log("Starting PowerPoint generation...", session_id)
437
  pptx_bytes = markdown_to_pptx(slides_content, session_id)
438
  with session['lock']:
439
+ # Remove previous generated pptx files for this session
440
+ session['files_box'] = [f for f in session['files_box'] if f['type'] != 'generated_pptx']
441
+ # Add new pptx file
442
+ session['files_box'].append({'name': 'presentation.pptx', 'content': pptx_bytes, 'type': 'generated_pptx'})
 
 
 
 
443
  add_log("PowerPoint generation completed.", session_id)
444
  log_str = '\n'.join(session['log_messages'][:100])
 
445
  except Exception as e:
446
  add_log(f"An error occurred during PowerPoint generation: {str(e)}", session_id)
447
  with session['lock']:
448
  log_str = '\n'.join(session['log_messages'][:100])
449
+ return rebuild_files_box(session), slides_content, log_str, slide_dropdown, slide_idx, None
450
+ return rebuild_files_box(session), slides_content, log_str, slide_dropdown, slide_idx, None
451
+
452
+ # Handle slide set dropdown selection
453
  elif button_id == 'slide-generation-list':
454
  try:
455
  if selected_slide_idx is not None and 0 <= selected_slide_idx < len(slide_md_list):
 
457
  slide_idx = selected_slide_idx
458
  else:
459
  slides_md = ""
 
460
  except Exception as e:
461
  add_log(f"An error occurred while loading slide set: {str(e)}", session_id)
462
  with session['lock']:
463
  log_str = '\n'.join(session['log_messages'][:100])
464
+ return rebuild_files_box(session), slides_content, log_str, slide_dropdown, slide_idx, None
465
+ return rebuild_files_box(session), slides_md, log_str, slide_dropdown, slide_idx, None
 
466
 
467
+ # Handle file download and delete
468
+ elif button_id.startswith("{"):
469
+ # Determine which pattern (download or delete) triggered
470
+ triggered_id = eval(button_id)
471
+ if triggered_id['type'] == 'file-download':
472
+ idx = triggered_id['index']
473
+ with session['lock']:
474
+ if 0 <= idx < len(session['files_box']):
475
+ file_bytes = session['files_box'][idx]['content']
476
+ file_name = session['files_box'][idx]['name']
477
+ # No other output changes, just return download
478
+ return rebuild_files_box(session), slides_content, log_str, slide_dropdown, slide_idx, dcc.send_bytes(file_bytes, file_name)
479
+ elif triggered_id['type'] == 'file-delete':
480
+ idx = triggered_id['index']
481
+ with session['lock']:
482
+ if 0 <= idx < len(session['files_box']):
483
+ deleted_name = session['files_box'][idx]['name']
484
+ del session['files_box'][idx]
485
+ session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - File '{deleted_name}' deleted.")
486
+ log_str = '\n'.join(session['log_messages'][:100])
487
+ return rebuild_files_box(session), slides_content, log_str, slide_dropdown, slide_idx, None
488
+ else:
489
+ raise PreventUpdate
490
+
491
+ else:
492
  raise PreventUpdate
 
 
 
 
 
 
 
 
493
 
494
  if __name__ == '__main__':
495
  print("Starting the Dash application...")