Spaces:
Paused
Paused
Update app.py via AI Editor
Browse files
app.py
CHANGED
@@ -33,7 +33,6 @@ uploaded_documents = {}
|
|
33 |
uploaded_documents_fileid = {}
|
34 |
uploaded_proposals = {}
|
35 |
uploaded_proposals_fileid = {}
|
36 |
-
generated_documents = {}
|
37 |
shredded_documents = {}
|
38 |
shredded_document = None
|
39 |
generated_response = None
|
@@ -146,6 +145,16 @@ def save_proposal_as_docx(proposal_text, base_filename):
|
|
146 |
memf.seek(0)
|
147 |
return memf.read()
|
148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
149 |
def process_document(action, selected_filename=None, chat_input=None, rfp_decoded_bytes=None, cancel_event=None, stream=True, selected_generated_filename=None):
|
150 |
global shredded_document, generated_response, stream_buffer
|
151 |
|
@@ -153,7 +162,7 @@ def process_document(action, selected_filename=None, chat_input=None, rfp_decode
|
|
153 |
|
154 |
doc_content = None
|
155 |
doc_fileid = None
|
156 |
-
if action in ["shred", "proposal"]:
|
157 |
if selected_filename and selected_filename in uploaded_documents:
|
158 |
doc_content = uploaded_documents[selected_filename]
|
159 |
doc_fileid = uploaded_documents_fileid.get(selected_filename)
|
@@ -258,7 +267,8 @@ def process_document(action, selected_filename=None, chat_input=None, rfp_decode
|
|
258 |
if not cancel_event or not cancel_event.is_set():
|
259 |
docx_bytes = save_proposal_as_docx(response, rfp_filename)
|
260 |
generated_docx_name = f"{os.path.splitext(rfp_filename)[0]}_proposal.docx"
|
261 |
-
|
|
|
262 |
result_holder["text"] = response
|
263 |
result_holder["docx_name"] = generated_docx_name
|
264 |
else:
|
@@ -277,29 +287,15 @@ def process_document(action, selected_filename=None, chat_input=None, rfp_decode
|
|
277 |
return result_holder["text"], None, result_holder["docx_name"], result_holder["text"]
|
278 |
|
279 |
elif action == 'compliance':
|
280 |
-
|
281 |
-
# Get selected generated doc (proposal) and selected RFP doc
|
282 |
-
if not selected_generated_filename or selected_generated_filename not in generated_documents:
|
283 |
return "No generated document selected for compliance.", None, None, None
|
284 |
if not selected_filename or selected_filename not in uploaded_documents:
|
285 |
return "No RFP/SOW/PWS/RFI document selected for compliance.", None, None, None
|
286 |
|
287 |
-
proposal_docx_bytes = generated_documents[selected_generated_filename]
|
288 |
rfp_text = uploaded_documents[selected_filename]
|
|
|
289 |
logging.info(f"Compliance check: comparing proposal [{selected_generated_filename}] to RFP [{selected_filename}]")
|
290 |
|
291 |
-
# Decode the proposal docx to text (best-effort, using python-docx)
|
292 |
-
proposal_text = ""
|
293 |
-
try:
|
294 |
-
file_stream = io.BytesIO(proposal_docx_bytes)
|
295 |
-
doc = Document(file_stream)
|
296 |
-
proposal_text = "\n".join([para.text for para in doc.paragraphs])
|
297 |
-
if not proposal_text.strip():
|
298 |
-
proposal_text = "[Unable to extract text from proposal docx]"
|
299 |
-
except Exception as e:
|
300 |
-
proposal_text = "[Error decoding proposal docx: {}]".format(str(e))
|
301 |
-
logging.error("Error extracting text from proposal docx: %s", e)
|
302 |
-
|
303 |
prompt = (
|
304 |
"You are a proposal compliance expert. Use the following RFP/SOW/PWS/RFI and the generated proposal response. "
|
305 |
"Compare the proposal to the RFP requirements and win themes. "
|
@@ -325,6 +321,14 @@ def process_document(action, selected_filename=None, chat_input=None, rfp_decode
|
|
325 |
break
|
326 |
result = stream_buffer["preview"]
|
327 |
result_holder["text"] = result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
328 |
logging.info("Compliance check completed.")
|
329 |
except Exception as e:
|
330 |
logging.error("Error during compliance check: %s", e)
|
@@ -334,7 +338,7 @@ def process_document(action, selected_filename=None, chat_input=None, rfp_decode
|
|
334 |
t = Thread(target=thread_compliance)
|
335 |
t.start()
|
336 |
t.join()
|
337 |
-
return result_holder["text"], None,
|
338 |
|
339 |
elif action == 'recover':
|
340 |
return "Recovery not implemented yet.", None, None, None
|
@@ -350,16 +354,14 @@ def get_uploaded_doc_list(docdict):
|
|
350 |
doc_list = []
|
351 |
for filename in docdict:
|
352 |
file_content = docdict[filename]
|
353 |
-
if filename.lower().endswith('.docx')
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
docx_bytes = save_shredded_as_docx(file_content, filename)
|
358 |
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
359 |
-
|
360 |
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
361 |
-
|
362 |
-
mime = "text/plain"
|
363 |
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
364 |
download_link = html.A(
|
365 |
filename,
|
@@ -403,16 +405,11 @@ def get_uploaded_proposal_list(docdict):
|
|
403 |
doc_list = []
|
404 |
for filename in docdict:
|
405 |
file_content = docdict[filename]
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
412 |
-
except Exception:
|
413 |
-
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
414 |
-
else:
|
415 |
-
mime = "text/plain"
|
416 |
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
417 |
download_link = html.A(
|
418 |
filename,
|
@@ -429,46 +426,6 @@ def get_uploaded_proposal_list(docdict):
|
|
429 |
)
|
430 |
return dbc.ListGroup(doc_list, flush=True)
|
431 |
|
432 |
-
def get_generated_doc_list(docdict):
|
433 |
-
if not docdict:
|
434 |
-
return html.Div("No generated documents yet.", style={"wordWrap": "break-word"})
|
435 |
-
doc_list = []
|
436 |
-
for filename in docdict:
|
437 |
-
docx_bytes = docdict[filename]
|
438 |
-
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
439 |
-
download_link = html.A(
|
440 |
-
filename,
|
441 |
-
href=f"data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{b64}",
|
442 |
-
download=filename,
|
443 |
-
target="_blank",
|
444 |
-
style={"wordWrap": "break-word", "marginRight": "10px", "textDecoration": "underline"}
|
445 |
-
)
|
446 |
-
doc_list.append(
|
447 |
-
dbc.ListGroupItem([
|
448 |
-
download_link,
|
449 |
-
dbc.Button("Delete", id={'type': 'delete-generated-btn', 'index': filename, 'group': 'generated'}, size="sm", color="danger", className="float-end ms-2")
|
450 |
-
], className="d-flex justify-content-between align-items-center")
|
451 |
-
)
|
452 |
-
return dbc.ListGroup(doc_list, flush=True)
|
453 |
-
|
454 |
-
def get_generated_rfp_download_section(selected_value):
|
455 |
-
if not selected_value or selected_value not in generated_documents:
|
456 |
-
return html.Div("No generated document selected.", style={"wordWrap": "break-word"})
|
457 |
-
docx_bytes = generated_documents[selected_value]
|
458 |
-
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
459 |
-
download_link = html.A(
|
460 |
-
f"Download {selected_value}",
|
461 |
-
href=f"data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{b64}",
|
462 |
-
download=selected_value,
|
463 |
-
target="_blank",
|
464 |
-
style={"wordWrap": "break-word"}
|
465 |
-
)
|
466 |
-
return html.Div([
|
467 |
-
html.H6("RFP/SOW/PWS/RFI", style={"marginTop": "10px"}),
|
468 |
-
html.Div(download_link, style={"marginBottom": "15px"}),
|
469 |
-
html.Div("Preview not available for docx. Download to view.", style={"wordWrap": "break-word"})
|
470 |
-
])
|
471 |
-
|
472 |
app.layout = dbc.Container([
|
473 |
dcc.Store(id='shred-store', data={'text': None, 'docx_name': None}),
|
474 |
dcc.Store(id='proposal-store', data={'text': None, 'docx_name': None}),
|
@@ -507,15 +464,14 @@ app.layout = dbc.Container([
|
|
507 |
multiple=False
|
508 |
),
|
509 |
html.Hr(style={"marginTop": "20px", "marginBottom": "10px"}),
|
510 |
-
html.H6("Shredded
|
511 |
html.Div(get_shredded_doc_list(shredded_documents), id='shredded-doc-list')
|
512 |
])
|
513 |
], className="mb-3"),
|
514 |
-
|
515 |
dbc.Card([
|
516 |
dbc.CardHeader(html.H5("Proposal")),
|
517 |
dbc.CardBody([
|
518 |
-
html.H6("
|
519 |
html.Div(get_uploaded_proposal_list(uploaded_proposals), id='uploaded-proposal-list'),
|
520 |
dcc.Dropdown(
|
521 |
id='select-proposal-dropdown',
|
@@ -544,22 +500,6 @@ app.layout = dbc.Container([
|
|
544 |
)
|
545 |
])
|
546 |
], className="mb-3"),
|
547 |
-
|
548 |
-
dbc.Card([
|
549 |
-
dbc.CardHeader(html.H5("Generated Documents")),
|
550 |
-
dbc.CardBody([
|
551 |
-
dcc.Dropdown(
|
552 |
-
id='select-generated-dropdown',
|
553 |
-
options=[{'label': fn, 'value': fn} for fn in generated_documents.keys()],
|
554 |
-
placeholder="Select a generated document",
|
555 |
-
value=next(iter(generated_documents), None),
|
556 |
-
style={"marginBottom": "10px"}
|
557 |
-
),
|
558 |
-
html.Div(id='generated-rfp-section'),
|
559 |
-
html.Hr(style={"marginTop": "20px", "marginBottom": "10px"}),
|
560 |
-
html.Div(get_generated_doc_list(generated_documents), id='generated-doc-list'),
|
561 |
-
])
|
562 |
-
], className="mb-3")
|
563 |
], style={'minWidth': '260px', 'width':'30vw','maxWidth':'30vw'}, width=3),
|
564 |
|
565 |
dbc.Col([
|
@@ -598,10 +538,9 @@ app.layout = dbc.Container([
|
|
598 |
Output('select-document-dropdown', 'options'),
|
599 |
Output('select-document-dropdown', 'value'),
|
600 |
Output('shredded-doc-list', 'children'),
|
601 |
-
Output('
|
602 |
-
Output('select-
|
603 |
-
Output('select-
|
604 |
-
Output('generated-rfp-section', 'children'),
|
605 |
Output('stream-status', 'data'),
|
606 |
Output('stream-interval', 'disabled'),
|
607 |
[
|
@@ -616,15 +555,12 @@ app.layout = dbc.Container([
|
|
616 |
State('upload-proposal', 'filename'),
|
617 |
Input({'type': 'delete-proposal-btn', 'index': ALL, 'group': 'proposal'}, 'n_clicks'),
|
618 |
State('select-proposal-dropdown', 'value'),
|
619 |
-
Input({'type': 'delete-generated-btn', 'index': ALL, 'group': 'generated'}, 'n_clicks'),
|
620 |
-
State('select-generated-dropdown', 'value'),
|
621 |
Input({'type': 'delete-shredded-btn', 'index': ALL, 'group': 'shredded'}, 'n_clicks'),
|
622 |
State('shredded-doc-list', 'children'),
|
623 |
-
|
624 |
State('chat-input', 'value'),
|
625 |
State('select-document-dropdown', 'value'),
|
626 |
State('select-proposal-dropdown', 'value'),
|
627 |
-
State('select-generated-dropdown', 'value'),
|
628 |
Input('cancel-action-btn', 'n_clicks'),
|
629 |
Input('stream-interval', 'n_intervals'),
|
630 |
State('stream-status', 'data')
|
@@ -635,10 +571,9 @@ def master_callback(
|
|
635 |
shred_clicks, proposal_clicks, compliance_clicks,
|
636 |
rfp_content, rfp_filename, rfp_delete_clicks, selected_doc,
|
637 |
proposal_content, proposal_filename, proposal_delete_clicks, selected_proposal,
|
638 |
-
generated_delete_clicks, selected_generated,
|
639 |
shredded_delete_clicks, shredded_doc_children,
|
640 |
-
|
641 |
-
chat_input, selected_filename, selected_proposal_dropdown,
|
642 |
cancel_clicks,
|
643 |
stream_n_intervals, stream_status
|
644 |
):
|
@@ -659,8 +594,7 @@ def master_callback(
|
|
659 |
|
660 |
rfp_delete_clicks = safe_get_n_clicks(ctx, 5)
|
661 |
proposal_delete_clicks = safe_get_n_clicks(ctx, 9)
|
662 |
-
|
663 |
-
shredded_delete_clicks = safe_get_n_clicks(ctx, 13)
|
664 |
|
665 |
uploaded_rfp_decoded_bytes = None
|
666 |
|
@@ -668,7 +602,7 @@ def master_callback(
|
|
668 |
|
669 |
if triggered_id == 'stream-interval':
|
670 |
if not stream_status or not stream_status.get('streaming'):
|
671 |
-
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update,
|
672 |
preview = stream_buffer.get("preview", "")
|
673 |
still_streaming = gemini_lock.locked()
|
674 |
if not still_streaming:
|
@@ -678,20 +612,18 @@ def master_callback(
|
|
678 |
doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
679 |
shredded_doc_list_items = get_shredded_doc_list(shredded_documents)
|
680 |
uploaded_doc_list = get_uploaded_doc_list(uploaded_documents)
|
681 |
-
|
682 |
-
|
683 |
-
|
684 |
-
generated_rfp_section = get_generated_rfp_download_section(generated_doc_value)
|
685 |
return (
|
686 |
shred_store, proposal_store, output_data_upload,
|
687 |
uploaded_doc_list, doc_options, doc_value,
|
688 |
shredded_doc_list_items,
|
689 |
-
|
690 |
-
generated_rfp_section,
|
691 |
stream_status,
|
692 |
True
|
693 |
)
|
694 |
-
return dash.no_update, dash.no_update, dcc.Markdown(preview, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"}), dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update,
|
695 |
|
696 |
if triggered_id == 'cancel-action-btn':
|
697 |
stream_event.set()
|
@@ -701,16 +633,14 @@ def master_callback(
|
|
701 |
doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
702 |
shredded_doc_list_items = get_shredded_doc_list(shredded_documents)
|
703 |
uploaded_doc_list = get_uploaded_doc_list(uploaded_documents)
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
generated_rfp_section = get_generated_rfp_download_section(generated_doc_value)
|
708 |
return (
|
709 |
shred_store, proposal_store, output_data_upload,
|
710 |
uploaded_doc_list, doc_options, doc_value,
|
711 |
shredded_doc_list_items,
|
712 |
-
|
713 |
-
generated_rfp_section,
|
714 |
{'streaming': False},
|
715 |
True
|
716 |
)
|
@@ -786,23 +716,10 @@ def master_callback(
|
|
786 |
upload_triggered = True
|
787 |
break
|
788 |
|
789 |
-
if triggered_id and isinstance(generated_delete_clicks, list):
|
790 |
-
for i, n_click in enumerate(generated_delete_clicks):
|
791 |
-
if n_click:
|
792 |
-
btn_id = ctx.inputs_list[11][i]['id']
|
793 |
-
del_filename = btn_id['index']
|
794 |
-
if del_filename in generated_documents:
|
795 |
-
del generated_documents[del_filename]
|
796 |
-
logging.info(f"Generated doc deleted: {del_filename}")
|
797 |
-
if selected_generated == del_filename:
|
798 |
-
selected_generated = None
|
799 |
-
upload_triggered = True
|
800 |
-
break
|
801 |
-
|
802 |
if triggered_id and isinstance(shredded_delete_clicks, list):
|
803 |
for i, n_click in enumerate(shredded_delete_clicks):
|
804 |
if n_click:
|
805 |
-
btn_id = ctx.inputs_list[
|
806 |
del_filename = btn_id['index']
|
807 |
if del_filename in shredded_documents:
|
808 |
del shredded_documents[del_filename]
|
@@ -814,10 +731,9 @@ def master_callback(
|
|
814 |
doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
815 |
shredded_doc_list_items = get_shredded_doc_list(shredded_documents)
|
816 |
uploaded_doc_list = get_uploaded_doc_list(uploaded_documents)
|
817 |
-
|
818 |
-
|
819 |
-
|
820 |
-
generated_rfp_section = get_generated_rfp_download_section(generated_doc_value)
|
821 |
|
822 |
output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
|
823 |
|
@@ -829,8 +745,7 @@ def master_callback(
|
|
829 |
shred_store, proposal_store, output_data_upload,
|
830 |
uploaded_doc_list, doc_options, doc_value,
|
831 |
shredded_doc_list_items,
|
832 |
-
|
833 |
-
generated_rfp_section,
|
834 |
{'streaming': False},
|
835 |
True
|
836 |
)
|
@@ -843,56 +758,41 @@ def master_callback(
|
|
843 |
finally:
|
844 |
gemini_lock.release()
|
845 |
action_name = "shred" if triggered_id=="shred-action-btn" else ("proposal" if triggered_id=="proposal-action-btn" else "compliance")
|
846 |
-
|
|
|
847 |
t.daemon = True
|
848 |
t.start()
|
849 |
output_data_upload = dcc.Markdown("Starting Gemini operation...", style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
|
|
|
850 |
return (
|
851 |
shred_store, proposal_store, output_data_upload,
|
852 |
uploaded_doc_list, doc_options, doc_value,
|
853 |
shredded_doc_list_items,
|
854 |
-
|
855 |
-
generated_rfp_section,
|
856 |
{'streaming': True},
|
857 |
False
|
858 |
)
|
859 |
|
860 |
-
if triggered_id == 'select-generated-dropdown':
|
861 |
-
generated_rfp_section = get_generated_rfp_download_section(select_generated_value)
|
862 |
-
return (
|
863 |
-
shred_store, proposal_store, output_data_upload,
|
864 |
-
uploaded_doc_list, doc_options, doc_value,
|
865 |
-
shredded_doc_list_items,
|
866 |
-
generated_doc_list, generated_doc_options, generated_doc_value,
|
867 |
-
generated_rfp_section,
|
868 |
-
{'streaming': False},
|
869 |
-
True
|
870 |
-
)
|
871 |
-
|
872 |
if upload_triggered:
|
873 |
doc_value = doc_value if doc_value in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
874 |
-
|
875 |
-
generated_rfp_section = get_generated_rfp_download_section(generated_doc_value)
|
876 |
output_data_upload = html.Div("Upload/Delete completed.", style={"wordWrap": "break-word"})
|
877 |
return (
|
878 |
shred_store, proposal_store, output_data_upload,
|
879 |
uploaded_doc_list, doc_options, doc_value,
|
880 |
shredded_doc_list_items,
|
881 |
-
|
882 |
-
generated_rfp_section,
|
883 |
{'streaming': False},
|
884 |
True
|
885 |
)
|
886 |
|
887 |
doc_value = doc_value if doc_value in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
888 |
-
|
889 |
-
generated_rfp_section = get_generated_rfp_download_section(generated_doc_value)
|
890 |
return (
|
891 |
shred_store, proposal_store, output_data_upload,
|
892 |
uploaded_doc_list, doc_options, doc_value,
|
893 |
shredded_doc_list_items,
|
894 |
-
|
895 |
-
generated_rfp_section,
|
896 |
{'streaming': False},
|
897 |
True
|
898 |
)
|
|
|
33 |
uploaded_documents_fileid = {}
|
34 |
uploaded_proposals = {}
|
35 |
uploaded_proposals_fileid = {}
|
|
|
36 |
shredded_documents = {}
|
37 |
shredded_document = None
|
38 |
generated_response = None
|
|
|
145 |
memf.seek(0)
|
146 |
return memf.read()
|
147 |
|
148 |
+
def save_compliance_as_docx(compliance_text, rfp_filename):
|
149 |
+
doc = Document()
|
150 |
+
doc.add_heading(f"Compliance Check for {rfp_filename}", 0)
|
151 |
+
for line in compliance_text.split('\n'):
|
152 |
+
doc.add_paragraph(line)
|
153 |
+
memf = io.BytesIO()
|
154 |
+
doc.save(memf)
|
155 |
+
memf.seek(0)
|
156 |
+
return memf.read()
|
157 |
+
|
158 |
def process_document(action, selected_filename=None, chat_input=None, rfp_decoded_bytes=None, cancel_event=None, stream=True, selected_generated_filename=None):
|
159 |
global shredded_document, generated_response, stream_buffer
|
160 |
|
|
|
162 |
|
163 |
doc_content = None
|
164 |
doc_fileid = None
|
165 |
+
if action in ["shred", "proposal", "compliance"]:
|
166 |
if selected_filename and selected_filename in uploaded_documents:
|
167 |
doc_content = uploaded_documents[selected_filename]
|
168 |
doc_fileid = uploaded_documents_fileid.get(selected_filename)
|
|
|
267 |
if not cancel_event or not cancel_event.is_set():
|
268 |
docx_bytes = save_proposal_as_docx(response, rfp_filename)
|
269 |
generated_docx_name = f"{os.path.splitext(rfp_filename)[0]}_proposal.docx"
|
270 |
+
uploaded_proposals[generated_docx_name] = response
|
271 |
+
uploaded_proposals_fileid[generated_docx_name] = None
|
272 |
result_holder["text"] = response
|
273 |
result_holder["docx_name"] = generated_docx_name
|
274 |
else:
|
|
|
287 |
return result_holder["text"], None, result_holder["docx_name"], result_holder["text"]
|
288 |
|
289 |
elif action == 'compliance':
|
290 |
+
if not selected_generated_filename or selected_generated_filename not in uploaded_proposals:
|
|
|
|
|
291 |
return "No generated document selected for compliance.", None, None, None
|
292 |
if not selected_filename or selected_filename not in uploaded_documents:
|
293 |
return "No RFP/SOW/PWS/RFI document selected for compliance.", None, None, None
|
294 |
|
|
|
295 |
rfp_text = uploaded_documents[selected_filename]
|
296 |
+
proposal_text = uploaded_proposals[selected_generated_filename]
|
297 |
logging.info(f"Compliance check: comparing proposal [{selected_generated_filename}] to RFP [{selected_filename}]")
|
298 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
299 |
prompt = (
|
300 |
"You are a proposal compliance expert. Use the following RFP/SOW/PWS/RFI and the generated proposal response. "
|
301 |
"Compare the proposal to the RFP requirements and win themes. "
|
|
|
321 |
break
|
322 |
result = stream_buffer["preview"]
|
323 |
result_holder["text"] = result
|
324 |
+
if not cancel_event or not cancel_event.is_set():
|
325 |
+
docx_bytes = save_compliance_as_docx(result, selected_filename)
|
326 |
+
compliance_docx_name = f"{os.path.splitext(selected_filename)[0]}_compliance_check.docx"
|
327 |
+
uploaded_documents[compliance_docx_name] = result
|
328 |
+
shredded_documents[compliance_docx_name] = docx_bytes
|
329 |
+
result_holder["docx_name"] = compliance_docx_name
|
330 |
+
else:
|
331 |
+
result_holder["docx_name"] = None
|
332 |
logging.info("Compliance check completed.")
|
333 |
except Exception as e:
|
334 |
logging.error("Error during compliance check: %s", e)
|
|
|
338 |
t = Thread(target=thread_compliance)
|
339 |
t.start()
|
340 |
t.join()
|
341 |
+
return result_holder["text"], None, result_holder["docx_name"], result_holder["text"]
|
342 |
|
343 |
elif action == 'recover':
|
344 |
return "Recovery not implemented yet.", None, None, None
|
|
|
354 |
doc_list = []
|
355 |
for filename in docdict:
|
356 |
file_content = docdict[filename]
|
357 |
+
mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" if filename.lower().endswith('.docx') else "text/plain"
|
358 |
+
try:
|
359 |
+
if filename.lower().endswith('.docx') and filename in shredded_documents:
|
360 |
+
docx_bytes = shredded_documents[filename]
|
|
|
361 |
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
362 |
+
else:
|
363 |
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
364 |
+
except Exception:
|
|
|
365 |
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
366 |
download_link = html.A(
|
367 |
filename,
|
|
|
405 |
doc_list = []
|
406 |
for filename in docdict:
|
407 |
file_content = docdict[filename]
|
408 |
+
mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
409 |
+
try:
|
410 |
+
docx_bytes = save_proposal_as_docx(file_content, filename)
|
411 |
+
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
412 |
+
except Exception:
|
|
|
|
|
|
|
|
|
|
|
413 |
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
414 |
download_link = html.A(
|
415 |
filename,
|
|
|
426 |
)
|
427 |
return dbc.ListGroup(doc_list, flush=True)
|
428 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
429 |
app.layout = dbc.Container([
|
430 |
dcc.Store(id='shred-store', data={'text': None, 'docx_name': None}),
|
431 |
dcc.Store(id='proposal-store', data={'text': None, 'docx_name': None}),
|
|
|
464 |
multiple=False
|
465 |
),
|
466 |
html.Hr(style={"marginTop": "20px", "marginBottom": "10px"}),
|
467 |
+
html.H6("Shredded/Compliance Documents"),
|
468 |
html.Div(get_shredded_doc_list(shredded_documents), id='shredded-doc-list')
|
469 |
])
|
470 |
], className="mb-3"),
|
|
|
471 |
dbc.Card([
|
472 |
dbc.CardHeader(html.H5("Proposal")),
|
473 |
dbc.CardBody([
|
474 |
+
html.H6("Generated Proposals"),
|
475 |
html.Div(get_uploaded_proposal_list(uploaded_proposals), id='uploaded-proposal-list'),
|
476 |
dcc.Dropdown(
|
477 |
id='select-proposal-dropdown',
|
|
|
500 |
)
|
501 |
])
|
502 |
], className="mb-3"),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
503 |
], style={'minWidth': '260px', 'width':'30vw','maxWidth':'30vw'}, width=3),
|
504 |
|
505 |
dbc.Col([
|
|
|
538 |
Output('select-document-dropdown', 'options'),
|
539 |
Output('select-document-dropdown', 'value'),
|
540 |
Output('shredded-doc-list', 'children'),
|
541 |
+
Output('uploaded-proposal-list', 'children'),
|
542 |
+
Output('select-proposal-dropdown', 'options'),
|
543 |
+
Output('select-proposal-dropdown', 'value'),
|
|
|
544 |
Output('stream-status', 'data'),
|
545 |
Output('stream-interval', 'disabled'),
|
546 |
[
|
|
|
555 |
State('upload-proposal', 'filename'),
|
556 |
Input({'type': 'delete-proposal-btn', 'index': ALL, 'group': 'proposal'}, 'n_clicks'),
|
557 |
State('select-proposal-dropdown', 'value'),
|
|
|
|
|
558 |
Input({'type': 'delete-shredded-btn', 'index': ALL, 'group': 'shredded'}, 'n_clicks'),
|
559 |
State('shredded-doc-list', 'children'),
|
560 |
+
State('select-proposal-dropdown', 'value'),
|
561 |
State('chat-input', 'value'),
|
562 |
State('select-document-dropdown', 'value'),
|
563 |
State('select-proposal-dropdown', 'value'),
|
|
|
564 |
Input('cancel-action-btn', 'n_clicks'),
|
565 |
Input('stream-interval', 'n_intervals'),
|
566 |
State('stream-status', 'data')
|
|
|
571 |
shred_clicks, proposal_clicks, compliance_clicks,
|
572 |
rfp_content, rfp_filename, rfp_delete_clicks, selected_doc,
|
573 |
proposal_content, proposal_filename, proposal_delete_clicks, selected_proposal,
|
|
|
574 |
shredded_delete_clicks, shredded_doc_children,
|
575 |
+
select_proposal_dropdown_value,
|
576 |
+
chat_input, selected_filename, selected_proposal_dropdown, selected_proposal_dropdown_state,
|
577 |
cancel_clicks,
|
578 |
stream_n_intervals, stream_status
|
579 |
):
|
|
|
594 |
|
595 |
rfp_delete_clicks = safe_get_n_clicks(ctx, 5)
|
596 |
proposal_delete_clicks = safe_get_n_clicks(ctx, 9)
|
597 |
+
shredded_delete_clicks = safe_get_n_clicks(ctx, 11)
|
|
|
598 |
|
599 |
uploaded_rfp_decoded_bytes = None
|
600 |
|
|
|
602 |
|
603 |
if triggered_id == 'stream-interval':
|
604 |
if not stream_status or not stream_status.get('streaming'):
|
605 |
+
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, stream_status, True
|
606 |
preview = stream_buffer.get("preview", "")
|
607 |
still_streaming = gemini_lock.locked()
|
608 |
if not still_streaming:
|
|
|
612 |
doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
613 |
shredded_doc_list_items = get_shredded_doc_list(shredded_documents)
|
614 |
uploaded_doc_list = get_uploaded_doc_list(uploaded_documents)
|
615 |
+
uploaded_proposal_list = get_uploaded_proposal_list(uploaded_proposals)
|
616 |
+
proposal_options = [{'label': fn, 'value': fn} for fn in uploaded_proposals.keys()]
|
617 |
+
proposal_value = select_proposal_dropdown_value if select_proposal_dropdown_value in uploaded_proposals else (next(iter(uploaded_proposals), None) if uploaded_proposals else None)
|
|
|
618 |
return (
|
619 |
shred_store, proposal_store, output_data_upload,
|
620 |
uploaded_doc_list, doc_options, doc_value,
|
621 |
shredded_doc_list_items,
|
622 |
+
uploaded_proposal_list, proposal_options, proposal_value,
|
|
|
623 |
stream_status,
|
624 |
True
|
625 |
)
|
626 |
+
return dash.no_update, dash.no_update, dcc.Markdown(preview, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"}), dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, stream_status, False
|
627 |
|
628 |
if triggered_id == 'cancel-action-btn':
|
629 |
stream_event.set()
|
|
|
633 |
doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
634 |
shredded_doc_list_items = get_shredded_doc_list(shredded_documents)
|
635 |
uploaded_doc_list = get_uploaded_doc_list(uploaded_documents)
|
636 |
+
uploaded_proposal_list = get_uploaded_proposal_list(uploaded_proposals)
|
637 |
+
proposal_options = [{'label': fn, 'value': fn} for fn in uploaded_proposals.keys()]
|
638 |
+
proposal_value = select_proposal_dropdown_value if select_proposal_dropdown_value in uploaded_proposals else (next(iter(uploaded_proposals), None) if uploaded_proposals else None)
|
|
|
639 |
return (
|
640 |
shred_store, proposal_store, output_data_upload,
|
641 |
uploaded_doc_list, doc_options, doc_value,
|
642 |
shredded_doc_list_items,
|
643 |
+
uploaded_proposal_list, proposal_options, proposal_value,
|
|
|
644 |
{'streaming': False},
|
645 |
True
|
646 |
)
|
|
|
716 |
upload_triggered = True
|
717 |
break
|
718 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
719 |
if triggered_id and isinstance(shredded_delete_clicks, list):
|
720 |
for i, n_click in enumerate(shredded_delete_clicks):
|
721 |
if n_click:
|
722 |
+
btn_id = ctx.inputs_list[11][i]['id']
|
723 |
del_filename = btn_id['index']
|
724 |
if del_filename in shredded_documents:
|
725 |
del shredded_documents[del_filename]
|
|
|
731 |
doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
732 |
shredded_doc_list_items = get_shredded_doc_list(shredded_documents)
|
733 |
uploaded_doc_list = get_uploaded_doc_list(uploaded_documents)
|
734 |
+
uploaded_proposal_list = get_uploaded_proposal_list(uploaded_proposals)
|
735 |
+
proposal_options = [{'label': fn, 'value': fn} for fn in uploaded_proposals.keys()]
|
736 |
+
proposal_value = select_proposal_dropdown_value if select_proposal_dropdown_value in uploaded_proposals else (next(iter(uploaded_proposals), None) if uploaded_proposals else None)
|
|
|
737 |
|
738 |
output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
|
739 |
|
|
|
745 |
shred_store, proposal_store, output_data_upload,
|
746 |
uploaded_doc_list, doc_options, doc_value,
|
747 |
shredded_doc_list_items,
|
748 |
+
uploaded_proposal_list, proposal_options, proposal_value,
|
|
|
749 |
{'streaming': False},
|
750 |
True
|
751 |
)
|
|
|
758 |
finally:
|
759 |
gemini_lock.release()
|
760 |
action_name = "shred" if triggered_id=="shred-action-btn" else ("proposal" if triggered_id=="proposal-action-btn" else "compliance")
|
761 |
+
# For compliance, select_proposal_dropdown_value is the proposal doc
|
762 |
+
t = Thread(target=stream_gemini_thread, args=(action_name, doc_value, chat_input, uploaded_rfp_decoded_bytes, select_proposal_dropdown_value))
|
763 |
t.daemon = True
|
764 |
t.start()
|
765 |
output_data_upload = dcc.Markdown("Starting Gemini operation...", style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
|
766 |
+
# After action is started, future stream-interval will update the lists
|
767 |
return (
|
768 |
shred_store, proposal_store, output_data_upload,
|
769 |
uploaded_doc_list, doc_options, doc_value,
|
770 |
shredded_doc_list_items,
|
771 |
+
uploaded_proposal_list, proposal_options, proposal_value,
|
|
|
772 |
{'streaming': True},
|
773 |
False
|
774 |
)
|
775 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
776 |
if upload_triggered:
|
777 |
doc_value = doc_value if doc_value in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
778 |
+
proposal_value = proposal_value if proposal_value in uploaded_proposals else (next(iter(uploaded_proposals), None) if uploaded_proposals else None)
|
|
|
779 |
output_data_upload = html.Div("Upload/Delete completed.", style={"wordWrap": "break-word"})
|
780 |
return (
|
781 |
shred_store, proposal_store, output_data_upload,
|
782 |
uploaded_doc_list, doc_options, doc_value,
|
783 |
shredded_doc_list_items,
|
784 |
+
uploaded_proposal_list, proposal_options, proposal_value,
|
|
|
785 |
{'streaming': False},
|
786 |
True
|
787 |
)
|
788 |
|
789 |
doc_value = doc_value if doc_value in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
|
790 |
+
proposal_value = proposal_value if proposal_value in uploaded_proposals else (next(iter(uploaded_proposals), None) if uploaded_proposals else None)
|
|
|
791 |
return (
|
792 |
shred_store, proposal_store, output_data_upload,
|
793 |
uploaded_doc_list, doc_options, doc_value,
|
794 |
shredded_doc_list_items,
|
795 |
+
uploaded_proposal_list, proposal_options, proposal_value,
|
|
|
796 |
{'streaming': False},
|
797 |
True
|
798 |
)
|