bluenevus commited on
Commit
234506b
·
1 Parent(s): 7eacc0a

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +185 -282
app.py CHANGED
@@ -13,6 +13,8 @@ import logging
13
  import threading
14
  import re
15
  import google.generativeai as genai
 
 
16
 
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
18
 
@@ -20,10 +22,49 @@ GEMINI_KEY = os.environ.get("GEMINI_KEY", "")
20
  genai.configure(api_key=GEMINI_KEY)
21
  GEMINI_MODEL = "gemini-2.5-pro-preview-03-25"
22
 
23
- app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP], suppress_callback_exceptions=True)
24
-
25
- uploaded_files = {}
26
- uploaded_doc_contents = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  spreadsheet_types = ["Shred", "Pink Review", "Red Review", "Gold Review", "Virtual Board", "LOE"]
29
  narrative_types = ["Pink", "Red", "Gold"]
@@ -333,6 +374,8 @@ def get_right_col_content(selected_type, store_data):
333
  controls.append(html.Div(id='document-preview', className="border p-3 mb-3"))
334
  return dbc.Card(dbc.CardBody(controls))
335
 
 
 
336
  app.layout = dbc.Container([
337
  dcc.Store(id='selected-doc-type', data="Shred"),
338
  dcc.Store(id='store-shred'),
@@ -427,265 +470,145 @@ def update_selected_doc_type(n_clicks_list, btn_ids):
427
  @app.callback(
428
  Output('right-col-content', 'children'),
429
  Input('selected-doc-type', 'data'),
430
- State('store-shred', 'data'),
431
- State('store-pink', 'data'),
432
- State('store-pink-review', 'data'),
433
- State('store-red', 'data'),
434
- State('store-red-review', 'data'),
435
- State('store-gold', 'data'),
436
- State('store-gold-review', 'data'),
437
- State('store-loe', 'data'),
438
- State('store-virtual-board', 'data'),
439
  )
440
- def update_right_col(selected_type, shred, pink, pink_review, red, red_review, gold, gold_review, loe, virtual_board):
441
- store_data = {
442
- "shred": shred,
443
- "pink": pink,
444
- "pink_review": pink_review,
445
- "red": red,
446
- "red_review": red_review,
447
- "gold": gold,
448
- "gold_review": gold_review,
449
- "loe": loe,
450
- "virtual_board": virtual_board
451
- }
452
  return get_right_col_content(selected_type, store_data)
453
 
454
  @app.callback(
455
  [
456
  Output('store-shred', 'data'),
 
 
 
 
 
 
 
 
457
  Output('file-list', 'children'),
458
  Output('uploaded-doc-name-shred', 'children'),
 
 
 
 
 
 
 
 
459
  Output('progress-dot-store', 'data')
460
  ],
461
  [
462
- Input({'type': 'upload-doc-type', 'subtype': 'shred', 'index': 'Shred'}, 'contents')
463
  ],
464
  [
465
- State({'type': 'upload-doc-type', 'subtype': 'shred', 'index': 'Shred'}, 'filename'),
466
  State('store-shred', 'data'),
467
- State('file-list', 'children')
 
 
 
 
 
 
 
468
  ],
469
  prevent_initial_call=True
470
  )
471
- def handle_shred_upload(contents, filenames, current_shred, file_list):
472
- if not contents or not filenames:
473
- raise dash.exceptions.PreventUpdate
474
- if isinstance(contents, str):
475
- contents = [contents]
476
- filenames = [filenames]
477
- file_previews = []
478
- latest_text = current_shred
479
- for content, name in zip(contents, filenames):
480
- file_text = process_document(content, name)
481
- uploaded_files[name] = file_text
482
- latest_text = file_text
483
- file_previews.append(
484
- dbc.Row([
485
- dbc.Col(
486
- html.Button('×', id={'type': 'remove-file', 'index': name}, style={'marginRight': '5px', 'fontSize': '10px'}),
487
- width=1
488
- ),
489
- dbc.Col(
490
- html.Span(name, style={'wordBreak': 'break-all'}),
491
- width=11
492
- )
493
- ], id={'type': 'file-row', 'index': name}, align="center", className="mb-1")
494
- )
495
- logging.info("Shred document uploaded and stored.")
496
- return latest_text, file_previews, filenames[-1], "progress"
497
-
498
- @app.callback(
499
- Output('store-pink', 'data'),
500
- Output('uploaded-doc-name-pink', 'children'),
501
- Output('progress-dot-store', 'data'),
502
- Input({'type': 'upload-doc-type', 'subtype': 'pink', 'index': ALL}, 'contents'),
503
- State({'type': 'upload-doc-type', 'subtype': 'pink', 'index': ALL}, 'filename'),
504
- prevent_initial_call=True
505
- )
506
- def handle_pink_upload(contents_list, filenames_list):
507
- if not contents_list or not filenames_list:
508
- return dash.no_update, dash.no_update, dash.no_update
509
- for contents, filenames in zip(contents_list, filenames_list):
510
- if contents and filenames:
511
- if isinstance(contents, str):
512
- contents = [contents]
513
- filenames = [filenames]
514
- for content, name in zip(contents, filenames):
515
- file_text = process_document(content, name)
516
- return file_text, name, "progress"
517
- return dash.no_update, dash.no_update, dash.no_update
518
-
519
- @app.callback(
520
- Output('store-pink-review', 'data'),
521
- Output('uploaded-doc-name-pink_review', 'children'),
522
- Output('progress-dot-store', 'data'),
523
- Input({'type': 'upload-doc-type', 'subtype': 'pink_review', 'index': ALL}, 'contents'),
524
- State({'type': 'upload-doc-type', 'subtype': 'pink_review', 'index': ALL}, 'filename'),
525
- prevent_initial_call=True
526
- )
527
- def handle_pink_review_upload(contents_list, filenames_list):
528
- if not contents_list or not filenames_list:
529
- return dash.no_update, dash.no_update, dash.no_update
530
- for contents, filenames in zip(contents_list, filenames_list):
531
- if contents and filenames:
532
- if isinstance(contents, str):
533
- contents = [contents]
534
- filenames = [filenames]
535
- for content, name in zip(contents, filenames):
536
- file_text = process_document(content, name)
537
- return file_text, name, "progress"
538
- return dash.no_update, dash.no_update, dash.no_update
539
-
540
- @app.callback(
541
- Output('store-red', 'data'),
542
- Output('uploaded-doc-name-red', 'children'),
543
- Output('progress-dot-store', 'data'),
544
- Input({'type': 'upload-doc-type', 'subtype': 'red', 'index': ALL}, 'contents'),
545
- State({'type': 'upload-doc-type', 'subtype': 'red', 'index': ALL}, 'filename'),
546
- prevent_initial_call=True
547
- )
548
- def handle_red_upload(contents_list, filenames_list):
549
- if not contents_list or not filenames_list:
550
- return dash.no_update, dash.no_update, dash.no_update
551
- for contents, filenames in zip(contents_list, filenames_list):
552
- if contents and filenames:
553
- if isinstance(contents, str):
554
- contents = [contents]
555
- filenames = [filenames]
556
- for content, name in zip(contents, filenames):
557
- file_text = process_document(content, name)
558
- return file_text, name, "progress"
559
- return dash.no_update, dash.no_update, dash.no_update
560
-
561
- @app.callback(
562
- Output('store-red-review', 'data'),
563
- Output('uploaded-doc-name-red_review', 'children'),
564
- Output('progress-dot-store', 'data'),
565
- Input({'type': 'upload-doc-type', 'subtype': 'red_review', 'index': ALL}, 'contents'),
566
- State({'type': 'upload-doc-type', 'subtype': 'red_review', 'index': ALL}, 'filename'),
567
- prevent_initial_call=True
568
- )
569
- def handle_red_review_upload(contents_list, filenames_list):
570
- if not contents_list or not filenames_list:
571
- return dash.no_update, dash.no_update, dash.no_update
572
- for contents, filenames in zip(contents_list, filenames_list):
573
- if contents and filenames:
574
- if isinstance(contents, str):
575
- contents = [contents]
576
- filenames = [filenames]
577
- for content, name in zip(contents, filenames):
578
- file_text = process_document(content, name)
579
- return file_text, name, "progress"
580
- return dash.no_update, dash.no_update, dash.no_update
581
-
582
- @app.callback(
583
- Output('store-gold', 'data'),
584
- Output('uploaded-doc-name-gold', 'children'),
585
- Output('progress-dot-store', 'data'),
586
- Input({'type': 'upload-doc-type', 'subtype': 'gold', 'index': ALL}, 'contents'),
587
- State({'type': 'upload-doc-type', 'subtype': 'gold', 'index': ALL}, 'filename'),
588
- prevent_initial_call=True
589
- )
590
- def handle_gold_upload(contents_list, filenames_list):
591
- if not contents_list or not filenames_list:
592
- return dash.no_update, dash.no_update, dash.no_update
593
- for contents, filenames in zip(contents_list, filenames_list):
594
- if contents and filenames:
595
- if isinstance(contents, str):
596
- contents = [contents]
597
- filenames = [filenames]
598
- for content, name in zip(contents, filenames):
599
- file_text = process_document(content, name)
600
- return file_text, name, "progress"
601
- return dash.no_update, dash.no_update, dash.no_update
602
 
603
- @app.callback(
604
- Output('store-gold-review', 'data'),
605
- Output('uploaded-doc-name-gold_review', 'children'),
606
- Output('progress-dot-store', 'data'),
607
- Input({'type': 'upload-doc-type', 'subtype': 'gold_review', 'index': ALL}, 'contents'),
608
- State({'type': 'upload-doc-type', 'subtype': 'gold_review', 'index': ALL}, 'filename'),
609
- prevent_initial_call=True
610
- )
611
- def handle_gold_review_upload(contents_list, filenames_list):
612
- if not contents_list or not filenames_list:
613
- return dash.no_update, dash.no_update, dash.no_update
614
- for contents, filenames in zip(contents_list, filenames_list):
615
- if contents and filenames:
616
- if isinstance(contents, str):
617
- contents = [contents]
618
- filenames = [filenames]
619
- for content, name in zip(contents, filenames):
620
- file_text = process_document(content, name)
621
- return file_text, name, "progress"
622
- return dash.no_update, dash.no_update, dash.no_update
623
 
624
- @app.callback(
625
- Output('store-loe', 'data'),
626
- Output('uploaded-doc-name-loe', 'children'),
627
- Output('progress-dot-store', 'data'),
628
- Input({'type': 'upload-doc-type', 'subtype': 'loe', 'index': ALL}, 'contents'),
629
- State({'type': 'upload-doc-type', 'subtype': 'loe', 'index': ALL}, 'filename'),
630
- prevent_initial_call=True
631
- )
632
- def handle_loe_upload(contents_list, filenames_list):
633
- if not contents_list or not filenames_list:
634
- return dash.no_update, dash.no_update, dash.no_update
635
- for contents, filenames in zip(contents_list, filenames_list):
636
  if contents and filenames:
 
 
 
637
  if isinstance(contents, str):
638
  contents = [contents]
639
  filenames = [filenames]
640
- for content, name in zip(contents, filenames):
641
- file_text = process_document(content, name)
642
- return file_text, name, "progress"
643
- return dash.no_update, dash.no_update, dash.no_update
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
 
645
- @app.callback(
646
- Output('store-virtual-board', 'data'),
647
- Output('uploaded-doc-name-virtual_board', 'children'),
648
- Output('progress-dot-store', 'data'),
649
- Input({'type': 'upload-doc-type', 'subtype': 'virtual_board', 'index': ALL}, 'contents'),
650
- State({'type': 'upload-doc-type', 'subtype': 'virtual_board', 'index': ALL}, 'filename'),
651
- prevent_initial_call=True
652
- )
653
- def handle_virtual_board_upload(contents_list, filenames_list):
654
- if not contents_list or not filenames_list:
655
- return dash.no_update, dash.no_update, dash.no_update
656
- for contents, filenames in zip(contents_list, filenames_list):
657
- if contents and filenames:
658
- if isinstance(contents, str):
659
- contents = [contents]
660
- filenames = [filenames]
661
- for content, name in zip(contents, filenames):
662
- file_text = process_document(content, name)
663
- return file_text, name, "progress"
664
- return dash.no_update, dash.no_update, dash.no_update
665
 
666
  @app.callback(
667
- Output('document-preview', 'children'),
668
- Output('progress-dot-store', 'data'),
 
 
 
669
  [
670
  Input({'type': 'btn-generate-doc', 'index': ALL}, 'n_clicks'),
671
- Input('selected-doc-type', 'data'),
672
- Input('store-shred', 'data'),
673
- Input('store-pink', 'data'),
674
- Input('store-pink-review', 'data'),
675
- Input('store-red', 'data'),
676
- Input('store-red-review', 'data'),
677
- Input('store-gold', 'data'),
678
- Input('store-gold-review', 'data'),
679
- Input('store-loe', 'data'),
680
- Input('store-virtual-board', 'data'),
 
 
681
  State('store-generated-doc', 'data')
682
  ],
683
  prevent_initial_call=True
684
  )
685
- def preview_or_generate_doc(n_clicks_list, selected_type, shred, pink, pink_review, red, red_review, gold, gold_review, loe, virtual_board, prev_generated):
 
686
  ctx = callback_context
687
  trigger = ctx.triggered[0]['prop_id'] if ctx.triggered else ""
688
- logging.info(f"Document preview trigger: {trigger}")
 
 
689
  if any(n_clicks_list):
690
  logging.info(f"Generating document for type: {selected_type}")
691
  inputs = {
@@ -721,64 +644,43 @@ def preview_or_generate_doc(n_clicks_list, selected_type, shred, pink, pink_revi
721
  t.start()
722
  t.join(timeout=60)
723
  generated_doc = result_holder.get('result', 'Error: No document generated.')
724
- app.server.config['last_generated_doc'] = generated_doc
725
  if selected_type in spreadsheet_types:
726
  preview = markdown_table_preview(generated_doc)
727
  else:
728
  preview = markdown_narrative_preview(generated_doc)
729
  logging.info("Document preview updated.")
730
- return preview, "progress"
731
  else:
732
- if selected_type == "Shred" and shred:
733
- return markdown_table_preview(shred), dash.no_update
734
- elif selected_type in spreadsheet_types:
735
- doc_store = {
736
- "Pink Review": pink_review,
737
- "Red Review": red_review,
738
- "Gold Review": gold_review,
739
- "Virtual Board": virtual_board,
740
- "LOE": loe
741
- }
742
- doc = doc_store.get(selected_type, "")
743
- return markdown_table_preview(doc), dash.no_update
744
- elif selected_type in narrative_types:
745
- doc_store = {
746
- "Pink": pink,
747
- "Red": red,
748
- "Gold": gold
749
- }
750
- doc = doc_store.get(selected_type, "")
751
- return markdown_narrative_preview(doc), dash.no_update
752
- return html.Div("No document loaded."), dash.no_update
753
-
754
- @app.callback(
755
- Output('store-generated-doc', 'data'),
756
- Output('progress-dot-store', 'data'),
757
- [
758
- Input({'type': 'btn-generate-doc', 'index': ALL}, 'n_clicks'),
759
- Input('selected-doc-type', 'data'),
760
- Input('document-preview', 'children')
761
- ],
762
- [
763
- State('store-shred', 'data'),
764
- State('store-pink', 'data'),
765
- State('store-pink-review', 'data'),
766
- State('store-red', 'data'),
767
- State('store-red-review', 'data'),
768
- State('store-gold', 'data'),
769
- State('store-gold-review', 'data'),
770
- State('store-loe', 'data'),
771
- State('store-virtual-board', 'data'),
772
- State('store-generated-doc', 'data')
773
- ],
774
- prevent_initial_call=True
775
- )
776
- def update_generated_doc(n_clicks_list, selected_type, preview_content, shred, pink, pink_review, red, red_review, gold, gold_review, loe, virtual_board, prev_generated):
777
- ctx = callback_context
778
- trigger = ctx.triggered[0]['prop_id'] if ctx.triggered else ""
779
- if any(n_clicks_list):
780
- return app.server.config.get('last_generated_doc', prev_generated), "progress"
781
- return prev_generated, dash.no_update
782
 
783
  @app.callback(
784
  Output("download-document", "data"),
@@ -799,6 +701,8 @@ def update_generated_doc(n_clicks_list, selected_type, preview_content, shred, p
799
  )
800
  def download_document(n_clicks, selected_type, generated_doc, shred, pink, pink_review, red, red_review, gold, gold_review, loe, virtual_board):
801
  doc = generated_doc
 
 
802
  if not doc:
803
  if selected_type == "Shred":
804
  doc = shred
@@ -835,7 +739,6 @@ def download_document(n_clicks, selected_type, generated_doc, shred, pink, pink_
835
  Input('progress-dot-store', 'data')
836
  )
837
  def update_main_progress_dot(progress):
838
- # Dummy output to trigger dcc.Loading
839
  return ""
840
 
841
  if __name__ == '__main__':
 
13
  import threading
14
  import re
15
  import google.generativeai as genai
16
+ import sqlite3
17
+ import time
18
 
19
  logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
20
 
 
22
  genai.configure(api_key=GEMINI_KEY)
23
  GEMINI_MODEL = "gemini-2.5-pro-preview-03-25"
24
 
25
+ DB_PATH = "maiko_dash_docs.sqlite"
26
+
27
+ def init_db():
28
+ with sqlite3.connect(DB_PATH) as conn:
29
+ c = conn.cursor()
30
+ c.execute('''CREATE TABLE IF NOT EXISTS uploaded_docs
31
+ (doc_type TEXT PRIMARY KEY, doc_content TEXT, file_name TEXT, last_update TIMESTAMP)''')
32
+ c.execute('''CREATE TABLE IF NOT EXISTS generated_docs
33
+ (doc_type TEXT PRIMARY KEY, doc_content TEXT, last_update TIMESTAMP)''')
34
+ conn.commit()
35
+ init_db()
36
+
37
+ def set_uploaded_doc(doc_type, doc_content, file_name):
38
+ with sqlite3.connect(DB_PATH) as conn:
39
+ c = conn.cursor()
40
+ c.execute('''INSERT OR REPLACE INTO uploaded_docs (doc_type, doc_content, file_name, last_update)
41
+ VALUES (?, ?, ?, ?)''', (doc_type, doc_content, file_name, time.time()))
42
+ conn.commit()
43
+
44
+ def get_uploaded_doc(doc_type):
45
+ with sqlite3.connect(DB_PATH) as conn:
46
+ c = conn.cursor()
47
+ c.execute('''SELECT doc_content, file_name FROM uploaded_docs WHERE doc_type=?''', (doc_type,))
48
+ row = c.fetchone()
49
+ if row:
50
+ return row[0], row[1]
51
+ return None, None
52
+
53
+ def set_generated_doc(doc_type, doc_content):
54
+ with sqlite3.connect(DB_PATH) as conn:
55
+ c = conn.cursor()
56
+ c.execute('''INSERT OR REPLACE INTO generated_docs (doc_type, doc_content, last_update)
57
+ VALUES (?, ?, ?)''', (doc_type, doc_content, time.time()))
58
+ conn.commit()
59
+
60
+ def get_generated_doc(doc_type):
61
+ with sqlite3.connect(DB_PATH) as conn:
62
+ c = conn.cursor()
63
+ c.execute('''SELECT doc_content FROM generated_docs WHERE doc_type=?''', (doc_type,))
64
+ row = c.fetchone()
65
+ if row:
66
+ return row[0]
67
+ return None
68
 
69
  spreadsheet_types = ["Shred", "Pink Review", "Red Review", "Gold Review", "Virtual Board", "LOE"]
70
  narrative_types = ["Pink", "Red", "Gold"]
 
374
  controls.append(html.Div(id='document-preview', className="border p-3 mb-3"))
375
  return dbc.Card(dbc.CardBody(controls))
376
 
377
+ app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP], suppress_callback_exceptions=True)
378
+
379
  app.layout = dbc.Container([
380
  dcc.Store(id='selected-doc-type', data="Shred"),
381
  dcc.Store(id='store-shred'),
 
470
  @app.callback(
471
  Output('right-col-content', 'children'),
472
  Input('selected-doc-type', 'data'),
 
 
 
 
 
 
 
 
 
473
  )
474
+ def update_right_col(selected_type):
475
+ # Load all doc stores from sqlite
476
+ store_data = {}
477
+ for store_key in ["shred","pink","pink_review","red","red_review","gold","gold_review","loe","virtual_board"]:
478
+ doc_content, _ = get_uploaded_doc(store_key)
479
+ store_data[store_key] = doc_content
 
 
 
 
 
 
480
  return get_right_col_content(selected_type, store_data)
481
 
482
  @app.callback(
483
  [
484
  Output('store-shred', 'data'),
485
+ Output('store-pink', 'data'),
486
+ Output('store-pink-review', 'data'),
487
+ Output('store-red', 'data'),
488
+ Output('store-red-review', 'data'),
489
+ Output('store-gold', 'data'),
490
+ Output('store-gold-review', 'data'),
491
+ Output('store-loe', 'data'),
492
+ Output('store-virtual-board', 'data'),
493
  Output('file-list', 'children'),
494
  Output('uploaded-doc-name-shred', 'children'),
495
+ Output('uploaded-doc-name-pink', 'children'),
496
+ Output('uploaded-doc-name-pink_review', 'children'),
497
+ Output('uploaded-doc-name-red', 'children'),
498
+ Output('uploaded-doc-name-red_review', 'children'),
499
+ Output('uploaded-doc-name-gold', 'children'),
500
+ Output('uploaded-doc-name-gold_review', 'children'),
501
+ Output('uploaded-doc-name-loe', 'children'),
502
+ Output('uploaded-doc-name-virtual_board', 'children'),
503
  Output('progress-dot-store', 'data')
504
  ],
505
  [
506
+ Input({'type': 'upload-doc-type', 'subtype': ALL, 'index': ALL}, 'contents'),
507
  ],
508
  [
509
+ State({'type': 'upload-doc-type', 'subtype': ALL, 'index': ALL}, 'filename'),
510
  State('store-shred', 'data'),
511
+ State('store-pink', 'data'),
512
+ State('store-pink-review', 'data'),
513
+ State('store-red', 'data'),
514
+ State('store-red-review', 'data'),
515
+ State('store-gold', 'data'),
516
+ State('store-gold-review', 'data'),
517
+ State('store-loe', 'data'),
518
+ State('store-virtual-board', 'data')
519
  ],
520
  prevent_initial_call=True
521
  )
522
+ def handle_doc_upload(contents_lists, filenames_lists,
523
+ shred, pink, pink_review, red, red_review, gold, gold_review, loe, virtual_board):
524
+ stores = [shred, pink, pink_review, red, red_review, gold, gold_review, loe, virtual_board]
525
+ file_list = []
526
+ doc_names = [None]*9
527
+ progress = dash.no_update
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
528
 
529
+ triggered = callback_context.triggered
530
+ if not triggered or not contents_lists:
531
+ return [dash.no_update]*19 + [dash.no_update]
532
+
533
+ # Map subtype to store index and doc_name index
534
+ subtype_map = {
535
+ "shred": (0, 0),
536
+ "pink": (1, 1),
537
+ "pink_review": (2, 2),
538
+ "red": (3, 3),
539
+ "red_review": (4, 4),
540
+ "gold": (5, 5),
541
+ "gold_review": (6, 6),
542
+ "loe": (7, 7),
543
+ "virtual_board": (8, 8)
544
+ }
 
 
 
 
545
 
546
+ # Find which upload triggered
547
+ for i, (contents, filenames) in enumerate(zip(contents_lists, filenames_lists)):
 
 
 
 
 
 
 
 
 
 
548
  if contents and filenames:
549
+ id_dict = callback_context.inputs_list[0][i]['id']
550
+ subtype = id_dict['subtype']
551
+ idx, doc_name_idx = subtype_map[subtype]
552
  if isinstance(contents, str):
553
  contents = [contents]
554
  filenames = [filenames]
555
+ # Only take the last uploaded file for that slot
556
+ content = contents[-1]
557
+ name = filenames[-1]
558
+ file_text = process_document(content, name)
559
+ set_uploaded_doc(subtype, file_text, name)
560
+ stores[idx] = file_text
561
+ doc_names[doc_name_idx] = name
562
+ if subtype == "shred":
563
+ # For shred, build file list
564
+ file_list = [
565
+ dbc.Row([
566
+ dbc.Col(
567
+ html.Button('×', id={'type': 'remove-file', 'index': name}, style={'marginRight': '5px', 'fontSize': '10px'}),
568
+ width=1
569
+ ),
570
+ dbc.Col(
571
+ html.Span(name, style={'wordBreak': 'break-all'}),
572
+ width=11
573
+ )
574
+ ], id={'type': 'file-row', 'index': name}, align="center", className="mb-1")
575
+ ]
576
+ progress = "progress"
577
+ break
578
 
579
+ return stores + [file_list] + doc_names + [progress]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
 
581
  @app.callback(
582
+ [
583
+ Output('document-preview', 'children'),
584
+ Output('store-generated-doc', 'data'),
585
+ Output('progress-dot-store', 'data')
586
+ ],
587
  [
588
  Input({'type': 'btn-generate-doc', 'index': ALL}, 'n_clicks'),
589
+ Input('selected-doc-type', 'data')
590
+ ],
591
+ [
592
+ State('store-shred', 'data'),
593
+ State('store-pink', 'data'),
594
+ State('store-pink-review', 'data'),
595
+ State('store-red', 'data'),
596
+ State('store-red-review', 'data'),
597
+ State('store-gold', 'data'),
598
+ State('store-gold-review', 'data'),
599
+ State('store-loe', 'data'),
600
+ State('store-virtual-board', 'data'),
601
  State('store-generated-doc', 'data')
602
  ],
603
  prevent_initial_call=True
604
  )
605
+ def handle_preview_and_generate(n_clicks_list, selected_type,
606
+ shred, pink, pink_review, red, red_review, gold, gold_review, loe, virtual_board, prev_generated):
607
  ctx = callback_context
608
  trigger = ctx.triggered[0]['prop_id'] if ctx.triggered else ""
609
+ logging.info(f"Document preview/generate trigger: {trigger}")
610
+
611
+ # If generate button pressed
612
  if any(n_clicks_list):
613
  logging.info(f"Generating document for type: {selected_type}")
614
  inputs = {
 
644
  t.start()
645
  t.join(timeout=60)
646
  generated_doc = result_holder.get('result', 'Error: No document generated.')
647
+ set_generated_doc(selected_type, generated_doc)
648
  if selected_type in spreadsheet_types:
649
  preview = markdown_table_preview(generated_doc)
650
  else:
651
  preview = markdown_narrative_preview(generated_doc)
652
  logging.info("Document preview updated.")
653
+ return preview, generated_doc, "progress"
654
  else:
655
+ # Just preview from store or sqlite
656
+ doc = get_generated_doc(selected_type)
657
+ if not doc:
658
+ # Fallback to uploaded doc for preview
659
+ if selected_type == "Shred" and shred:
660
+ return markdown_table_preview(shred), dash.no_update, dash.no_update
661
+ elif selected_type in spreadsheet_types:
662
+ doc_store = {
663
+ "Pink Review": pink_review,
664
+ "Red Review": red_review,
665
+ "Gold Review": gold_review,
666
+ "Virtual Board": virtual_board,
667
+ "LOE": loe
668
+ }
669
+ doc = doc_store.get(selected_type, "")
670
+ return markdown_table_preview(doc), dash.no_update, dash.no_update
671
+ elif selected_type in narrative_types:
672
+ doc_store = {
673
+ "Pink": pink,
674
+ "Red": red,
675
+ "Gold": gold
676
+ }
677
+ doc = doc_store.get(selected_type, "")
678
+ return markdown_narrative_preview(doc), dash.no_update, dash.no_update
679
+ return html.Div("No document loaded."), dash.no_update, dash.no_update
680
+ if selected_type in spreadsheet_types:
681
+ return markdown_table_preview(doc), doc, dash.no_update
682
+ else:
683
+ return markdown_narrative_preview(doc), doc, dash.no_update
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
 
685
  @app.callback(
686
  Output("download-document", "data"),
 
701
  )
702
  def download_document(n_clicks, selected_type, generated_doc, shred, pink, pink_review, red, red_review, gold, gold_review, loe, virtual_board):
703
  doc = generated_doc
704
+ if not doc:
705
+ doc = get_generated_doc(selected_type)
706
  if not doc:
707
  if selected_type == "Shred":
708
  doc = shred
 
739
  Input('progress-dot-store', 'data')
740
  )
741
  def update_main_progress_dot(progress):
 
742
  return ""
743
 
744
  if __name__ == '__main__':