bluenevus commited on
Commit
307700a
·
1 Parent(s): bb8558f

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +86 -11
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"] = f"Error during Gemini completion: {e}"
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
- html.Span(filename, style={"wordWrap": "break-word"}),
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
- f"Download {filename}",
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
- html.Span(filename, style={"wordWrap": "break-word"}),
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
- html.Span(filename, style={"wordWrap": "break-word"}),
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
- proposal_text, _, proposal_docx_name, proposal_text_preview = process_document('proposal', doc_value, chat_input)
 
 
 
 
 
 
 
 
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: