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:
|