bluenevus commited on
Commit
9792dc9
·
1 Parent(s): 647dcd2

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +136 -24
app.py CHANGED
@@ -8,6 +8,7 @@ import pandas as pd
8
  import anthropic
9
  from threading import Thread
10
  import logging
 
11
 
12
  logging.basicConfig(
13
  level=logging.INFO,
@@ -25,6 +26,7 @@ CLAUDE3_MAX_OUTPUT_TOKENS = 64_000
25
 
26
  uploaded_documents = {}
27
  uploaded_proposals = {}
 
28
  shredded_document = None
29
  generated_response = None
30
 
@@ -61,6 +63,16 @@ def anthropic_stream_generate(prompt):
61
  logging.error("Error during anthropic streaming request: %s", e)
62
  return f"Error during streaming: {e}"
63
 
 
 
 
 
 
 
 
 
 
 
64
  def process_document(action, selected_filename=None, chat_input=None, selected_proposal=None):
65
  global shredded_document, generated_response
66
  logging.info(f"Process document called with action: {action}")
@@ -86,7 +98,7 @@ def process_document(action, selected_filename=None, chat_input=None, selected_p
86
  if action == 'shred':
87
  if not doc_content:
88
  logging.warning("No uploaded document found for shredding.")
89
- return "No document uploaded."
90
  prompt = (
91
  "Analyze the following RFP/PWS/SOW/RFI and generate a requirements spreadsheet. "
92
  "Identify requirements by action words like 'shall', 'will', 'perform', etc. Organize by PWS section and requirement. "
@@ -95,6 +107,8 @@ def process_document(action, selected_filename=None, chat_input=None, selected_p
95
  if chat_input:
96
  prompt += f"User additional instructions: {chat_input}\n"
97
  prompt += f"\nFile Name: {selected_filename}\n\n{doc_content}"
 
 
98
  def thread_shred():
99
  global shredded_document
100
  shredded_document = ""
@@ -103,19 +117,25 @@ def process_document(action, selected_filename=None, chat_input=None, selected_p
103
  result = anthropic_stream_generate(prompt)
104
  shredded_document = result
105
  logging.info("Document shredded successfully.")
 
 
 
 
 
106
  except Exception as e:
107
  shredded_document = f"Error during shredding: {e}"
108
  logging.error("Error in thread_shred: %s", e)
 
109
  shredded_document = "Shredding in progress..."
110
  t = Thread(target=thread_shred)
111
  t.start()
112
  t.join()
113
- return shredded_document
114
 
115
  elif action == 'generate':
116
  if not shredded_document:
117
  logging.warning("No shredded document found when generating response.")
118
- return "Shredded document not available."
119
  prompt = (
120
  "Create a highly detailed proposal response based on the following PWS requirements. "
121
  "Be compliant and compelling. Focus on describing the approach, steps, workflow, people, processes, and technology. "
@@ -124,6 +144,7 @@ def process_document(action, selected_filename=None, chat_input=None, selected_p
124
  if chat_input:
125
  prompt += f"User additional instructions: {chat_input}\n"
126
  prompt += f"\nFile Name: {selected_filename}\n\n{shredded_document}"
 
127
  def thread_generate():
128
  global generated_response
129
  generated_response = ""
@@ -132,28 +153,30 @@ def process_document(action, selected_filename=None, chat_input=None, selected_p
132
  result = anthropic_stream_generate(prompt)
133
  generated_response = result
134
  logging.info("Proposal response generated successfully.")
 
135
  except Exception as e:
136
  generated_response = f"Error during generation: {e}"
137
  logging.error("Error in thread_generate: %s", e)
 
138
  generated_response = "Generating response..."
139
  t = Thread(target=thread_generate)
140
  t.start()
141
  t.join()
142
- return generated_response
143
 
144
  elif action == 'proposal':
145
  if not doc_content:
146
- return "No proposal document uploaded."
147
- return dcc.Markdown(doc_content, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
148
  elif action == 'compliance':
149
- return "Compliance checking not implemented yet."
150
  elif action == 'recover':
151
- return "Recovery not implemented yet."
152
  elif action == 'board':
153
- return "Virtual board not implemented yet."
154
  elif action == 'loe':
155
- return "LOE estimation not implemented yet."
156
- return "Action not implemented yet."
157
 
158
  def get_uploaded_doc_list(docdict):
159
  if not docdict:
@@ -181,6 +204,19 @@ def get_uploaded_proposal_list(docdict):
181
  )
182
  return dbc.ListGroup(doc_list, flush=True)
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  app.layout = dbc.Container([
185
  dbc.Row([
186
  dbc.Col([
@@ -246,6 +282,20 @@ app.layout = dbc.Container([
246
  multiple=False
247
  )
248
  ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  ])
250
  ], style={'minWidth': '260px', 'width':'30vw','maxWidth':'30vw'}, width=3),
251
 
@@ -283,6 +333,9 @@ app.layout = dbc.Container([
283
  Output('select-proposal-dropdown', 'options'),
284
  Output('select-proposal-dropdown', 'value'),
285
  Output('uploaded-proposal-list', 'children'),
 
 
 
286
  Input('upload-document', 'contents'),
287
  State('upload-document', 'filename'),
288
  Input({'type': 'delete-doc-btn', 'index': dash.ALL, 'group': 'rfp'}, 'n_clicks'),
@@ -291,11 +344,15 @@ app.layout = dbc.Container([
291
  State('upload-proposal', 'filename'),
292
  Input({'type': 'delete-proposal-btn', 'index': dash.ALL, 'group': 'proposal'}, 'n_clicks'),
293
  State('select-proposal-dropdown', 'value'),
 
 
 
294
  prevent_initial_call=True
295
  )
296
  def update_uploaded_docs(
297
  rfp_content, rfp_filename, rfp_delete_clicks, selected_doc,
298
- proposal_content, proposal_filename, proposal_delete_clicks, selected_proposal
 
299
  ):
300
  ctx = callback_context
301
  triggered = ctx.triggered
@@ -342,22 +399,41 @@ def update_uploaded_docs(
342
  if selected_proposal == del_filename:
343
  selected_proposal = next(iter(uploaded_proposals), None)
344
  break
 
 
 
 
 
 
 
 
 
 
 
345
 
346
  doc_options = [{'label': fn, 'value': fn} for fn in uploaded_documents.keys()]
347
  doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
348
  proposal_options = [{'label': fn, 'value': fn} for fn in uploaded_proposals.keys()]
349
  proposal_value = selected_proposal if selected_proposal in uploaded_proposals else (next(iter(uploaded_proposals), None) if uploaded_proposals else None)
 
 
350
  return (
351
  get_uploaded_doc_list(uploaded_documents),
352
  doc_options,
353
  doc_value,
354
  proposal_options,
355
  proposal_value,
356
- get_uploaded_proposal_list(uploaded_proposals)
 
 
 
357
  )
358
 
359
  @app.callback(
360
  Output('output-data-upload', 'children'),
 
 
 
361
  [
362
  Input('shred-action-btn', 'n_clicks'),
363
  Input('generate-action-btn', 'n_clicks'),
@@ -369,35 +445,71 @@ def update_uploaded_docs(
369
  State('chat-input', 'value'),
370
  State('select-document-dropdown', 'value'),
371
  State('select-proposal-dropdown', 'value'),
 
372
  prevent_initial_call=True
373
  )
374
- def handle_actions(shred_clicks, generate_clicks, compliance_clicks, recover_clicks, board_clicks, loe_clicks, chat_input, selected_filename, selected_proposal):
 
 
 
375
  ctx = callback_context
376
  if not ctx.triggered:
377
  logging.info("No action triggered yet.")
378
- return html.Div("No action taken yet.", style={"wordWrap": "break-word"})
379
  button_id = ctx.triggered[0]['prop_id'].split('.')[0]
380
  logging.info(f"Button pressed: {button_id}")
 
381
  result = ""
 
 
 
 
382
  if button_id == 'shred-action-btn':
383
- result = process_document('shred', selected_filename, chat_input)
 
 
 
 
384
  elif button_id == 'generate-action-btn':
385
- result = process_document('generate', selected_filename, chat_input)
386
  elif button_id == 'compliance-action-btn':
387
- result = process_document('compliance', selected_filename, chat_input)
388
  elif button_id == 'recover-action-btn':
389
- result = process_document('recover', selected_filename, chat_input)
390
  elif button_id == 'board-action-btn':
391
- result = process_document('board', selected_filename, chat_input)
392
  elif button_id == 'loe-action-btn':
393
- result = process_document('loe', selected_filename, chat_input)
394
  else:
395
  result = "Action not implemented yet."
 
396
  if isinstance(result, str) and result.strip().startswith("Error"):
397
- return html.Div(result, style={"wordWrap": "break-word"})
398
  if isinstance(result, str) and ("not implemented" in result or "No document uploaded" in result or "Shredding in progress" in result or "Generating response" in result or "Shredded document not available" in result):
399
- return html.Div(result, style={"wordWrap": "break-word"})
400
- return dcc.Markdown(result, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
 
402
  if __name__ == '__main__':
403
  print("Starting the Dash application...")
 
8
  import anthropic
9
  from threading import Thread
10
  import logging
11
+ from docx import Document
12
 
13
  logging.basicConfig(
14
  level=logging.INFO,
 
26
 
27
  uploaded_documents = {}
28
  uploaded_proposals = {}
29
+ generated_documents = {}
30
  shredded_document = None
31
  generated_response = None
32
 
 
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)
69
+ for line in shredded_text.split('\n'):
70
+ doc.add_paragraph(line)
71
+ memf = io.BytesIO()
72
+ doc.save(memf)
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}")
 
98
  if action == 'shred':
99
  if not doc_content:
100
  logging.warning("No uploaded document found for shredding.")
101
+ return "No document uploaded.", None, None
102
  prompt = (
103
  "Analyze the following RFP/PWS/SOW/RFI and generate a requirements spreadsheet. "
104
  "Identify requirements by action words like 'shall', 'will', 'perform', etc. Organize by PWS section and requirement. "
 
107
  if chat_input:
108
  prompt += f"User additional instructions: {chat_input}\n"
109
  prompt += f"\nFile Name: {selected_filename}\n\n{doc_content}"
110
+
111
+ result_holder = {"text": None, "docx_bytes": None, "docx_name": None}
112
  def thread_shred():
113
  global shredded_document
114
  shredded_document = ""
 
117
  result = anthropic_stream_generate(prompt)
118
  shredded_document = result
119
  logging.info("Document shredded successfully.")
120
+ docx_bytes = save_shredded_as_docx(result, selected_filename)
121
+ generated_docx_name = f"{os.path.splitext(selected_filename)[0]}_shredded.docx"
122
+ result_holder["text"] = result
123
+ result_holder["docx_bytes"] = docx_bytes
124
+ result_holder["docx_name"] = generated_docx_name
125
  except Exception as e:
126
  shredded_document = f"Error during shredding: {e}"
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()
133
+ return result_holder["text"], result_holder["docx_bytes"], result_holder["docx_name"]
134
 
135
  elif action == 'generate':
136
  if not shredded_document:
137
  logging.warning("No shredded document found when generating response.")
138
+ return "Shredded document not available.", None, None
139
  prompt = (
140
  "Create a highly detailed proposal response based on the following PWS requirements. "
141
  "Be compliant and compelling. Focus on describing the approach, steps, workflow, people, processes, and technology. "
 
144
  if chat_input:
145
  prompt += f"User additional instructions: {chat_input}\n"
146
  prompt += f"\nFile Name: {selected_filename}\n\n{shredded_document}"
147
+ result_holder = {"text": None}
148
  def thread_generate():
149
  global generated_response
150
  generated_response = ""
 
153
  result = anthropic_stream_generate(prompt)
154
  generated_response = result
155
  logging.info("Proposal response generated successfully.")
156
+ result_holder["text"] = result
157
  except Exception as e:
158
  generated_response = f"Error during generation: {e}"
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
  if not doc_content:
169
+ return "No proposal document uploaded.", None, None
170
+ return dcc.Markdown(doc_content, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"}), None, None
171
  elif action == 'compliance':
172
+ return "Compliance checking not implemented yet.", None, None
173
  elif action == 'recover':
174
+ return "Recovery not implemented yet.", None, None
175
  elif action == 'board':
176
+ return "Virtual board not implemented yet.", None, None
177
  elif action == 'loe':
178
+ return "LOE estimation not implemented yet.", None, None
179
+ return "Action not implemented yet.", None, None
180
 
181
  def get_uploaded_doc_list(docdict):
182
  if not docdict:
 
204
  )
205
  return dbc.ListGroup(doc_list, flush=True)
206
 
207
+ def get_generated_doc_list(docdict):
208
+ if not docdict:
209
+ return html.Div("No generated documents yet.", style={"wordWrap": "break-word"})
210
+ doc_list = []
211
+ for filename in docdict:
212
+ doc_list.append(
213
+ dbc.ListGroupItem([
214
+ html.Span(filename, style={"wordWrap": "break-word"}),
215
+ dbc.Button("Delete", id={'type': 'delete-generated-btn', 'index': filename, 'group': 'generated'}, size="sm", color="danger", className="float-end ms-2")
216
+ ], className="d-flex justify-content-between align-items-center")
217
+ )
218
+ return dbc.ListGroup(doc_list, flush=True)
219
+
220
  app.layout = dbc.Container([
221
  dbc.Row([
222
  dbc.Col([
 
282
  multiple=False
283
  )
284
  ])
285
+ ], className="mb-3"),
286
+
287
+ dbc.Card([
288
+ dbc.CardHeader(html.H5("Generated Documents")),
289
+ dbc.CardBody([
290
+ html.Div(get_generated_doc_list(generated_documents), id='generated-doc-list'),
291
+ dcc.Dropdown(
292
+ id='select-generated-dropdown',
293
+ options=[{'label': fn, 'value': fn} for fn in generated_documents.keys()],
294
+ placeholder="Select a generated document",
295
+ value=next(iter(generated_documents), None),
296
+ style={"marginBottom": "10px"}
297
+ ),
298
+ ])
299
  ])
300
  ], style={'minWidth': '260px', 'width':'30vw','maxWidth':'30vw'}, width=3),
301
 
 
333
  Output('select-proposal-dropdown', 'options'),
334
  Output('select-proposal-dropdown', 'value'),
335
  Output('uploaded-proposal-list', 'children'),
336
+ Output('generated-doc-list', 'children'),
337
+ Output('select-generated-dropdown', 'options'),
338
+ Output('select-generated-dropdown', 'value'),
339
  Input('upload-document', 'contents'),
340
  State('upload-document', 'filename'),
341
  Input({'type': 'delete-doc-btn', 'index': dash.ALL, 'group': 'rfp'}, 'n_clicks'),
 
344
  State('upload-proposal', 'filename'),
345
  Input({'type': 'delete-proposal-btn', 'index': dash.ALL, 'group': 'proposal'}, 'n_clicks'),
346
  State('select-proposal-dropdown', 'value'),
347
+ Input({'type': 'delete-generated-btn', 'index': dash.ALL, 'group': 'generated'}, 'n_clicks'),
348
+ State('select-generated-dropdown', 'value'),
349
+ State('select-generated-dropdown', 'options'),
350
  prevent_initial_call=True
351
  )
352
  def update_uploaded_docs(
353
  rfp_content, rfp_filename, rfp_delete_clicks, selected_doc,
354
+ proposal_content, proposal_filename, proposal_delete_clicks, selected_proposal,
355
+ generated_delete_clicks, selected_generated, generated_options
356
  ):
357
  ctx = callback_context
358
  triggered = ctx.triggered
 
399
  if selected_proposal == del_filename:
400
  selected_proposal = next(iter(uploaded_proposals), None)
401
  break
402
+ if generated_delete_clicks:
403
+ for i, n_click in enumerate(generated_delete_clicks):
404
+ if n_click:
405
+ btn_id = ctx.inputs_list[10][i]['id']
406
+ del_filename = btn_id['index']
407
+ if del_filename in generated_documents:
408
+ del generated_documents[del_filename]
409
+ logging.info(f"Generated doc deleted: {del_filename}")
410
+ if selected_generated == del_filename:
411
+ selected_generated = next(iter(generated_documents), None)
412
+ break
413
 
414
  doc_options = [{'label': fn, 'value': fn} for fn in uploaded_documents.keys()]
415
  doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
416
  proposal_options = [{'label': fn, 'value': fn} for fn in uploaded_proposals.keys()]
417
  proposal_value = selected_proposal if selected_proposal in uploaded_proposals else (next(iter(uploaded_proposals), None) if uploaded_proposals else None)
418
+ generated_doc_options = [{'label': fn, 'value': fn} for fn in generated_documents.keys()]
419
+ generated_doc_value = selected_generated if selected_generated in generated_documents else (next(iter(generated_documents), None) if generated_documents else None)
420
  return (
421
  get_uploaded_doc_list(uploaded_documents),
422
  doc_options,
423
  doc_value,
424
  proposal_options,
425
  proposal_value,
426
+ get_uploaded_proposal_list(uploaded_proposals),
427
+ get_generated_doc_list(generated_documents),
428
+ generated_doc_options,
429
+ generated_doc_value
430
  )
431
 
432
  @app.callback(
433
  Output('output-data-upload', 'children'),
434
+ Output('generated-doc-list', 'children'),
435
+ Output('select-generated-dropdown', 'options'),
436
+ Output('select-generated-dropdown', 'value'),
437
  [
438
  Input('shred-action-btn', 'n_clicks'),
439
  Input('generate-action-btn', 'n_clicks'),
 
445
  State('chat-input', 'value'),
446
  State('select-document-dropdown', 'value'),
447
  State('select-proposal-dropdown', 'value'),
448
+ State('select-generated-dropdown', 'value'),
449
  prevent_initial_call=True
450
  )
451
+ def handle_actions(
452
+ shred_clicks, generate_clicks, compliance_clicks, recover_clicks, board_clicks, loe_clicks,
453
+ chat_input, selected_filename, selected_proposal, selected_generated
454
+ ):
455
  ctx = callback_context
456
  if not ctx.triggered:
457
  logging.info("No action triggered yet.")
458
+ return html.Div("No action taken yet.", style={"wordWrap": "break-word"}), get_generated_doc_list(generated_documents), [{'label': fn, 'value': fn} for fn in generated_documents.keys()], selected_generated
459
  button_id = ctx.triggered[0]['prop_id'].split('.')[0]
460
  logging.info(f"Button pressed: {button_id}")
461
+
462
  result = ""
463
+ generated_docx_bytes = None
464
+ generated_docx_name = None
465
+ new_selected_generated = selected_generated
466
+
467
  if button_id == 'shred-action-btn':
468
+ result, generated_docx_bytes, generated_docx_name = process_document('shred', selected_filename, chat_input)
469
+ if generated_docx_bytes and generated_docx_name:
470
+ generated_documents[generated_docx_name] = generated_docx_bytes
471
+ logging.info(f"Generated docx saved: {generated_docx_name}")
472
+ new_selected_generated = generated_docx_name
473
  elif button_id == 'generate-action-btn':
474
+ result, _, _ = process_document('generate', selected_filename, chat_input)
475
  elif button_id == 'compliance-action-btn':
476
+ result, _, _ = process_document('compliance', selected_filename, chat_input)
477
  elif button_id == 'recover-action-btn':
478
+ result, _, _ = process_document('recover', selected_filename, chat_input)
479
  elif button_id == 'board-action-btn':
480
+ result, _, _ = process_document('board', selected_filename, chat_input)
481
  elif button_id == 'loe-action-btn':
482
+ result, _, _ = process_document('loe', selected_filename, chat_input)
483
  else:
484
  result = "Action not implemented yet."
485
+
486
  if isinstance(result, str) and result.strip().startswith("Error"):
487
+ return html.Div(result, style={"wordWrap": "break-word"}), get_generated_doc_list(generated_documents), [{'label': fn, 'value': fn} for fn in generated_documents.keys()], new_selected_generated
488
  if isinstance(result, str) and ("not implemented" in result or "No document uploaded" in result or "Shredding in progress" in result or "Generating response" in result or "Shredded document not available" in result):
489
+ return html.Div(result, style={"wordWrap": "break-word"}), get_generated_doc_list(generated_documents), [{'label': fn, 'value': fn} for fn in generated_documents.keys()], new_selected_generated
490
+ return dcc.Markdown(result, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"}), get_generated_doc_list(generated_documents), [{'label': fn, 'value': fn} for fn in generated_documents.keys()], new_selected_generated
491
+
492
+ @app.callback(
493
+ Output('output-data-upload', 'children'),
494
+ Input('select-generated-dropdown', 'value'),
495
+ prevent_initial_call=True
496
+ )
497
+ def display_generated_doc(selected_generated):
498
+ if not selected_generated or selected_generated not in generated_documents:
499
+ return html.Div("No generated document selected.", style={"wordWrap": "break-word"})
500
+ docx_bytes = generated_documents[selected_generated]
501
+ b64 = base64.b64encode(docx_bytes).decode('utf-8')
502
+ download_link = html.A(
503
+ f"Download {selected_generated}",
504
+ href=f"data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,{b64}",
505
+ download=selected_generated,
506
+ target="_blank",
507
+ style={"wordWrap": "break-word"}
508
+ )
509
+ return html.Div([
510
+ html.Div(download_link, style={"marginBottom": "15px"}),
511
+ html.Div("Preview not available for docx. Download to view.", style={"wordWrap": "break-word"})
512
+ ])
513
 
514
  if __name__ == '__main__':
515
  print("Starting the Dash application...")