Spaces:
Paused
Paused
Update app.py via AI Editor
Browse files
app.py
CHANGED
|
@@ -6,7 +6,6 @@ from dash import dcc, html, Input, Output, State, callback_context
|
|
| 6 |
import dash_bootstrap_components as dbc
|
| 7 |
import pandas as pd
|
| 8 |
import anthropic
|
| 9 |
-
from threading import Thread
|
| 10 |
import logging
|
| 11 |
from docx import Document
|
| 12 |
|
|
@@ -63,6 +62,19 @@ def anthropic_stream_generate(prompt):
|
|
| 63 |
logging.error("Error during anthropic streaming request: %s", e)
|
| 64 |
return f"Error during streaming: {e}"
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
def save_shredded_as_docx(shredded_text, rfp_filename):
|
| 67 |
doc = Document()
|
| 68 |
doc.add_heading(f"Shredded Requirements for {rfp_filename}", 0)
|
|
@@ -73,7 +85,7 @@ def save_shredded_as_docx(shredded_text, rfp_filename):
|
|
| 73 |
memf.seek(0)
|
| 74 |
return memf.read()
|
| 75 |
|
| 76 |
-
def process_document(action, selected_filename=None, chat_input=None, selected_proposal=None):
|
| 77 |
global shredded_document, generated_response
|
| 78 |
logging.info(f"Process document called with action: {action}")
|
| 79 |
|
|
@@ -127,6 +139,7 @@ def process_document(action, selected_filename=None, chat_input=None, selected_p
|
|
| 127 |
logging.error("Error in thread_shred: %s", e)
|
| 128 |
result_holder["text"] = shredded_document
|
| 129 |
shredded_document = "Shredding in progress..."
|
|
|
|
| 130 |
t = Thread(target=thread_shred)
|
| 131 |
t.start()
|
| 132 |
t.join()
|
|
@@ -159,15 +172,52 @@ def process_document(action, selected_filename=None, chat_input=None, selected_p
|
|
| 159 |
logging.error("Error in thread_generate: %s", e)
|
| 160 |
result_holder["text"] = generated_response
|
| 161 |
generated_response = "Generating response..."
|
|
|
|
| 162 |
t = Thread(target=thread_generate)
|
| 163 |
t.start()
|
| 164 |
t.join()
|
| 165 |
return result_holder["text"], None, None
|
| 166 |
|
| 167 |
elif action == 'proposal':
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
elif action == 'compliance':
|
| 172 |
return "Compliance checking not implemented yet.", None, None
|
| 173 |
elif action == 'recover':
|
|
@@ -374,10 +424,8 @@ def master_callback(
|
|
| 374 |
ctx = callback_context
|
| 375 |
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
|
| 376 |
|
| 377 |
-
# Upload or delete document or proposal or generated document
|
| 378 |
upload_triggered = False
|
| 379 |
|
| 380 |
-
# Handle upload document
|
| 381 |
if triggered_id == 'upload-document' and rfp_content is not None and rfp_filename:
|
| 382 |
content_type, content_string = rfp_content.split(',')
|
| 383 |
decoded = base64.b64decode(content_string)
|
|
@@ -389,7 +437,6 @@ def master_callback(
|
|
| 389 |
logging.error(f"Failed to decode uploaded document: {rfp_filename}")
|
| 390 |
upload_triggered = True
|
| 391 |
|
| 392 |
-
# Handle upload proposal
|
| 393 |
if triggered_id == 'upload-proposal' and proposal_content is not None and proposal_filename:
|
| 394 |
content_type, content_string = proposal_content.split(',')
|
| 395 |
decoded = base64.b64decode(content_string)
|
|
@@ -401,7 +448,6 @@ def master_callback(
|
|
| 401 |
logging.error(f"Failed to decode uploaded proposal: {proposal_filename}")
|
| 402 |
upload_triggered = True
|
| 403 |
|
| 404 |
-
# Handle delete document
|
| 405 |
if triggered_id and isinstance(ctx.inputs_list[2], list):
|
| 406 |
for i, n_click in enumerate(rfp_delete_clicks):
|
| 407 |
if n_click:
|
|
@@ -415,7 +461,6 @@ def master_callback(
|
|
| 415 |
upload_triggered = True
|
| 416 |
break
|
| 417 |
|
| 418 |
-
# Handle delete proposal
|
| 419 |
if triggered_id and isinstance(ctx.inputs_list[6], list):
|
| 420 |
for i, n_click in enumerate(proposal_delete_clicks):
|
| 421 |
if n_click:
|
|
@@ -429,7 +474,6 @@ def master_callback(
|
|
| 429 |
upload_triggered = True
|
| 430 |
break
|
| 431 |
|
| 432 |
-
# Handle delete generated document
|
| 433 |
if triggered_id and isinstance(ctx.inputs_list[10], list):
|
| 434 |
for i, n_click in enumerate(generated_delete_clicks):
|
| 435 |
if n_click:
|
|
@@ -454,7 +498,6 @@ def master_callback(
|
|
| 454 |
uploaded_proposal_list = get_uploaded_proposal_list(uploaded_proposals)
|
| 455 |
generated_doc_list = get_generated_doc_list(generated_documents)
|
| 456 |
|
| 457 |
-
# Default output
|
| 458 |
output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
|
| 459 |
|
| 460 |
action_buttons = [
|
|
@@ -462,7 +505,6 @@ def master_callback(
|
|
| 462 |
'recover-action-btn', 'board-action-btn', 'loe-action-btn'
|
| 463 |
]
|
| 464 |
|
| 465 |
-
# Handle action buttons
|
| 466 |
if triggered_id in action_buttons:
|
| 467 |
result = ""
|
| 468 |
generated_docx_bytes = None
|
|
@@ -476,7 +518,7 @@ def master_callback(
|
|
| 476 |
logging.info(f"Generated docx saved: {generated_docx_name}")
|
| 477 |
new_selected_generated = generated_docx_name
|
| 478 |
elif triggered_id == 'generate-action-btn':
|
| 479 |
-
result, _, _ = process_document('
|
| 480 |
elif triggered_id == 'compliance-action-btn':
|
| 481 |
result, _, _ = process_document('compliance', selected_filename, chat_input)
|
| 482 |
elif triggered_id == 'recover-action-btn':
|
|
@@ -499,7 +541,6 @@ def master_callback(
|
|
| 499 |
generated_doc_value = new_selected_generated if new_selected_generated in generated_documents else (next(iter(generated_documents), None) if generated_documents else None)
|
| 500 |
generated_doc_list = get_generated_doc_list(generated_documents)
|
| 501 |
|
| 502 |
-
# Handle select-generated-dropdown for download
|
| 503 |
elif triggered_id == 'select-generated-dropdown':
|
| 504 |
sel_gen = selected_generated_dropdown
|
| 505 |
if not sel_gen or sel_gen not in generated_documents:
|
|
|
|
| 6 |
import dash_bootstrap_components as dbc
|
| 7 |
import pandas as pd
|
| 8 |
import anthropic
|
|
|
|
| 9 |
import logging
|
| 10 |
from docx import Document
|
| 11 |
|
|
|
|
| 62 |
logging.error("Error during anthropic streaming request: %s", e)
|
| 63 |
return f"Error during streaming: {e}"
|
| 64 |
|
| 65 |
+
def anthropic_sync_generate(prompt):
|
| 66 |
+
try:
|
| 67 |
+
response = anthropic_client.messages.create(
|
| 68 |
+
model=CLAUDE3_SONNET_MODEL,
|
| 69 |
+
max_tokens=CLAUDE3_MAX_OUTPUT_TOKENS,
|
| 70 |
+
messages=[{"role": "user", "content": prompt}]
|
| 71 |
+
)
|
| 72 |
+
logging.info("Synchronous anthropic generation complete.")
|
| 73 |
+
return response.content[0].text if hasattr(response, "content") else ""
|
| 74 |
+
except Exception as e:
|
| 75 |
+
logging.error("Error during anthropic synchronous request: %s", e)
|
| 76 |
+
return f"Error during synchronous call: {e}"
|
| 77 |
+
|
| 78 |
def save_shredded_as_docx(shredded_text, rfp_filename):
|
| 79 |
doc = Document()
|
| 80 |
doc.add_heading(f"Shredded Requirements for {rfp_filename}", 0)
|
|
|
|
| 85 |
memf.seek(0)
|
| 86 |
return memf.read()
|
| 87 |
|
| 88 |
+
def process_document(action, selected_filename=None, chat_input=None, selected_proposal=None, selected_generated=None):
|
| 89 |
global shredded_document, generated_response
|
| 90 |
logging.info(f"Process document called with action: {action}")
|
| 91 |
|
|
|
|
| 139 |
logging.error("Error in thread_shred: %s", e)
|
| 140 |
result_holder["text"] = shredded_document
|
| 141 |
shredded_document = "Shredding in progress..."
|
| 142 |
+
from threading import Thread
|
| 143 |
t = Thread(target=thread_shred)
|
| 144 |
t.start()
|
| 145 |
t.join()
|
|
|
|
| 172 |
logging.error("Error in thread_generate: %s", e)
|
| 173 |
result_holder["text"] = generated_response
|
| 174 |
generated_response = "Generating response..."
|
| 175 |
+
from threading import Thread
|
| 176 |
t = Thread(target=thread_generate)
|
| 177 |
t.start()
|
| 178 |
t.join()
|
| 179 |
return result_holder["text"], None, None
|
| 180 |
|
| 181 |
elif action == 'proposal':
|
| 182 |
+
rfp_content = None
|
| 183 |
+
generated_doc_content = None
|
| 184 |
+
rfp_filename = selected_filename
|
| 185 |
+
generated_docname = selected_generated
|
| 186 |
+
if not (selected_filename and selected_filename in uploaded_documents):
|
| 187 |
+
return "No RFP/SOW/PWS/RFI document selected.", None, None
|
| 188 |
+
if not (selected_generated and selected_generated in generated_documents):
|
| 189 |
+
return "No generated document selected.", None, None
|
| 190 |
+
rfp_content = uploaded_documents[selected_filename]
|
| 191 |
+
gen_bytes = generated_documents[selected_generated]
|
| 192 |
+
try:
|
| 193 |
+
# Try to read as docx, fallback to decode
|
| 194 |
+
try:
|
| 195 |
+
docx_stream = io.BytesIO(gen_bytes)
|
| 196 |
+
doc = Document(docx_stream)
|
| 197 |
+
generated_doc_content = "\n".join([para.text for para in doc.paragraphs])
|
| 198 |
+
except Exception as e:
|
| 199 |
+
try:
|
| 200 |
+
generated_doc_content = gen_bytes.decode('utf-8')
|
| 201 |
+
except Exception:
|
| 202 |
+
generated_doc_content = "<Unable to decode generated document.>"
|
| 203 |
+
except Exception as e:
|
| 204 |
+
generated_doc_content = "<Unable to read generated document.>"
|
| 205 |
+
logging.error(f"Failed to read generated document: {e}")
|
| 206 |
+
|
| 207 |
+
prompt = (
|
| 208 |
+
"Respond to the following RFP/SOW/PWS/RFI provided and focus on the Generated Document provided by creating a highly detailed proposal response that follows each section and subsection header and numbering. "
|
| 209 |
+
"The response to each section and subsection will be compliant and compelling, focusing on describing the approach, and how the labor category uses a specific industry standard process in a workflow described in steps, and how technology is used. "
|
| 210 |
+
"You must show innovation in the approach. Refer to research that validates the approach and cite sources with measurable outcome. "
|
| 211 |
+
"Be sure to respond in paragraph format, significantly limiting the use of bullets.\n"
|
| 212 |
+
)
|
| 213 |
+
if chat_input:
|
| 214 |
+
prompt += f"User additional instructions: {chat_input}\n"
|
| 215 |
+
prompt += f"\n---\nRFP/SOW/PWS/RFI ({rfp_filename}):\n{rfp_content}\n"
|
| 216 |
+
prompt += f"\n---\nGenerated Document ({generated_docname}):\n{generated_doc_content}\n"
|
| 217 |
+
logging.info("Sending proposal prompt to anthropic. This is a synchronous call.")
|
| 218 |
+
result = anthropic_sync_generate(prompt)
|
| 219 |
+
return result, None, None
|
| 220 |
+
|
| 221 |
elif action == 'compliance':
|
| 222 |
return "Compliance checking not implemented yet.", None, None
|
| 223 |
elif action == 'recover':
|
|
|
|
| 424 |
ctx = callback_context
|
| 425 |
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
|
| 426 |
|
|
|
|
| 427 |
upload_triggered = False
|
| 428 |
|
|
|
|
| 429 |
if triggered_id == 'upload-document' and rfp_content is not None and rfp_filename:
|
| 430 |
content_type, content_string = rfp_content.split(',')
|
| 431 |
decoded = base64.b64decode(content_string)
|
|
|
|
| 437 |
logging.error(f"Failed to decode uploaded document: {rfp_filename}")
|
| 438 |
upload_triggered = True
|
| 439 |
|
|
|
|
| 440 |
if triggered_id == 'upload-proposal' and proposal_content is not None and proposal_filename:
|
| 441 |
content_type, content_string = proposal_content.split(',')
|
| 442 |
decoded = base64.b64decode(content_string)
|
|
|
|
| 448 |
logging.error(f"Failed to decode uploaded proposal: {proposal_filename}")
|
| 449 |
upload_triggered = True
|
| 450 |
|
|
|
|
| 451 |
if triggered_id and isinstance(ctx.inputs_list[2], list):
|
| 452 |
for i, n_click in enumerate(rfp_delete_clicks):
|
| 453 |
if n_click:
|
|
|
|
| 461 |
upload_triggered = True
|
| 462 |
break
|
| 463 |
|
|
|
|
| 464 |
if triggered_id and isinstance(ctx.inputs_list[6], list):
|
| 465 |
for i, n_click in enumerate(proposal_delete_clicks):
|
| 466 |
if n_click:
|
|
|
|
| 474 |
upload_triggered = True
|
| 475 |
break
|
| 476 |
|
|
|
|
| 477 |
if triggered_id and isinstance(ctx.inputs_list[10], list):
|
| 478 |
for i, n_click in enumerate(generated_delete_clicks):
|
| 479 |
if n_click:
|
|
|
|
| 498 |
uploaded_proposal_list = get_uploaded_proposal_list(uploaded_proposals)
|
| 499 |
generated_doc_list = get_generated_doc_list(generated_documents)
|
| 500 |
|
|
|
|
| 501 |
output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
|
| 502 |
|
| 503 |
action_buttons = [
|
|
|
|
| 505 |
'recover-action-btn', 'board-action-btn', 'loe-action-btn'
|
| 506 |
]
|
| 507 |
|
|
|
|
| 508 |
if triggered_id in action_buttons:
|
| 509 |
result = ""
|
| 510 |
generated_docx_bytes = None
|
|
|
|
| 518 |
logging.info(f"Generated docx saved: {generated_docx_name}")
|
| 519 |
new_selected_generated = generated_docx_name
|
| 520 |
elif triggered_id == 'generate-action-btn':
|
| 521 |
+
result, _, _ = process_document('proposal', selected_filename, chat_input, selected_proposal_dropdown, selected_generated_dropdown_state)
|
| 522 |
elif triggered_id == 'compliance-action-btn':
|
| 523 |
result, _, _ = process_document('compliance', selected_filename, chat_input)
|
| 524 |
elif triggered_id == 'recover-action-btn':
|
|
|
|
| 541 |
generated_doc_value = new_selected_generated if new_selected_generated in generated_documents else (next(iter(generated_documents), None) if generated_documents else None)
|
| 542 |
generated_doc_list = get_generated_doc_list(generated_documents)
|
| 543 |
|
|
|
|
| 544 |
elif triggered_id == 'select-generated-dropdown':
|
| 545 |
sel_gen = selected_generated_dropdown
|
| 546 |
if not sel_gen or sel_gen not in generated_documents:
|