Spaces:
Paused
Paused
Update app.py via AI Editor
Browse files
app.py
CHANGED
@@ -132,7 +132,7 @@ def save_proposal_as_docx(proposal_text, base_filename):
|
|
132 |
memf.seek(0)
|
133 |
return memf.read()
|
134 |
|
135 |
-
def process_document(action, selected_filename=None, chat_input=None):
|
136 |
global shredded_document, generated_response
|
137 |
logging.info(f"Process document called with action: {action}")
|
138 |
|
@@ -195,6 +195,16 @@ def process_document(action, selected_filename=None, chat_input=None):
|
|
195 |
return "No RFP/SOW/PWS/RFI document selected.", None, None, None
|
196 |
rfp_filename = selected_filename
|
197 |
rfp_fileid = uploaded_documents_fileid.get(selected_filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
prompt = (
|
199 |
"Respond to the following RFP/SOW/PWS/RFI by creating a highly detailed proposal response that follows each section and subsection header. "
|
200 |
"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. "
|
@@ -207,9 +217,12 @@ def process_document(action, selected_filename=None, chat_input=None):
|
|
207 |
logging.info(f"Sending proposal prompt to Gemini. RFP: {rfp_filename}")
|
208 |
result_holder = {"text": None, "docx_name": None}
|
209 |
def thread_proposal():
|
|
|
|
|
210 |
try:
|
211 |
logging.info("Connecting to Gemini for proposal.")
|
212 |
response = gemini_generate_content(prompt, file_id=rfp_fileid, chat_input=chat_input)
|
|
|
213 |
logging.info("Received proposal results from Gemini.")
|
214 |
docx_bytes = save_proposal_as_docx(response, rfp_filename)
|
215 |
generated_docx_name = f"{os.path.splitext(rfp_filename)[0]}_proposal.docx"
|
@@ -217,8 +230,9 @@ def process_document(action, selected_filename=None, chat_input=None):
|
|
217 |
result_holder["text"] = response
|
218 |
result_holder["docx_name"] = generated_docx_name
|
219 |
except Exception as e:
|
|
|
220 |
logging.error("Error during Gemini proposal request: %s", e)
|
221 |
-
result_holder["text"] =
|
222 |
t = Thread(target=thread_proposal)
|
223 |
t.start()
|
224 |
t.join()
|
@@ -239,9 +253,32 @@ def get_uploaded_doc_list(docdict):
|
|
239 |
return html.Div("No documents uploaded.", style={"wordWrap": "break-word"})
|
240 |
doc_list = []
|
241 |
for filename in docdict:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
242 |
doc_list.append(
|
243 |
dbc.ListGroupItem([
|
244 |
-
|
245 |
dbc.Button("Delete", id={'type': 'delete-doc-btn', 'index': filename, 'group': 'rfp'}, size="sm", color="danger", className="float-end ms-2")
|
246 |
], className="d-flex justify-content-between align-items-center")
|
247 |
)
|
@@ -254,15 +291,14 @@ def get_shredded_doc_list(shreddict):
|
|
254 |
for filename in shreddict:
|
255 |
b64 = base64.b64encode(shreddict[filename]).decode('utf-8')
|
256 |
download_link = html.A(
|
257 |
-
|
258 |
href=f"data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{b64}",
|
259 |
download=filename,
|
260 |
target="_blank",
|
261 |
-
style={"wordWrap": "break-word", "marginRight": "10px"}
|
262 |
)
|
263 |
doc_list.append(
|
264 |
dbc.ListGroupItem([
|
265 |
-
html.Span(filename, style={"wordWrap": "break-word", "marginRight": "10px"}),
|
266 |
download_link,
|
267 |
dbc.Button("Delete", id={'type': 'delete-shredded-btn', 'index': filename, 'group': 'shredded'}, size="sm", color="danger", className="float-end ms-2")
|
268 |
], className="d-flex justify-content-between align-items-center")
|
@@ -274,9 +310,28 @@ def get_uploaded_proposal_list(docdict):
|
|
274 |
return html.Div("No proposal documents uploaded.", style={"wordWrap": "break-word"})
|
275 |
doc_list = []
|
276 |
for filename in docdict:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
277 |
doc_list.append(
|
278 |
dbc.ListGroupItem([
|
279 |
-
|
280 |
dbc.Button("Delete", id={'type': 'delete-proposal-btn', 'index': filename, 'group': 'proposal'}, size="sm", color="danger", className="float-end ms-2")
|
281 |
], className="d-flex justify-content-between align-items-center")
|
282 |
)
|
@@ -287,9 +342,18 @@ def get_generated_doc_list(docdict):
|
|
287 |
return html.Div("No generated documents yet.", style={"wordWrap": "break-word"})
|
288 |
doc_list = []
|
289 |
for filename in docdict:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
290 |
doc_list.append(
|
291 |
dbc.ListGroupItem([
|
292 |
-
|
293 |
dbc.Button("Delete", id={'type': 'delete-generated-btn', 'index': filename, 'group': 'generated'}, size="sm", color="danger", className="float-end ms-2")
|
294 |
], className="d-flex justify-content-between align-items-center")
|
295 |
)
|
@@ -494,9 +558,12 @@ def master_callback(
|
|
494 |
generated_delete_clicks = safe_get_n_clicks(ctx, 10)
|
495 |
shredded_delete_clicks = safe_get_n_clicks(ctx, 12)
|
496 |
|
|
|
|
|
497 |
if triggered_id == 'upload-document' and rfp_content is not None and rfp_filename:
|
498 |
content_type, content_string = rfp_content.split(',')
|
499 |
decoded = base64.b64decode(content_string)
|
|
|
500 |
text = decode_document(decoded)
|
501 |
fileid = None
|
502 |
if rfp_filename.lower().endswith(('.pdf', '.docx', '.xlsx', '.xls')):
|
@@ -600,7 +667,7 @@ def master_callback(
|
|
600 |
output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
|
601 |
|
602 |
if triggered_id == 'shred-action-btn':
|
603 |
-
output_data_upload = dcc.Loading(type="default", children=html.Div("Shredding document...", style={"wordWrap": "break-word"}))
|
604 |
shred_text, _, shredded_docx_name, shredded_text = process_document('shred', doc_value, chat_input)
|
605 |
shred_store = {'text': shred_text, 'docx_name': shredded_docx_name}
|
606 |
|
@@ -620,8 +687,16 @@ def master_callback(
|
|
620 |
)
|
621 |
|
622 |
if triggered_id == 'proposal-action-btn':
|
623 |
-
output_data_upload = dcc.Loading(type="default", children=html.Div("Generating proposal...", style={"wordWrap": "break-word"}))
|
624 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
625 |
proposal_store = {'text': proposal_text, 'docx_name': proposal_docx_name}
|
626 |
new_generated_doc_value = generated_doc_value
|
627 |
if proposal_docx_name:
|
|
|
132 |
memf.seek(0)
|
133 |
return memf.read()
|
134 |
|
135 |
+
def process_document(action, selected_filename=None, chat_input=None, rfp_decoded_bytes=None):
|
136 |
global shredded_document, generated_response
|
137 |
logging.info(f"Process document called with action: {action}")
|
138 |
|
|
|
195 |
return "No RFP/SOW/PWS/RFI document selected.", None, None, None
|
196 |
rfp_filename = selected_filename
|
197 |
rfp_fileid = uploaded_documents_fileid.get(selected_filename)
|
198 |
+
# Upload file to Gemini if not already uploaded (if rfp_decoded_bytes provided and no fileid)
|
199 |
+
if not rfp_fileid and rfp_decoded_bytes is not None:
|
200 |
+
try:
|
201 |
+
fileid = upload_to_gemini_file(rfp_decoded_bytes, rfp_filename)
|
202 |
+
if fileid:
|
203 |
+
uploaded_documents_fileid[rfp_filename] = fileid
|
204 |
+
rfp_fileid = fileid
|
205 |
+
logging.info(f"RFP file {rfp_filename} uploaded to Gemini for proposal.")
|
206 |
+
except Exception as e:
|
207 |
+
logging.error(f"Failed to upload RFP file {rfp_filename} for proposal: {e}")
|
208 |
prompt = (
|
209 |
"Respond to the following RFP/SOW/PWS/RFI by creating a highly detailed proposal response that follows each section and subsection header. "
|
210 |
"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. "
|
|
|
217 |
logging.info(f"Sending proposal prompt to Gemini. RFP: {rfp_filename}")
|
218 |
result_holder = {"text": None, "docx_name": None}
|
219 |
def thread_proposal():
|
220 |
+
global generated_response
|
221 |
+
generated_response = ""
|
222 |
try:
|
223 |
logging.info("Connecting to Gemini for proposal.")
|
224 |
response = gemini_generate_content(prompt, file_id=rfp_fileid, chat_input=chat_input)
|
225 |
+
generated_response = response
|
226 |
logging.info("Received proposal results from Gemini.")
|
227 |
docx_bytes = save_proposal_as_docx(response, rfp_filename)
|
228 |
generated_docx_name = f"{os.path.splitext(rfp_filename)[0]}_proposal.docx"
|
|
|
230 |
result_holder["text"] = response
|
231 |
result_holder["docx_name"] = generated_docx_name
|
232 |
except Exception as e:
|
233 |
+
generated_response = f"Error during Gemini completion: {e}"
|
234 |
logging.error("Error during Gemini proposal request: %s", e)
|
235 |
+
result_holder["text"] = generated_response
|
236 |
t = Thread(target=thread_proposal)
|
237 |
t.start()
|
238 |
t.join()
|
|
|
253 |
return html.Div("No documents uploaded.", style={"wordWrap": "break-word"})
|
254 |
doc_list = []
|
255 |
for filename in docdict:
|
256 |
+
file_content = docdict[filename]
|
257 |
+
# Always encode as docx for download, but if not docx, encode as txt
|
258 |
+
if filename.lower().endswith('.docx'):
|
259 |
+
mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
260 |
+
b64 = None
|
261 |
+
# docx as text, not as file: re-encode to docx for download
|
262 |
+
try:
|
263 |
+
# If it's a docx, but only text available, save as docx
|
264 |
+
docx_bytes = save_shredded_as_docx(file_content, filename)
|
265 |
+
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
266 |
+
except Exception:
|
267 |
+
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
268 |
+
else:
|
269 |
+
mime = "text/plain"
|
270 |
+
# Always encode as utf-8 text for download
|
271 |
+
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
272 |
+
download_link = html.A(
|
273 |
+
filename,
|
274 |
+
href=f"data:{mime};base64,{b64}",
|
275 |
+
download=filename,
|
276 |
+
target="_blank",
|
277 |
+
style={"wordWrap": "break-word", "marginRight": "10px", "textDecoration": "underline"}
|
278 |
+
)
|
279 |
doc_list.append(
|
280 |
dbc.ListGroupItem([
|
281 |
+
download_link,
|
282 |
dbc.Button("Delete", id={'type': 'delete-doc-btn', 'index': filename, 'group': 'rfp'}, size="sm", color="danger", className="float-end ms-2")
|
283 |
], className="d-flex justify-content-between align-items-center")
|
284 |
)
|
|
|
291 |
for filename in shreddict:
|
292 |
b64 = base64.b64encode(shreddict[filename]).decode('utf-8')
|
293 |
download_link = html.A(
|
294 |
+
filename,
|
295 |
href=f"data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{b64}",
|
296 |
download=filename,
|
297 |
target="_blank",
|
298 |
+
style={"wordWrap": "break-word", "marginRight": "10px", "textDecoration": "underline"}
|
299 |
)
|
300 |
doc_list.append(
|
301 |
dbc.ListGroupItem([
|
|
|
302 |
download_link,
|
303 |
dbc.Button("Delete", id={'type': 'delete-shredded-btn', 'index': filename, 'group': 'shredded'}, size="sm", color="danger", className="float-end ms-2")
|
304 |
], className="d-flex justify-content-between align-items-center")
|
|
|
310 |
return html.Div("No proposal documents uploaded.", style={"wordWrap": "break-word"})
|
311 |
doc_list = []
|
312 |
for filename in docdict:
|
313 |
+
file_content = docdict[filename]
|
314 |
+
if filename.lower().endswith('.docx'):
|
315 |
+
mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
316 |
+
b64 = None
|
317 |
+
try:
|
318 |
+
docx_bytes = save_proposal_as_docx(file_content, filename)
|
319 |
+
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
320 |
+
except Exception:
|
321 |
+
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
322 |
+
else:
|
323 |
+
mime = "text/plain"
|
324 |
+
b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
|
325 |
+
download_link = html.A(
|
326 |
+
filename,
|
327 |
+
href=f"data:{mime};base64,{b64}",
|
328 |
+
download=filename,
|
329 |
+
target="_blank",
|
330 |
+
style={"wordWrap": "break-word", "marginRight": "10px", "textDecoration": "underline"}
|
331 |
+
)
|
332 |
doc_list.append(
|
333 |
dbc.ListGroupItem([
|
334 |
+
download_link,
|
335 |
dbc.Button("Delete", id={'type': 'delete-proposal-btn', 'index': filename, 'group': 'proposal'}, size="sm", color="danger", className="float-end ms-2")
|
336 |
], className="d-flex justify-content-between align-items-center")
|
337 |
)
|
|
|
342 |
return html.Div("No generated documents yet.", style={"wordWrap": "break-word"})
|
343 |
doc_list = []
|
344 |
for filename in docdict:
|
345 |
+
docx_bytes = docdict[filename]
|
346 |
+
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
347 |
+
download_link = html.A(
|
348 |
+
filename,
|
349 |
+
href=f"data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{b64}",
|
350 |
+
download=filename,
|
351 |
+
target="_blank",
|
352 |
+
style={"wordWrap": "break-word", "marginRight": "10px", "textDecoration": "underline"}
|
353 |
+
)
|
354 |
doc_list.append(
|
355 |
dbc.ListGroupItem([
|
356 |
+
download_link,
|
357 |
dbc.Button("Delete", id={'type': 'delete-generated-btn', 'index': filename, 'group': 'generated'}, size="sm", color="danger", className="float-end ms-2")
|
358 |
], className="d-flex justify-content-between align-items-center")
|
359 |
)
|
|
|
558 |
generated_delete_clicks = safe_get_n_clicks(ctx, 10)
|
559 |
shredded_delete_clicks = safe_get_n_clicks(ctx, 12)
|
560 |
|
561 |
+
uploaded_rfp_decoded_bytes = None
|
562 |
+
|
563 |
if triggered_id == 'upload-document' and rfp_content is not None and rfp_filename:
|
564 |
content_type, content_string = rfp_content.split(',')
|
565 |
decoded = base64.b64decode(content_string)
|
566 |
+
uploaded_rfp_decoded_bytes = decoded
|
567 |
text = decode_document(decoded)
|
568 |
fileid = None
|
569 |
if rfp_filename.lower().endswith(('.pdf', '.docx', '.xlsx', '.xls')):
|
|
|
667 |
output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
|
668 |
|
669 |
if triggered_id == 'shred-action-btn':
|
670 |
+
output_data_upload = dcc.Loading(type="default", children=html.Div("Shredding document...", style={"wordWrap": "break-word"}), style={"textAlign": "center"})
|
671 |
shred_text, _, shredded_docx_name, shredded_text = process_document('shred', doc_value, chat_input)
|
672 |
shred_store = {'text': shred_text, 'docx_name': shredded_docx_name}
|
673 |
|
|
|
687 |
)
|
688 |
|
689 |
if triggered_id == 'proposal-action-btn':
|
690 |
+
output_data_upload = dcc.Loading(type="default", children=html.Div("Generating proposal...", style={"wordWrap": "break-word"}), style={"textAlign": "center"})
|
691 |
+
# get decoded bytes for the selected RFP doc for upload if needed
|
692 |
+
rfp_decoded_bytes = None
|
693 |
+
if doc_value and doc_value in uploaded_documents:
|
694 |
+
# Try to get from upload-document if that was just uploaded
|
695 |
+
if rfp_content is not None and rfp_filename == doc_value:
|
696 |
+
content_type, content_string = rfp_content.split(',')
|
697 |
+
rfp_decoded_bytes = base64.b64decode(content_string)
|
698 |
+
# otherwise, not available, unless user re-uploads
|
699 |
+
proposal_text, _, proposal_docx_name, proposal_text_preview = process_document('proposal', doc_value, chat_input, rfp_decoded_bytes)
|
700 |
proposal_store = {'text': proposal_text, 'docx_name': proposal_docx_name}
|
701 |
new_generated_doc_value = generated_doc_value
|
702 |
if proposal_docx_name:
|