bluenevus commited on
Commit
07728ee
·
1 Parent(s): a9dc90a

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +166 -76
app.py CHANGED
@@ -23,7 +23,8 @@ CLAUDE3_SONNET_MODEL = "claude-3-7-sonnet-20250219"
23
  CLAUDE3_MAX_CONTEXT_TOKENS = 200_000
24
  CLAUDE3_MAX_OUTPUT_TOKENS = 64_000
25
 
26
- uploaded_documents = {} # filename: content
 
27
  shredded_document = None
28
  generated_response = None
29
 
@@ -60,18 +61,29 @@ def anthropic_stream_generate(prompt):
60
  logging.error("Error during anthropic streaming request: %s", e)
61
  return f"Error during streaming: {e}"
62
 
63
- def process_document(action, selected_filename=None, chat_input=None):
64
  global shredded_document, generated_response
65
  logging.info(f"Process document called with action: {action}")
66
 
67
  doc_content = None
68
- if selected_filename and selected_filename in uploaded_documents:
69
- doc_content = uploaded_documents[selected_filename]
70
- elif uploaded_documents:
71
- doc_content = next(iter(uploaded_documents.values()))
72
- selected_filename = next(iter(uploaded_documents.keys()))
73
- else:
74
- doc_content = None
 
 
 
 
 
 
 
 
 
 
 
75
 
76
  if action == 'shred':
77
  if not doc_content:
@@ -131,6 +143,10 @@ def process_document(action, selected_filename=None, chat_input=None):
131
  t.join()
132
  return generated_response
133
 
 
 
 
 
134
  elif action == 'compliance':
135
  return "Compliance checking not implemented yet."
136
  elif action == 'recover':
@@ -141,15 +157,28 @@ def process_document(action, selected_filename=None, chat_input=None):
141
  return "LOE estimation not implemented yet."
142
  return "Action not implemented yet."
143
 
144
- def get_uploaded_doc_list():
145
- if not uploaded_documents:
146
  return html.Div("No documents uploaded.", style={"wordWrap": "break-word"})
147
  doc_list = []
148
- for filename in uploaded_documents:
149
  doc_list.append(
150
  dbc.ListGroupItem([
151
  html.Span(filename, style={"wordWrap": "break-word"}),
152
- dbc.Button("Delete", id={'type': 'delete-doc-btn', 'index': filename}, size="sm", color="danger", className="float-end ms-2")
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  ], className="d-flex justify-content-between align-items-center")
154
  )
155
  return dbc.ListGroup(doc_list, flush=True)
@@ -158,38 +187,15 @@ app.layout = dbc.Container([
158
  dbc.Row([
159
  dbc.Col([
160
  dbc.Card([
161
- dbc.CardHeader(html.H3("Navigation")),
162
- dbc.CardBody([
163
- dbc.Button("Shred RFP/PWS/SOW/RFI", id="shred-btn", className="mb-2 w-100 btn-primary"),
164
- dbc.Button("Generate Proposal Response", id="generate-btn", className="mb-2 w-100 btn-secondary"),
165
- dbc.Button("Check Compliance", id="compliance-btn", className="mb-2 w-100 btn-tertiary"),
166
- dbc.Button("Recover Document", id="recover-btn", className="mb-2 w-100 btn-tertiary"),
167
- dbc.Button("Virtual Board", id="board-btn", className="mb-2 w-100 btn-tertiary"),
168
- dbc.Button("Estimate LOE", id="loe-btn", className="mb-2 w-100 btn-tertiary"),
169
- ])
170
- ], className="mb-3"),
171
- dbc.Card([
172
- dbc.CardHeader(html.H5("Uploaded Documents")),
173
  dbc.CardBody([
174
  html.Div(id='uploaded-doc-list')
175
  ])
176
- ])
177
- ], width=3, style={'minWidth': '260px'}),
178
- dbc.Col([
179
  dbc.Card([
180
- dbc.CardHeader(html.H2("RFP Proposal Assistant", style={'wordWrap': 'break-word'})),
181
  dbc.CardBody([
182
- dbc.Form([
183
- dbc.Textarea(id="chat-input", placeholder="Enter additional instructions...", style={"width":"100%", "wordWrap": "break-word"}, className="mb-2"),
184
- ]),
185
- html.Div([
186
- dbc.Button("Shred", id="shred-action-btn", className="mr-2 btn-primary"),
187
- dbc.Button("Generate", id="generate-action-btn", className="mr-2 btn-secondary"),
188
- dbc.Button("Check Compliance", id="compliance-action-btn", className="mr-2 btn-tertiary"),
189
- dbc.Button("Recover", id="recover-action-btn", className="mr-2 btn-tertiary"),
190
- dbc.Button("Virtual Board", id="board-action-btn", className="mr-2 btn-tertiary"),
191
- dbc.Button("LOE", id="loe-action-btn", className="btn-tertiary"),
192
- ], className="mt-3 mb-3"),
193
  dcc.Dropdown(
194
  id='select-document-dropdown',
195
  options=[{'label': fn, 'value': fn} for fn in uploaded_documents.keys()],
@@ -215,7 +221,58 @@ app.layout = dbc.Container([
215
  },
216
  multiple=False
217
  ),
218
- html.Div(id='output-document-upload'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  dcc.Loading(
220
  id="loading",
221
  type="default",
@@ -232,32 +289,59 @@ app.layout = dbc.Container([
232
  Output('uploaded-doc-list', 'children'),
233
  Output('select-document-dropdown', 'options'),
234
  Output('select-document-dropdown', 'value'),
 
 
 
 
235
  Input('upload-document', 'contents'),
236
  State('upload-document', 'filename'),
237
- Input({'type': 'delete-doc-btn', 'index': dash.ALL}, 'n_clicks'),
238
- State('uploaded-doc-list', 'children'),
239
  State('select-document-dropdown', 'value'),
 
 
 
 
240
  prevent_initial_call=True
241
  )
242
- def update_uploaded_docs(content, filename, delete_clicks, children, selected_doc):
 
 
 
243
  ctx = callback_context
244
  triggered = ctx.triggered
245
  changed_id = ""
246
- if triggered:
247
- changed_id = triggered[0]['prop_id'].split('.')[0]
248
- # Handle upload
249
- if content is not None and filename:
250
- content_type, content_string = content.split(',')
 
251
  decoded = base64.b64decode(content_string)
252
  text = decode_document(decoded)
253
  if text is not None:
254
- uploaded_documents[filename] = text
255
- logging.info(f"Document uploaded: {filename}")
 
256
  else:
257
- logging.error(f"Failed to decode uploaded document: {filename}")
258
- # Handle delete
259
- if delete_clicks:
260
- for i, n_click in enumerate(delete_clicks):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  if n_click:
262
  btn_id = ctx.inputs_list[2][i]['id']
263
  del_filename = btn_id['index']
@@ -267,27 +351,32 @@ def update_uploaded_docs(content, filename, delete_clicks, children, selected_do
267
  if selected_doc == del_filename:
268
  selected_doc = next(iter(uploaded_documents), None)
269
  break
270
- options = [{'label': fn, 'value': fn} for fn in uploaded_documents.keys()]
271
- value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
272
- return get_uploaded_doc_list(), options, value
 
 
 
 
 
 
 
 
 
273
 
274
- @app.callback(
275
- Output('output-document-upload', 'children'),
276
- Input('upload-document', 'contents'),
277
- State('upload-document', 'filename'),
278
- prevent_initial_call=True
279
- )
280
- def handle_upload(content, filename):
281
- logging.info("Upload callback triggered (output-document-upload).")
282
- if content is not None and filename:
283
- content_type, content_string = content.split(',')
284
- decoded = base64.b64decode(content_string)
285
- text = decode_document(decoded)
286
- if text is None:
287
- return html.Div("Error: Could not decode document. Please upload a valid text file.", style={"wordWrap": "break-word"})
288
- else:
289
- return html.Div(f"Document '{filename}' uploaded successfully.", style={"wordWrap": "break-word"})
290
- return ""
291
 
292
  @app.callback(
293
  Output('output-data-upload', 'children'),
@@ -301,9 +390,10 @@ def handle_upload(content, filename):
301
  ],
302
  State('chat-input', 'value'),
303
  State('select-document-dropdown', 'value'),
 
304
  prevent_initial_call=True
305
  )
306
- def handle_actions(shred_clicks, generate_clicks, compliance_clicks, recover_clicks, board_clicks, loe_clicks, chat_input, selected_filename):
307
  ctx = callback_context
308
  if not ctx.triggered:
309
  logging.info("No action triggered yet.")
 
23
  CLAUDE3_MAX_CONTEXT_TOKENS = 200_000
24
  CLAUDE3_MAX_OUTPUT_TOKENS = 64_000
25
 
26
+ uploaded_documents = {} # filename: content for RFP/SOW/PWS/RFI
27
+ uploaded_proposals = {} # filename: content for Proposal
28
  shredded_document = None
29
  generated_response = None
30
 
 
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}")
67
 
68
  doc_content = None
69
+ # Actions "shred" and "generate" use the RFP/SOW/PWS/RFI document dropdown.
70
+ # If needed, proposal document available in selected_proposal.
71
+ if action in ["shred", "generate"]:
72
+ if selected_filename and selected_filename in uploaded_documents:
73
+ doc_content = uploaded_documents[selected_filename]
74
+ elif uploaded_documents:
75
+ doc_content = next(iter(uploaded_documents.values()))
76
+ selected_filename = next(iter(uploaded_documents.keys()))
77
+ else:
78
+ doc_content = None
79
+ elif action == "proposal":
80
+ if selected_proposal and selected_proposal in uploaded_proposals:
81
+ doc_content = uploaded_proposals[selected_proposal]
82
+ elif uploaded_proposals:
83
+ doc_content = next(iter(uploaded_proposals.values()))
84
+ selected_proposal = next(iter(uploaded_proposals.keys()))
85
+ else:
86
+ doc_content = None
87
 
88
  if action == 'shred':
89
  if not doc_content:
 
143
  t.join()
144
  return generated_response
145
 
146
+ elif action == 'proposal':
147
+ if not doc_content:
148
+ return "No proposal document uploaded."
149
+ return dcc.Markdown(doc_content, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
150
  elif action == 'compliance':
151
  return "Compliance checking not implemented yet."
152
  elif action == 'recover':
 
157
  return "LOE estimation not implemented yet."
158
  return "Action not implemented yet."
159
 
160
+ def get_uploaded_doc_list(docdict):
161
+ if not docdict:
162
  return html.Div("No documents uploaded.", style={"wordWrap": "break-word"})
163
  doc_list = []
164
+ for filename in docdict:
165
  doc_list.append(
166
  dbc.ListGroupItem([
167
  html.Span(filename, style={"wordWrap": "break-word"}),
168
+ dbc.Button("Delete", id={'type': 'delete-doc-btn', 'index': filename, 'group': 'rfp'}, size="sm", color="danger", className="float-end ms-2")
169
+ ], className="d-flex justify-content-between align-items-center")
170
+ )
171
+ return dbc.ListGroup(doc_list, flush=True)
172
+
173
+ def get_uploaded_proposal_list(docdict):
174
+ if not docdict:
175
+ return html.Div("No proposal documents uploaded.", style={"wordWrap": "break-word"})
176
+ doc_list = []
177
+ for filename in docdict:
178
+ doc_list.append(
179
+ dbc.ListGroupItem([
180
+ html.Span(filename, style={"wordWrap": "break-word"}),
181
+ dbc.Button("Delete", id={'type': 'delete-proposal-btn', 'index': filename, 'group': 'proposal'}, size="sm", color="danger", className="float-end ms-2")
182
  ], className="d-flex justify-content-between align-items-center")
183
  )
184
  return dbc.ListGroup(doc_list, flush=True)
 
187
  dbc.Row([
188
  dbc.Col([
189
  dbc.Card([
190
+ dbc.CardHeader(html.H3("Documents")),
 
 
 
 
 
 
 
 
 
 
 
191
  dbc.CardBody([
192
  html.Div(id='uploaded-doc-list')
193
  ])
194
+ ], className="mb-3"),
195
+
 
196
  dbc.Card([
197
+ dbc.CardHeader(html.H5("RFP/SOW/PWS/RFI")),
198
  dbc.CardBody([
 
 
 
 
 
 
 
 
 
 
 
199
  dcc.Dropdown(
200
  id='select-document-dropdown',
201
  options=[{'label': fn, 'value': fn} for fn in uploaded_documents.keys()],
 
221
  },
222
  multiple=False
223
  ),
224
+ html.Div(id='output-document-upload')
225
+ ])
226
+ ], className="mb-3"),
227
+
228
+ dbc.Card([
229
+ dbc.CardHeader(html.H5("Proposal")),
230
+ dbc.CardBody([
231
+ dcc.Dropdown(
232
+ id='select-proposal-dropdown',
233
+ options=[{'label': fn, 'value': fn} for fn in uploaded_proposals.keys()],
234
+ placeholder="Select a proposal document",
235
+ value=next(iter(uploaded_proposals), None),
236
+ style={"marginBottom": "10px"}
237
+ ),
238
+ dcc.Upload(
239
+ id='upload-proposal',
240
+ children=html.Div([
241
+ 'Drag and Drop or ',
242
+ html.A('Select Files')
243
+ ]),
244
+ style={
245
+ 'width': '100%',
246
+ 'height': '60px',
247
+ 'lineHeight': '60px',
248
+ 'borderWidth': '1px',
249
+ 'borderStyle': 'dashed',
250
+ 'borderRadius': '5px',
251
+ 'textAlign': 'center',
252
+ 'margin': '10px'
253
+ },
254
+ multiple=False
255
+ ),
256
+ html.Div(id='output-proposal-upload')
257
+ ])
258
+ ])
259
+ ], width=3, style={'minWidth': '260px'}),
260
+
261
+ dbc.Col([
262
+ dbc.Card([
263
+ dbc.CardHeader(html.H2("RFP Proposal Assistant", style={'wordWrap': 'break-word'})),
264
+ dbc.CardBody([
265
+ dbc.Form([
266
+ dbc.Textarea(id="chat-input", placeholder="Enter additional instructions...", style={"width":"100%", "wordWrap": "break-word"}, className="mb-2"),
267
+ ]),
268
+ html.Div([
269
+ dbc.Button("Shred", id="shred-action-btn", className="mr-2 btn-primary"),
270
+ dbc.Button("Generate", id="generate-action-btn", className="mr-2 btn-secondary"),
271
+ dbc.Button("Check Compliance", id="compliance-action-btn", className="mr-2 btn-tertiary"),
272
+ dbc.Button("Recover", id="recover-action-btn", className="mr-2 btn-tertiary"),
273
+ dbc.Button("Virtual Board", id="board-action-btn", className="mr-2 btn-tertiary"),
274
+ dbc.Button("LOE", id="loe-action-btn", className="btn-tertiary"),
275
+ ], className="mt-3 mb-3"),
276
  dcc.Loading(
277
  id="loading",
278
  type="default",
 
289
  Output('uploaded-doc-list', 'children'),
290
  Output('select-document-dropdown', 'options'),
291
  Output('select-document-dropdown', 'value'),
292
+ Output('output-document-upload', 'children'),
293
+ Output('select-proposal-dropdown', 'options'),
294
+ Output('select-proposal-dropdown', 'value'),
295
+ Output('output-proposal-upload', 'children'),
296
  Input('upload-document', 'contents'),
297
  State('upload-document', 'filename'),
298
+ Input({'type': 'delete-doc-btn', 'index': dash.ALL, 'group': 'rfp'}, 'n_clicks'),
 
299
  State('select-document-dropdown', 'value'),
300
+ Input('upload-proposal', 'contents'),
301
+ State('upload-proposal', 'filename'),
302
+ Input({'type': 'delete-proposal-btn', 'index': dash.ALL, 'group': 'proposal'}, 'n_clicks'),
303
+ State('select-proposal-dropdown', 'value'),
304
  prevent_initial_call=True
305
  )
306
+ def update_uploaded_docs(
307
+ rfp_content, rfp_filename, rfp_delete_clicks, selected_doc,
308
+ proposal_content, proposal_filename, proposal_delete_clicks, selected_proposal
309
+ ):
310
  ctx = callback_context
311
  triggered = ctx.triggered
312
  changed_id = ""
313
+ output_document_upload = ""
314
+ output_proposal_upload = ""
315
+
316
+ # --- Handle RFP/SOW/PWS/RFI document upload ---
317
+ if rfp_content is not None and rfp_filename:
318
+ content_type, content_string = rfp_content.split(',')
319
  decoded = base64.b64decode(content_string)
320
  text = decode_document(decoded)
321
  if text is not None:
322
+ uploaded_documents[rfp_filename] = text
323
+ output_document_upload = html.Div(f"Document '{rfp_filename}' uploaded successfully.", style={"wordWrap": "break-word"})
324
+ logging.info(f"Document uploaded: {rfp_filename}")
325
  else:
326
+ output_document_upload = html.Div("Error: Could not decode document. Please upload a valid text file.", style={"wordWrap": "break-word"})
327
+ logging.error(f"Failed to decode uploaded document: {rfp_filename}")
328
+
329
+ # --- Handle Proposal document upload ---
330
+ if proposal_content is not None and proposal_filename:
331
+ content_type, content_string = proposal_content.split(',')
332
+ decoded = base64.b64decode(content_string)
333
+ text = decode_document(decoded)
334
+ if text is not None:
335
+ uploaded_proposals[proposal_filename] = text
336
+ output_proposal_upload = html.Div(f"Proposal '{proposal_filename}' uploaded successfully.", style={"wordWrap": "break-word"})
337
+ logging.info(f"Proposal uploaded: {proposal_filename}")
338
+ else:
339
+ output_proposal_upload = html.Div("Error: Could not decode proposal. Please upload a valid text file.", style={"wordWrap": "break-word"})
340
+ logging.error(f"Failed to decode uploaded proposal: {proposal_filename}")
341
+
342
+ # --- Handle RFP/SOW/PWS/RFI document delete ---
343
+ if rfp_delete_clicks:
344
+ for i, n_click in enumerate(rfp_delete_clicks):
345
  if n_click:
346
  btn_id = ctx.inputs_list[2][i]['id']
347
  del_filename = btn_id['index']
 
351
  if selected_doc == del_filename:
352
  selected_doc = next(iter(uploaded_documents), None)
353
  break
354
+ # --- Handle Proposal document delete ---
355
+ if proposal_delete_clicks:
356
+ for i, n_click in enumerate(proposal_delete_clicks):
357
+ if n_click:
358
+ btn_id = ctx.inputs_list[6][i]['id']
359
+ del_filename = btn_id['index']
360
+ if del_filename in uploaded_proposals:
361
+ del uploaded_proposals[del_filename]
362
+ logging.info(f"Proposal deleted: {del_filename}")
363
+ if selected_proposal == del_filename:
364
+ selected_proposal = next(iter(uploaded_proposals), None)
365
+ break
366
 
367
+ doc_options = [{'label': fn, 'value': fn} for fn in uploaded_documents.keys()]
368
+ doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
369
+ proposal_options = [{'label': fn, 'value': fn} for fn in uploaded_proposals.keys()]
370
+ proposal_value = selected_proposal if selected_proposal in uploaded_proposals else (next(iter(uploaded_proposals), None) if uploaded_proposals else None)
371
+ return (
372
+ get_uploaded_doc_list(uploaded_documents),
373
+ doc_options,
374
+ doc_value,
375
+ output_document_upload,
376
+ proposal_options,
377
+ proposal_value,
378
+ output_proposal_upload
379
+ )
 
 
 
 
380
 
381
  @app.callback(
382
  Output('output-data-upload', 'children'),
 
390
  ],
391
  State('chat-input', 'value'),
392
  State('select-document-dropdown', 'value'),
393
+ State('select-proposal-dropdown', 'value'),
394
  prevent_initial_call=True
395
  )
396
+ def handle_actions(shred_clicks, generate_clicks, compliance_clicks, recover_clicks, board_clicks, loe_clicks, chat_input, selected_filename, selected_proposal):
397
  ctx = callback_context
398
  if not ctx.triggered:
399
  logging.info("No action triggered yet.")