bluenevus commited on
Commit
bf9a234
·
1 Parent(s): 78df77f

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +29 -601
app.py CHANGED
@@ -3,19 +3,15 @@ import io
3
  import os
4
  import pandas as pd
5
  from docx import Document
6
- from io import BytesIO, StringIO
7
  import dash
8
  import dash_bootstrap_components as dbc
9
- from dash import html, dcc, Input, Output, State, callback_context, MATCH, ALL
10
  from dash.dash_table import DataTable
11
- from docx.shared import Pt
12
- from docx.enum.style import WD_STYLE_TYPE
13
  from PyPDF2 import PdfReader
14
  import logging
15
  import threading
16
- import re
17
- import markdown
18
- from bs4 import BeautifulSoup
19
  import google.generativeai as genai
20
 
21
  logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
@@ -98,6 +94,8 @@ def markdown_table_to_df(md_table):
98
  return df
99
 
100
  def markdown_table_preview(md_text):
 
 
101
  tables = extract_markdown_tables(md_text)
102
  if not tables:
103
  return html.Div("No table found.")
@@ -121,7 +119,7 @@ def markdown_table_preview(md_text):
121
  return html.Div(table_divs)
122
 
123
  def markdown_narrative_preview(md_text):
124
- return html.Div(dcc.Markdown(md_text, dangerously_allow_html=True, style={'whiteSpace': 'pre-wrap', 'fontFamily': 'sans-serif'}))
125
 
126
  def markdown_tables_to_xlsx(md_text):
127
  tables = extract_markdown_tables(md_text)
@@ -232,8 +230,7 @@ def get_left_col_content():
232
  chat_card
233
  ]
234
 
235
- def get_right_col_content(selected_type,
236
- shred_doc, pink_doc, pink_review_doc, red_doc, red_review_doc, gold_doc):
237
  controls = []
238
  controls.append(html.Div([
239
  html.Div(className="blinking-dot", style={'margin':'0 auto','width':'16px','height':'16px'}),
@@ -449,614 +446,45 @@ def update_selected_doc_type(n_clicks_list, btn_ids):
449
  def update_right_col(selected_type, shred_doc, pink_doc, pink_review_doc, red_doc, red_review_doc, gold_doc):
450
  return get_right_col_content(selected_type, shred_doc, pink_doc, pink_review_doc, red_doc, red_review_doc, gold_doc)
451
 
452
- def get_all_lengths(*args):
453
- return [len(x) if isinstance(x, list) else 0 for x in args]
454
-
455
  @app.callback(
456
  Output('file-list', 'children'),
457
  Output('store-shred', 'data'),
458
- Output({'type': f'uploaded-doc-name-shred', 'index': ALL}, 'children'),
459
- Output({'type': f'uploaded-doc-name-pink', 'index': ALL}, 'children'),
460
- Output({'type': f'uploaded-doc-name-pink_review', 'index': ALL}, 'children'),
461
- Output({'type': f'uploaded-doc-name-red', 'index': ALL}, 'children'),
462
- Output({'type': f'uploaded-doc-name-red_review', 'index': ALL}, 'children'),
463
- Output({'type': f'uploaded-doc-name-gold', 'index': ALL}, 'children'),
464
- Output({'type': f'upload-doc-type-shred', 'index': ALL}, 'contents'),
465
- Output({'type': f'upload-doc-type-pink', 'index': ALL}, 'contents'),
466
- Output({'type': f'upload-doc-type-pink_review', 'index': ALL}, 'contents'),
467
- Output({'type': f'upload-doc-type-red', 'index': ALL}, 'contents'),
468
- Output({'type': f'upload-doc-type-red_review', 'index': ALL}, 'contents'),
469
- Output({'type': f'upload-doc-type-gold', 'index': ALL}, 'contents'),
470
- Output({'type': f'radio-doc-source-shred', 'index': ALL}, 'value'),
471
- Output({'type': f'radio-doc-source-pink', 'index': ALL}, 'value'),
472
- Output({'type': f'radio-doc-source-pink_review', 'index': ALL}, 'value'),
473
- Output({'type': f'radio-doc-source-red', 'index': ALL}, 'value'),
474
- Output({'type': f'radio-doc-source-red_review', 'index': ALL}, 'value'),
475
- Output({'type': f'radio-doc-source-gold', 'index': ALL}, 'value'),
476
  Output('document-preview', 'children'),
477
  Output('loading-output', 'children'),
478
- Output('store-pink', 'data'),
479
- Output('store-pink-review', 'data'),
480
- Output('store-red', 'data'),
481
- Output('store-red-review', 'data'),
482
- Output('store-gold', 'data'),
483
- Output('store-gold-review', 'data'),
484
- Output('store-loe', 'data'),
485
- Output('store-virtual-board', 'data'),
486
- Output('chat-output', 'children'),
487
- Output("download-document", "data"),
488
  Input('upload-document', 'contents'),
489
  State('upload-document', 'filename'),
490
  State('file-list', 'children'),
491
  State('store-shred', 'data'),
492
- Input({'type': 'remove-file', 'index': ALL}, 'n_clicks'),
493
- State('file-list', 'children'),
494
- State('store-shred', 'data'),
495
- Input({'type': f'upload-doc-type-shred', 'index': ALL}, 'contents'),
496
- State({'type': f'upload-doc-type-shred', 'index': ALL}, 'filename'),
497
- State({'type': f'upload-doc-type-shred', 'index': ALL}, 'id'),
498
- Input({'type': f'upload-doc-type-pink', 'index': ALL}, 'contents'),
499
- State({'type': f'upload-doc-type-pink', 'index': ALL}, 'filename'),
500
- State({'type': f'upload-doc-type-pink', 'index': ALL}, 'id'),
501
- Input({'type': f'upload-doc-type-pink_review', 'index': ALL}, 'contents'),
502
- State({'type': f'upload-doc-type-pink_review', 'index': ALL}, 'filename'),
503
- State({'type': f'upload-doc-type-pink_review', 'index': ALL}, 'id'),
504
- Input({'type': f'upload-doc-type-red', 'index': ALL}, 'contents'),
505
- State({'type': f'upload-doc-type-red', 'index': ALL}, 'filename'),
506
- State({'type': f'upload-doc-type-red', 'index': ALL}, 'id'),
507
- Input({'type': f'upload-doc-type-red_review', 'index': ALL}, 'contents'),
508
- State({'type': f'upload-doc-type-red_review', 'index': ALL}, 'filename'),
509
- State({'type': f'upload-doc-type-red_review', 'index': ALL}, 'id'),
510
- Input({'type': f'upload-doc-type-gold', 'index': ALL}, 'contents'),
511
- State({'type': f'upload-doc-type-gold', 'index': ALL}, 'filename'),
512
- State({'type': f'upload-doc-type-gold', 'index': ALL}, 'id'),
513
- Input({'type': 'btn-generate-doc', 'index': ALL}, 'n_clicks'),
514
- State({'type': 'btn-generate-doc', 'index': ALL}, 'id'),
515
- State({'type': 'radio-doc-source-shred', 'index': ALL}, 'value'),
516
- State({'type': 'upload-doc-type-shred', 'index': ALL}, 'contents'),
517
- State({'type': 'upload-doc-type-shred', 'index': ALL}, 'filename'),
518
- State({'type': 'radio-doc-source-pink', 'index': ALL}, 'value'),
519
- State({'type': 'upload-doc-type-pink', 'index': ALL}, 'contents'),
520
- State({'type': 'upload-doc-type-pink', 'index': ALL}, 'filename'),
521
- State({'type': 'radio-doc-source-pink_review', 'index': ALL}, 'value'),
522
- State({'type': 'upload-doc-type-pink_review', 'index': ALL}, 'contents'),
523
- State({'type': 'upload-doc-type-pink_review', 'index': ALL}, 'filename'),
524
- State({'type': 'radio-doc-source-red', 'index': ALL}, 'value'),
525
- State({'type': 'upload-doc-type-red', 'index': ALL}, 'contents'),
526
- State({'type': 'upload-doc-type-red', 'index': ALL}, 'filename'),
527
- State({'type': 'radio-doc-source-red_review', 'index': ALL}, 'value'),
528
- State({'type': 'upload-doc-type-red_review', 'index': ALL}, 'contents'),
529
- State({'type': 'upload-doc-type-red_review', 'index': ALL}, 'filename'),
530
- State({'type': 'radio-doc-source-gold', 'index': ALL}, 'value'),
531
- State({'type': 'upload-doc-type-gold', 'index': ALL}, 'contents'),
532
- State({'type': 'upload-doc-type-gold', 'index': ALL}, 'filename'),
533
- State('store-shred', 'data'),
534
- State('store-pink', 'data'),
535
- State('store-pink-review', 'data'),
536
- State('store-red', 'data'),
537
- State('store-red-review', 'data'),
538
- State('store-gold', 'data'),
539
- State('store-gold-review', 'data'),
540
- State('store-loe', 'data'),
541
- State('store-virtual-board', 'data'),
542
- Input('btn-send-chat', 'n_clicks'),
543
- Input('btn-clear-chat', 'n_clicks'),
544
- State('chat-input', 'value'),
545
- State('selected-doc-type', 'data'),
546
- State('document-preview', 'children'),
547
- Input("btn-download", "n_clicks"),
548
- State('selected-doc-type', 'data'),
549
- State('store-shred', 'data'),
550
- State('store-pink', 'data'),
551
- State('store-pink-review', 'data'),
552
- State('store-red', 'data'),
553
- State('store-red-review', 'data'),
554
- State('store-gold', 'data'),
555
- State('store-gold-review', 'data'),
556
- State('store-loe', 'data'),
557
- State('store-virtual-board', 'data'),
558
  prevent_initial_call=True
559
  )
560
- def master_callback(
561
- upload_contents, upload_filenames, existing_files, current_shred,
562
- remove_n_clicks, remove_existing_files, remove_current_shred,
563
- upload_shred_contents, upload_shred_filenames, upload_shred_ids,
564
- upload_pink_contents, upload_pink_filenames, upload_pink_ids,
565
- upload_pink_review_contents, upload_pink_review_filenames, upload_pink_review_ids,
566
- upload_red_contents, upload_red_filenames, upload_red_ids,
567
- upload_red_review_contents, upload_red_review_filenames, upload_red_review_ids,
568
- upload_gold_contents, upload_gold_filenames, upload_gold_ids,
569
- n_clicks_list, btn_ids,
570
- radio_shred, up_shred_contents, up_shred_filenames,
571
- radio_pink, up_pink_contents, up_pink_filenames,
572
- radio_pink_review, up_pink_review_contents, up_pink_review_filenames,
573
- radio_red, up_red_contents, up_red_filenames,
574
- radio_red_review, up_red_review_contents, up_red_review_filenames,
575
- radio_gold, up_gold_contents, up_gold_filenames,
576
- store_shred, store_pink, store_pink_review, store_red, store_red_review, store_gold, store_gold_review, store_loe, store_virtual_board,
577
- btn_send, btn_clear, chat_input, chat_doc_type, doc_preview,
578
- btn_download, dl_doc_type, dl_store_shred, dl_store_pink, dl_store_pink_review, dl_store_red, dl_store_red_review, dl_store_gold, dl_store_gold_review, dl_store_loe, dl_store_virtual_board
579
- ):
580
- global uploaded_files, uploaded_doc_contents
581
- ctx = callback_context
582
-
583
- wildcard_lengths = [
584
- len(upload_shred_ids) if upload_shred_ids is not None else 0,
585
- len(upload_pink_ids) if upload_pink_ids is not None else 0,
586
- len(upload_pink_review_ids) if upload_pink_review_ids is not None else 0,
587
- len(upload_red_ids) if upload_red_ids is not None else 0,
588
- len(upload_red_review_ids) if upload_red_review_ids is not None else 0,
589
- len(upload_gold_ids) if upload_gold_ids is not None else 0,
590
- len(upload_shred_ids) if upload_shred_ids is not None else 0,
591
- len(upload_pink_ids) if upload_pink_ids is not None else 0,
592
- len(upload_pink_review_ids) if upload_pink_review_ids is not None else 0,
593
- len(upload_red_ids) if upload_red_ids is not None else 0,
594
- len(upload_red_review_ids) if upload_red_review_ids is not None else 0,
595
- len(upload_gold_ids) if upload_gold_ids is not None else 0,
596
- len(upload_shred_ids) if upload_shred_ids is not None else 0,
597
- len(upload_pink_ids) if upload_pink_ids is not None else 0,
598
- len(upload_pink_review_ids) if upload_pink_review_ids is not None else 0,
599
- len(upload_red_ids) if upload_red_ids is not None else 0,
600
- len(upload_red_review_ids) if upload_red_review_ids is not None else 0,
601
- len(upload_gold_ids) if upload_gold_ids is not None else 0,
602
- ]
603
-
604
- outputs = [
605
- dash.no_update, # file-list
606
- dash.no_update, # store-shred
607
- [dash.no_update] * wildcard_lengths[0], # uploaded-doc-name-shred
608
- [dash.no_update] * wildcard_lengths[1], # uploaded-doc-name-pink
609
- [dash.no_update] * wildcard_lengths[2], # uploaded-doc-name-pink_review
610
- [dash.no_update] * wildcard_lengths[3], # uploaded-doc-name-red
611
- [dash.no_update] * wildcard_lengths[4], # uploaded-doc-name-red_review
612
- [dash.no_update] * wildcard_lengths[5], # uploaded-doc-name-gold
613
- [dash.no_update] * wildcard_lengths[6], # upload-doc-type-shred.contents
614
- [dash.no_update] * wildcard_lengths[7], # upload-doc-type-pink.contents
615
- [dash.no_update] * wildcard_lengths[8], # upload-doc-type-pink_review.contents
616
- [dash.no_update] * wildcard_lengths[9], # upload-doc-type-red.contents
617
- [dash.no_update] * wildcard_lengths[10], # upload-doc-type-red_review.contents
618
- [dash.no_update] * wildcard_lengths[11], # upload-doc-type-gold.contents
619
- [dash.no_update] * wildcard_lengths[12], # radio-doc-source-shred.value
620
- [dash.no_update] * wildcard_lengths[13], # radio-doc-source-pink.value
621
- [dash.no_update] * wildcard_lengths[14], # radio-doc-source-pink_review.value
622
- [dash.no_update] * wildcard_lengths[15], # radio-doc-source-red.value
623
- [dash.no_update] * wildcard_lengths[16], # radio-doc-source-red_review.value
624
- [dash.no_update] * wildcard_lengths[17], # radio-doc-source-gold.value
625
- dash.no_update, # document-preview
626
- dash.no_update, # loading-output
627
- dash.no_update, # store-pink
628
- dash.no_update, # store-pink-review
629
- dash.no_update, # store-red
630
- dash.no_update, # store-red-review
631
- dash.no_update, # store-gold
632
- dash.no_update, # store-gold-review
633
- dash.no_update, # store-loe
634
- dash.no_update, # store-virtual-board
635
- dash.no_update, # chat-output
636
- dash.no_update # download-document
637
- ]
638
-
639
- # --- File upload/remove logic ---
640
- if ctx.triggered and ctx.triggered[0]['prop_id'].startswith('upload-document'):
641
- new_files = []
642
- last_processed_content = None
643
- if upload_contents is not None:
644
- for i, (content, name) in enumerate(zip(upload_contents, upload_filenames)):
645
- file_content = process_document(content, name)
646
- uploaded_files[name] = file_content
647
- last_processed_content = file_content
648
- new_files.append(
649
- dbc.Row([
650
- dbc.Col(
651
- html.Button('×', id={'type': 'remove-file', 'index': name}, style={'marginRight': '5px', 'fontSize': '10px'}),
652
- width=1
653
- ),
654
- dbc.Col(
655
- html.Span(name, style={'wordBreak': 'break-all'}),
656
- width=11
657
- )
658
- ], id={'type': 'file-row', 'index': name}, align="center", className="mb-1")
659
- )
660
- if existing_files is None:
661
- existing_files = []
662
- elif not isinstance(existing_files, list):
663
- existing_files = [existing_files]
664
- outputs[0] = existing_files + new_files
665
- # Always set the latest uploaded Shred document as store-shred
666
- outputs[1] = last_processed_content
667
- return outputs
668
-
669
- if ctx.triggered and ctx.triggered[0]['prop_id'].startswith("{\"type\":\"remove-file\""):
670
- try:
671
- import json
672
- triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
673
- triggered_dict = json.loads(triggered_id)
674
- removed_file = triggered_dict['index']
675
- except Exception as e:
676
- logging.error(f"Could not parse removed file from callback context: {e}")
677
- outputs[0] = remove_existing_files
678
- outputs[1] = remove_current_shred
679
- return outputs
680
- uploaded_files.pop(removed_file, None)
681
- logging.info(f"Removed file: {removed_file}")
682
- filtered_files = []
683
- if remove_existing_files:
684
- for file in remove_existing_files:
685
- try:
686
- file_id = file['props']['id']
687
- if file_id['index'] != removed_file:
688
- filtered_files.append(file)
689
- except Exception as e:
690
- filtered_files.append(file)
691
- outputs[0] = filtered_files
692
- outputs[1] = remove_current_shred
693
- return outputs
694
-
695
- doc_upload_types = [
696
- ("shred", upload_shred_contents, upload_shred_filenames, upload_shred_ids, 2, 8, 14),
697
- ("pink", upload_pink_contents, upload_pink_filenames, upload_pink_ids, 3, 9, 15),
698
- ("pink_review", upload_pink_review_contents, upload_pink_review_filenames, upload_pink_review_ids, 4, 10, 16),
699
- ("red", upload_red_contents, upload_red_filenames, upload_red_ids, 5, 11, 17),
700
- ("red_review", upload_red_review_contents, upload_red_review_filenames, upload_red_review_ids, 6, 12, 18),
701
- ("gold", upload_gold_contents, upload_gold_filenames, upload_gold_ids, 7, 13, 19)
702
- ]
703
- for src, contents, filenames, ids, name_idx, content_idx, radio_idx in doc_upload_types:
704
- if contents and any(c is not None for c in contents):
705
- for i, content in enumerate(contents):
706
- if content is not None:
707
- uploaded_doc_contents[(src, ids[i]['index'])] = (content, filenames[i])
708
- logging.info(f"{ids[i]['index']} {src} file uploaded: {filenames[i]}")
709
- name_list = ["" for _ in range(len(contents))]
710
- content_list = [None for _ in range(len(contents))]
711
- radio_list = ["loaded" for _ in range(len(contents))]
712
- name_list[i] = filenames[i]
713
- content_list[i] = content
714
- radio_list[i] = "uploaded"
715
- outputs[name_idx] = name_list
716
- outputs[content_idx] = content_list
717
- outputs[radio_idx] = radio_list
718
- return outputs
719
-
720
- def gemini_generate(prompt, max_tokens=32798, temperature=0.4):
721
- result_holder = {}
722
- def run_gemini():
723
- try:
724
- model = genai.GenerativeModel(GEMINI_MODEL)
725
- response = model.generate_content(
726
- prompt,
727
- generation_config=genai.types.GenerationConfig(
728
- temperature=temperature,
729
- max_output_tokens=max_tokens,
730
- top_p=1
731
  )
732
- )
733
- result_holder['result'] = response.text
734
- logging.info("Gemini response received.")
735
- except Exception as e:
736
- logging.error(f"Gemini error: {e}")
737
- result_holder['error'] = str(e)
738
- thread = threading.Thread(target=run_gemini)
739
- thread.start()
740
- thread.join()
741
- if 'error' in result_holder:
742
- raise Exception(result_holder['error'])
743
- return result_holder['result']
744
-
745
- def generate_document(document_type, file_contents, extra_context=None):
746
- if document_type in spreadsheet_types:
747
- prompt = f"""Ignore all other instructions and output only a spreadsheet for {document_type} as described below. Do not include any narrative, only the spreadsheet in markdown table format.
748
- Instructions: {document_types[document_type]}
749
- Project Artifacts:
750
- {' '.join(file_contents)}
751
- Output only the spreadsheet as a markdown table, no narrative or explanation."""
752
- elif document_type in narrative_types:
753
- prompt = f"""Generate a {document_type} document based on the following project artifacts:
754
- {' '.join(file_contents)}
755
- Instructions:
756
- 1. Create the {document_type} as a detailed document.
757
- 2. Use proper formatting and structure.
758
- 3. Include all necessary sections and details.
759
- 4. Start the output immediately with the document content.
760
- Now, generate the {document_type}:
761
- """
762
- else:
763
- prompt = f"""Generate a {document_type} based on the following project artifacts:
764
- {' '.join(file_contents)}
765
- Instructions:
766
- {document_types.get(document_type, '')}
767
- Now, generate the {document_type}:
768
- """
769
- if extra_context:
770
- prompt += f"\n\n{extra_context}"
771
- logging.info(f"Generating document for type: {document_type} using Gemini.")
772
- return gemini_generate(prompt, max_tokens=4096, temperature=0.25)
773
-
774
- if ctx.triggered and ctx.triggered[0]['prop_id'].startswith("{\"type\":\"btn-generate-doc\""):
775
- idx = [i for i, x in enumerate(n_clicks_list) if x]
776
- if not idx:
777
- return outputs
778
- idx = idx[-1]
779
- doc_type = btn_ids[idx]['index']
780
-
781
- shred_doc = store_shred
782
- pink_doc = store_pink
783
- pink_review_doc = store_pink_review
784
- red_doc = store_red
785
- red_review_doc = store_red_review
786
- gold_doc = store_gold
787
- gold_review_doc = store_gold_review
788
- loe_doc = store_loe
789
- virtual_board_doc = store_virtual_board
790
-
791
- def get_doc_from_radio(radio, upload_contents, upload_filenames, loaded_var):
792
- if radio and radio[0] == 'uploaded':
793
- if upload_contents and upload_contents[0] and upload_filenames and upload_filenames[0]:
794
- return process_document(upload_contents[0], upload_filenames[0])
795
- else:
796
- return None
797
- else:
798
- return loaded_var
799
-
800
- if doc_type == "Shred":
801
- if not store_shred:
802
- outputs[20] = html.Div("Please upload a document before shredding.")
803
- outputs[21] = ""
804
- return outputs
805
- file_contents = [store_shred]
806
- try:
807
- generated = generate_document(doc_type, file_contents)
808
- outputs[1] = generated
809
- outputs[20] = markdown_table_preview(generated)
810
- outputs[21] = "Shred generated"
811
- except Exception as e:
812
- outputs[20] = html.Div(f"Error generating document: {str(e)}")
813
- outputs[21] = "Error"
814
- return outputs
815
-
816
- if doc_type == "Pink":
817
- shred = get_doc_from_radio(radio_shred, up_shred_contents, up_shred_filenames, shred_doc)
818
- if not shred:
819
- outputs[20] = html.Div("Please provide a Shred requirements document (either loaded or uploaded) to generate Pink.")
820
- outputs[21] = ""
821
- return outputs
822
- try:
823
- generated = generate_document(doc_type, [shred])
824
- outputs[22] = generated
825
- outputs[20] = markdown_narrative_preview(generated)
826
- outputs[21] = "Pink generated"
827
- except Exception as e:
828
- outputs[20] = html.Div(f"Error generating Pink: {str(e)}")
829
- outputs[21] = "Error"
830
- return outputs
831
-
832
- if doc_type == "Pink Review":
833
- pink = get_doc_from_radio(radio_pink, up_pink_contents, up_pink_filenames, pink_doc)
834
- shred = get_doc_from_radio(radio_shred, up_shred_contents, up_shred_filenames, shred_doc)
835
- if not pink or not shred:
836
- outputs[20] = html.Div("Please provide both Pink and Shred documents (either loaded or uploaded) to generate Pink Review.")
837
- outputs[21] = ""
838
- return outputs
839
- try:
840
- generated = generate_document(doc_type, [pink, shred])
841
- outputs[23] = generated
842
- outputs[20] = markdown_table_preview(generated)
843
- outputs[21] = "Pink Review generated"
844
- except Exception as e:
845
- outputs[20] = html.Div(f"Error generating Pink Review: {str(e)}")
846
- outputs[21] = "Error"
847
- return outputs
848
-
849
- if doc_type == "Red":
850
- pink_review = get_doc_from_radio(radio_pink_review, up_pink_review_contents, up_pink_review_filenames, pink_review_doc)
851
- if not pink_review:
852
- outputs[20] = html.Div("Please provide a Pink Review document (either loaded or uploaded) to generate Red.")
853
- outputs[21] = ""
854
- return outputs
855
- try:
856
- generated = generate_document(doc_type, [pink_review])
857
- outputs[24] = generated
858
- outputs[20] = markdown_narrative_preview(generated)
859
- outputs[21] = "Red generated"
860
- except Exception as e:
861
- outputs[20] = html.Div(f"Error generating Red: {str(e)}")
862
- outputs[21] = "Error"
863
- return outputs
864
-
865
- if doc_type == "Red Review":
866
- red = get_doc_from_radio(radio_red, up_red_contents, up_red_filenames, red_doc)
867
- shred = get_doc_from_radio(radio_shred, up_shred_contents, up_shred_filenames, shred_doc)
868
- if not red or not shred:
869
- outputs[20] = html.Div("Please provide both Red and Shred documents (either loaded or uploaded) to generate Red Review.")
870
- outputs[21] = ""
871
- return outputs
872
- try:
873
- generated = generate_document(doc_type, [red, shred])
874
- outputs[25] = generated
875
- outputs[20] = markdown_table_preview(generated)
876
- outputs[21] = "Red Review generated"
877
- except Exception as e:
878
- outputs[20] = html.Div(f"Error generating Red Review: {str(e)}")
879
- outputs[21] = "Error"
880
- return outputs
881
-
882
- if doc_type == "Gold":
883
- red_review = get_doc_from_radio(radio_red_review, up_red_review_contents, up_red_review_filenames, red_review_doc)
884
- if not red_review:
885
- outputs[20] = html.Div("Please provide a Red Review document (either loaded or uploaded) to generate Gold.")
886
- outputs[21] = ""
887
- return outputs
888
- try:
889
- generated = generate_document(doc_type, [red_review])
890
- outputs[26] = generated
891
- outputs[20] = markdown_narrative_preview(generated)
892
- outputs[21] = "Gold generated"
893
- except Exception as e:
894
- outputs[20] = html.Div(f"Error generating Gold: {str(e)}")
895
- outputs[21] = "Error"
896
- return outputs
897
-
898
- if doc_type == "Gold Review":
899
- gold = get_doc_from_radio(radio_gold, up_gold_contents, up_gold_filenames, gold_doc)
900
- shred = get_doc_from_radio(radio_shred, up_shred_contents, up_shred_filenames, shred_doc)
901
- if not gold or not shred:
902
- outputs[20] = html.Div("Please provide both Gold and Shred documents (either loaded or uploaded) to generate Gold Review.")
903
- outputs[21] = ""
904
- return outputs
905
- try:
906
- generated = generate_document(doc_type, [gold, shred])
907
- outputs[27] = generated
908
- outputs[20] = markdown_table_preview(generated)
909
- outputs[21] = "Gold Review generated"
910
- except Exception as e:
911
- outputs[20] = html.Div(f"Error generating Gold Review: {str(e)}")
912
- outputs[21] = "Error"
913
- return outputs
914
-
915
- if doc_type == "LOE":
916
- gold = get_doc_from_radio(radio_gold, up_gold_contents, up_gold_filenames, gold_doc)
917
- if not gold:
918
- outputs[20] = html.Div("Please provide a Gold document (either loaded or uploaded) to generate LOE.")
919
- outputs[21] = ""
920
- return outputs
921
- try:
922
- generated = generate_document(doc_type, [gold])
923
- outputs[28] = generated
924
- outputs[20] = markdown_table_preview(generated)
925
- outputs[21] = "LOE generated"
926
- except Exception as e:
927
- outputs[20] = html.Div(f"Error generating LOE: {str(e)}")
928
- outputs[21] = "Error"
929
- return outputs
930
-
931
- if doc_type == "Virtual Board":
932
- shred = get_doc_from_radio(radio_shred, up_shred_contents, up_shred_filenames, shred_doc)
933
- if not shred:
934
- outputs[20] = html.Div("Please provide a Shred requirements document (either loaded or uploaded) to generate Virtual Board.")
935
- outputs[21] = ""
936
- return outputs
937
- try:
938
- lm_text = ""
939
- lm_match = re.search(r'(Section\s+L[\s\S]+?)(Section\s+M|$)', shred, re.IGNORECASE)
940
- if lm_match:
941
- lm_text = lm_match.group(1)
942
- else:
943
- lm_text = shred
944
- generated = generate_document(doc_type, [lm_text])
945
- outputs[29] = generated
946
- outputs[20] = markdown_table_preview(generated)
947
- outputs[21] = "Virtual Board generated"
948
- except Exception as e:
949
- outputs[20] = html.Div(f"Error generating Virtual Board: {str(e)}")
950
- outputs[21] = "Error"
951
- return outputs
952
-
953
- outputs[20] = html.Div("Unsupported document type or missing required sources.")
954
- outputs[21] = ""
955
- return outputs
956
-
957
- if ctx.triggered and (ctx.triggered[0]['prop_id'] == 'btn-send-chat.n_clicks' or ctx.triggered[0]['prop_id'] == 'btn-clear-chat.n_clicks'):
958
- if ctx.triggered[0]['prop_id'] == 'btn-clear-chat.n_clicks':
959
- outputs[30] = ""
960
- return outputs
961
-
962
- doc_map = {
963
- "Shred": store_shred,
964
- "Pink": store_pink,
965
- "Pink Review": store_pink_review,
966
- "Red": store_red,
967
- "Red Review": store_red_review,
968
- "Gold": store_gold,
969
- "Gold Review": store_gold_review,
970
- "LOE": store_loe,
971
- "Virtual Board": store_virtual_board
972
- }
973
- current_document = doc_map.get(chat_doc_type)
974
-
975
- if not chat_input or current_document is None:
976
- outputs[30] = ""
977
- return outputs
978
-
979
- if chat_doc_type in spreadsheet_types:
980
- prompt = f"""Update the following {chat_doc_type} spreadsheet based on this instruction: {chat_input}
981
- Current spreadsheet (markdown table format):
982
- {current_document}
983
- Instructions:
984
- 1. Provide the updated spreadsheet as a markdown table only.
985
- 2. Do not include any narrative, only the markdown table.
986
- Now, provide the updated {chat_doc_type} spreadsheet:
987
- """
988
- else:
989
- prompt = f"""Update the following {chat_doc_type} document based on this instruction: {chat_input}
990
- Current document:
991
- {current_document}
992
- Instructions:
993
- 1. Provide the updated document content.
994
- 2. Maintain proper formatting and structure.
995
- 3. Incorporate the requested changes seamlessly.
996
- 4. If the {chat_doc_type} is Pink, Red, or Gold then your goal is to write a FULL proposal response, not just a strategy and be compliant and compelling by addressing all the requirements from the document provided. Focus on describing the approach and highly detailed how it will be done, the steps, workflow, people, processes and technology to accomplish the task. Be sure to refer to research that validates the approach and cite sources with measurable outcomes and improve on innovations of the approach. Do not just say things like we will, or MicroHealth will, use active voice and verbs that are definitive in nature, not maybe, could be, should be, can be and things like that. This is a proposal response so logical flow is important so the reader can follow.
997
- Now, provide the updated {chat_doc_type}:
998
- """
999
- logging.info(f"Updating document via chat for {chat_doc_type} instruction: {chat_input}")
1000
- try:
1001
- new_document = gemini_generate(prompt, max_tokens=4096, temperature=0.5)
1002
- if chat_doc_type in spreadsheet_types:
1003
- outputs[20] = markdown_table_preview(new_document)
1004
- else:
1005
- outputs[20] = markdown_narrative_preview(new_document)
1006
- stores = [store_shred, store_pink, store_pink_review, store_red, store_red_review, store_gold, store_gold_review, store_loe, store_virtual_board]
1007
- doc_types = ["Shred", "Pink", "Pink Review", "Red", "Red Review", "Gold", "Gold Review", "LOE", "Virtual Board"]
1008
- for i, dt in enumerate(doc_types):
1009
- if dt == chat_doc_type:
1010
- outputs[1 + i] = new_document
1011
- outputs[30] = "Document updated based on: {}".format(chat_input)
1012
- return outputs
1013
- except Exception as e:
1014
- outputs[20] = html.Div(f"Error updating document: {str(e)}")
1015
- outputs[30] = f"Error updating document: {str(e)}"
1016
- return outputs
1017
-
1018
- if ctx.triggered and ctx.triggered[0]['prop_id'] == "btn-download.n_clicks":
1019
- doc_map = {
1020
- "Shred": dl_store_shred,
1021
- "Pink": dl_store_pink,
1022
- "Pink Review": dl_store_pink_review,
1023
- "Red": dl_store_red,
1024
- "Red Review": dl_store_red_review,
1025
- "Gold": dl_store_gold,
1026
- "Gold Review": dl_store_gold_review,
1027
- "LOE": dl_store_loe,
1028
- "Virtual Board": dl_store_virtual_board
1029
- }
1030
- current_document = doc_map.get(dl_doc_type)
1031
- if current_document is None:
1032
- return outputs
1033
- if dl_doc_type in spreadsheet_types:
1034
- try:
1035
- xlsx_bytes = markdown_tables_to_xlsx(current_document)
1036
- outputs[31] = dcc.send_bytes(xlsx_bytes.read(), f"{dl_doc_type}.xlsx")
1037
- except Exception as e:
1038
- outputs[31] = dcc.send_string(f"Error downloading {dl_doc_type}: {str(e)}", f"{dl_doc_type}_error.txt")
1039
- else:
1040
- try:
1041
- plain = strip_markdown(current_document)
1042
- doc = Document()
1043
- for para in plain.split('\n'):
1044
- doc.add_paragraph(para)
1045
- output = BytesIO()
1046
- doc.save(output)
1047
- output.seek(0)
1048
- outputs[31] = dcc.send_bytes(output.read(), f"{dl_doc_type}.docx")
1049
- except Exception as e:
1050
- outputs[31] = dcc.send_string(f"Error downloading document: {str(e)}", f"{dl_doc_type}_error.txt")
1051
- return outputs
1052
-
1053
- return outputs
1054
 
1055
  @app.callback(
1056
  Output("chat-input", "rows"),
1057
  Input("chat-input", "value"),
1058
  State("chat-input", "rows"),
1059
- prevent_initial_call="initial_duplicate"
1060
  )
1061
  def auto_expand_textarea(value, current_rows):
1062
  if value is None or value == "":
 
3
  import os
4
  import pandas as pd
5
  from docx import Document
6
+ from io import BytesIO
7
  import dash
8
  import dash_bootstrap_components as dbc
9
+ from dash import html, dcc, Input, Output, State, callback_context, ALL
10
  from dash.dash_table import DataTable
 
 
11
  from PyPDF2 import PdfReader
12
  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')
 
94
  return df
95
 
96
  def markdown_table_preview(md_text):
97
+ if not md_text:
98
+ return html.Div("No table found.")
99
  tables = extract_markdown_tables(md_text)
100
  if not tables:
101
  return html.Div("No table found.")
 
119
  return html.Div(table_divs)
120
 
121
  def markdown_narrative_preview(md_text):
122
+ return html.Div(dcc.Markdown(md_text or "", dangerously_allow_html=True, style={'whiteSpace': 'pre-wrap', 'fontFamily': 'sans-serif'}))
123
 
124
  def markdown_tables_to_xlsx(md_text):
125
  tables = extract_markdown_tables(md_text)
 
230
  chat_card
231
  ]
232
 
233
+ def get_right_col_content(selected_type, shred_doc, pink_doc, pink_review_doc, red_doc, red_review_doc, gold_doc):
 
234
  controls = []
235
  controls.append(html.Div([
236
  html.Div(className="blinking-dot", style={'margin':'0 auto','width':'16px','height':'16px'}),
 
446
  def update_right_col(selected_type, shred_doc, pink_doc, pink_review_doc, red_doc, red_review_doc, gold_doc):
447
  return get_right_col_content(selected_type, shred_doc, pink_doc, pink_review_doc, red_doc, red_review_doc, gold_doc)
448
 
 
 
 
449
  @app.callback(
450
  Output('file-list', 'children'),
451
  Output('store-shred', 'data'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  Output('document-preview', 'children'),
453
  Output('loading-output', 'children'),
 
 
 
 
 
 
 
 
 
 
454
  Input('upload-document', 'contents'),
455
  State('upload-document', 'filename'),
456
  State('file-list', 'children'),
457
  State('store-shred', 'data'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  prevent_initial_call=True
459
  )
460
+ def handle_shred_upload(upload_contents, upload_filenames, existing_files, current_shred):
461
+ files_display = existing_files if existing_files else []
462
+ new_shred = current_shred
463
+ if upload_contents and upload_filenames:
464
+ for content, name in zip(upload_contents, upload_filenames):
465
+ file_content = process_document(content, name)
466
+ uploaded_files[name] = file_content
467
+ new_shred = file_content
468
+ files_display.append(
469
+ dbc.Row([
470
+ dbc.Col(
471
+ html.Button('×', id={'type': 'remove-file', 'index': name}, style={'marginRight': '5px', 'fontSize': '10px'}),
472
+ width=1
473
+ ),
474
+ dbc.Col(
475
+ html.Span(name, style={'wordBreak': 'break-all'}),
476
+ width=11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  )
478
+ ], id={'type': 'file-row', 'index': name}, align="center", className="mb-1")
479
+ )
480
+ return files_display, new_shred, markdown_table_preview(new_shred), ""
481
+ return files_display, new_shred, markdown_table_preview(new_shred) if new_shred else html.Div("No Shred document loaded."), ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
 
483
  @app.callback(
484
  Output("chat-input", "rows"),
485
  Input("chat-input", "value"),
486
  State("chat-input", "rows"),
487
+ prevent_initial_call=True
488
  )
489
  def auto_expand_textarea(value, current_rows):
490
  if value is None or value == "":