bluenevus commited on
Commit
c887bf3
·
1 Parent(s): d1b1cb2

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +433 -1
app.py CHANGED
@@ -1 +1,433 @@
1
- --
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dash
2
+ from dash import dcc, html, Input, Output, State, callback_context, no_update
3
+ import dash_bootstrap_components as dbc
4
+ import logging
5
+ import threading
6
+ import os
7
+ import base64
8
+ import io
9
+ import uuid
10
+ import time
11
+ from flask import Flask
12
+
13
+ # Anthropic API stub (replace with real implementation)
14
+ import requests
15
+
16
+ # For allowed file types and basic file handling
17
+ ALLOWED_EXTENSIONS = ('pdf', 'doc', 'docx', 'txt')
18
+
19
+ # Logging configuration
20
+ logging.basicConfig(
21
+ format="%(asctime)s %(levelname)s:%(message)s",
22
+ level=logging.INFO
23
+ )
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # In-memory storage for uploaded documents and results
27
+ uploaded_documents = {}
28
+ generated_content = {}
29
+
30
+ # Anthropic API endpoint/config stub
31
+ ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages"
32
+ ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "YOUR_ANTHROPIC_API_KEY") # Replace as appropriate
33
+
34
+ # Flask server for Dash
35
+ server = Flask(__name__)
36
+
37
+ # CUDA GPU configuration (for completeness, if used by downstream libraries)
38
+ os.environ["CUDA_VISIBLE_DEVICES"] = "0"
39
+
40
+ # External stylesheets
41
+ external_stylesheets = [dbc.themes.BOOTSTRAP]
42
+
43
+ # Dash app initialization
44
+ app = dash.Dash(
45
+ __name__,
46
+ server=server,
47
+ external_stylesheets=external_stylesheets,
48
+ suppress_callback_exceptions=True,
49
+ title="Proposal Writing Assistant"
50
+ )
51
+
52
+ # Helper functions
53
+ def allowed_file(filename):
54
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
55
+
56
+ def save_uploaded_file(file_content, filename):
57
+ doc_id = str(uuid.uuid4())
58
+ uploaded_documents[doc_id] = {
59
+ "filename": filename,
60
+ "content": file_content
61
+ }
62
+ logger.info(f"Uploaded document saved: {filename} with id {doc_id}")
63
+ return doc_id
64
+
65
+ def anthropic_api_call(prompt, files=None, task_type=None, extra_instructions=""):
66
+ logger.info(f"Calling Anthropic API for task: {task_type}")
67
+ # Stubbed implementation; replace with actual Anthropic API call
68
+ headers = {
69
+ "x-api-key": ANTHROPIC_API_KEY,
70
+ "content-type": "application/json"
71
+ }
72
+ data = {
73
+ "model": "claude-3-opus-20240229",
74
+ "messages": [
75
+ {"role": "user", "content": prompt + "\n" + extra_instructions}
76
+ ],
77
+ "max_tokens": 4096,
78
+ "temperature": 0.2
79
+ }
80
+ try:
81
+ # response = requests.post(ANTHROPIC_API_URL, headers=headers, json=data, timeout=120)
82
+ # result = response.json().get('content', ['[Anthropic response placeholder]'])[0]
83
+ # Simulated response:
84
+ time.sleep(2)
85
+ result = f"[Simulated response for {task_type}]"
86
+ logger.info(f"Anthropic API success for task: {task_type}")
87
+ return result
88
+ except Exception as e:
89
+ logger.error(f"Anthropic API error: {str(e)}")
90
+ return f"Error: {str(e)}"
91
+
92
+ def parse_contents(contents, filename):
93
+ content_type, content_string = contents.split(',')
94
+ decoded = base64.b64decode(content_string)
95
+ # Only display first few characters for preview
96
+ try:
97
+ if filename.lower().endswith('.txt'):
98
+ preview = decoded.decode('utf-8')[:2048]
99
+ else:
100
+ preview = f"Preview not available for {filename}"
101
+ return preview
102
+ except Exception as e:
103
+ logger.error(f"Could not decode file {filename}: {e}")
104
+ return f"Error decoding {filename}"
105
+
106
+ # UI Elements
107
+ def navbar():
108
+ return dbc.Card(
109
+ [
110
+ dbc.Nav(
111
+ [
112
+ dbc.Button("Shred RFP/PWS/SOW/RFI", id="btn-shred", className="mb-2 btn-primary", style={"width": "100%"}),
113
+ dbc.Button("Generate Proposal Response", id="btn-generate", className="mb-2 btn-secondary", style={"width": "100%"}),
114
+ dbc.Button("Check Compliance", id="btn-compliance", className="mb-2 btn-tertiary", style={"width": "100%"}),
115
+ dbc.Button("Recover Document", id="btn-recover", className="mb-2 btn-primary", style={"width": "100%"}),
116
+ dbc.Button("Virtual Board", id="btn-virtual-board", className="mb-2 btn-secondary", style={"width": "100%"}),
117
+ dbc.Button("Estimate LOE", id="btn-loe", className="mb-2 btn-tertiary", style={"width": "100%"}),
118
+ ],
119
+ vertical=True,
120
+ pills=False
121
+ ),
122
+ html.Hr(),
123
+ html.Div(
124
+ [
125
+ html.H6("Uploaded Documents"),
126
+ html.Ul(
127
+ id="uploaded-doc-list",
128
+ style={"listStyleType": "none", "paddingLeft": "0"}
129
+ ),
130
+ ]
131
+ ),
132
+ ],
133
+ body=True
134
+ )
135
+
136
+ def chat_window():
137
+ return dbc.Card(
138
+ [
139
+ html.Div(
140
+ [
141
+ html.Div(id="chat-history", style={"height": "160px", "overflowY": "auto", "padding": "0.5rem"}),
142
+ dbc.InputGroup(
143
+ [
144
+ dbc.Textarea(id="chat-input", placeholder="Send additional instructions...", style={"resize":"vertical", "wordWrap":"break-word", "width": "100%", "height": "60px"}),
145
+ dbc.Button("Send", id="btn-send-chat", className="btn-secondary", n_clicks=0),
146
+ ],
147
+ className="mt-2"
148
+ ),
149
+ ]
150
+ ),
151
+ ],
152
+ body=True,
153
+ style={"marginBottom": "10px"}
154
+ )
155
+
156
+ def top_action_buttons():
157
+ return html.Div(
158
+ [
159
+ dbc.Button("Shred", id="action-shred", className="me-2 btn-primary", n_clicks=0, style={"minWidth": "120px"}),
160
+ dbc.Button("Generate Response", id="action-generate", className="me-2 btn-secondary", n_clicks=0, style={"minWidth": "180px"}),
161
+ dbc.Button("Check Compliance", id="action-compliance", className="me-2 btn-tertiary", n_clicks=0, style={"minWidth": "160px"}),
162
+ dbc.Button("Recover", id="action-recover", className="me-2 btn-primary", n_clicks=0, style={"minWidth": "120px"}),
163
+ dbc.Button("Virtual Board", id="action-virtual-board", className="me-2 btn-secondary", n_clicks=0, style={"minWidth": "160px"}),
164
+ dbc.Button("Estimate LOE", id="action-loe", className="btn-tertiary", n_clicks=0, style={"minWidth": "140px"}),
165
+ ],
166
+ className="mb-3",
167
+ style={"display": "flex", "flexWrap": "wrap"}
168
+ )
169
+
170
+ def upload_area():
171
+ return html.Div(
172
+ [
173
+ dcc.Upload(
174
+ id="upload-document",
175
+ children=html.Div(["Drag & drop or click to select a file."]),
176
+ multiple=False,
177
+ style={
178
+ "width": "100%",
179
+ "height": "70px",
180
+ "lineHeight": "70px",
181
+ "borderWidth": "1px",
182
+ "borderStyle": "dashed",
183
+ "borderRadius": "4px",
184
+ "textAlign": "center",
185
+ "marginBottom": "8px"
186
+ }
187
+ ),
188
+ html.Div(id="upload-feedback")
189
+ ]
190
+ )
191
+
192
+ def preview_area():
193
+ return dbc.Card(
194
+ [
195
+ html.H6("Document Preview / Output"),
196
+ html.Pre(id="preview-content", style={"whiteSpace": "pre-wrap", "wordWrap": "break-word", "maxHeight": "340px", "overflowY": "auto"})
197
+ ],
198
+ body=True
199
+ )
200
+
201
+ def main_layout():
202
+ return dbc.Container(
203
+ [
204
+ dbc.Row(
205
+ [
206
+ dbc.Col(
207
+ html.H2("Proposal Writing Assistant", style={"margin": "12px 0"}),
208
+ width=12
209
+ ),
210
+ ],
211
+ align="center",
212
+ style={"marginBottom": "8px"}
213
+ ),
214
+ dbc.Row(
215
+ [
216
+ dbc.Col(
217
+ navbar(),
218
+ width=3,
219
+ style={"minWidth": "220px", "maxWidth": "400px"}
220
+ ),
221
+ dbc.Col(
222
+ dbc.Card(
223
+ [
224
+ chat_window(),
225
+ top_action_buttons(),
226
+ upload_area(),
227
+ preview_area(),
228
+ dcc.Loading(
229
+ id="loading",
230
+ type="default",
231
+ children=html.Div(id="loading-output"),
232
+ style={"position": "absolute", "top": "6px", "left": "50%"}
233
+ ),
234
+ ],
235
+ body=True
236
+ ),
237
+ width=9
238
+ ),
239
+ ],
240
+ style={"minHeight": "90vh"}
241
+ ),
242
+ ],
243
+ fluid=True
244
+ )
245
+
246
+ app.layout = main_layout
247
+
248
+ # Callbacks
249
+ @app.callback(
250
+ Output("uploaded-doc-list", "children"),
251
+ Output("preview-content", "children"),
252
+ Output("upload-feedback", "children"),
253
+ Output("loading-output", "children"),
254
+ Output("chat-history", "children"),
255
+ [
256
+ Input("upload-document", "contents"),
257
+ Input("action-shred", "n_clicks"),
258
+ Input("action-generate", "n_clicks"),
259
+ Input("action-compliance", "n_clicks"),
260
+ Input("action-recover", "n_clicks"),
261
+ Input("action-virtual-board", "n_clicks"),
262
+ Input("action-loe", "n_clicks"),
263
+ Input("btn-send-chat", "n_clicks"),
264
+ Input({"type": "delete-doc-btn", "index": dash.ALL}, "n_clicks"),
265
+ ],
266
+ [
267
+ State("upload-document", "filename"),
268
+ State("chat-input", "value"),
269
+ State("chat-history", "children"),
270
+ State("preview-content", "children"),
271
+ State("uploaded-doc-list", "children"),
272
+ ],
273
+ prevent_initial_call=True
274
+ )
275
+ def main_callback(
276
+ upload_contents, shred, generate, compliance, recover, virtual_board, loe, send_chat, delete_doc_clicks,
277
+ upload_filename, chat_input, chat_history, preview_content, uploaded_doc_list
278
+ ):
279
+ triggered_id = callback_context.triggered[0]["prop_id"].split(".")[0] if callback_context.triggered else None
280
+ logger.info(f"Triggered callback: {triggered_id}")
281
+
282
+ feedback = no_update
283
+ loading_message = ""
284
+ new_preview_content = no_update
285
+ new_chat_history = chat_history if chat_history else []
286
+ doc_list_items = []
287
+
288
+ # Handle file upload
289
+ if triggered_id == "upload-document" and upload_contents and upload_filename:
290
+ if not allowed_file(upload_filename):
291
+ feedback = dbc.Alert("Unsupported file type. Please upload PDF, Word, or TXT.", color="danger", dismissable=True)
292
+ else:
293
+ doc_id = save_uploaded_file(upload_contents, upload_filename)
294
+ preview = parse_contents(upload_contents, upload_filename)
295
+ new_preview_content = f"{upload_filename}:\n\n{preview}"
296
+ feedback = dbc.Alert(f"Uploaded {upload_filename}", color="success", dismissable=True)
297
+ logger.info(f"File uploaded: {upload_filename}")
298
+
299
+ # Update doc list
300
+ for doc_id, doc in uploaded_documents.items():
301
+ doc_list_items.append(
302
+ html.Li(
303
+ [
304
+ html.Span(doc['filename'], style={"marginRight": "8px"}),
305
+ dbc.Button("Delete", id={"type": "delete-doc-btn", "index": doc_id}, color="danger", size="sm", n_clicks=0)
306
+ ],
307
+ style={"display": "flex", "justifyContent": "space-between", "alignItems": "center", "marginBottom": "5px"}
308
+ )
309
+ )
310
+
311
+ # Handle document deletion
312
+ if isinstance(delete_doc_clicks, list) and any(delete_doc_clicks):
313
+ idx = delete_doc_clicks.index(max(delete_doc_clicks))
314
+ doc_ids = list(uploaded_documents.keys())
315
+ if idx < len(doc_ids):
316
+ deleted_doc = uploaded_documents.pop(doc_ids[idx])
317
+ feedback = dbc.Alert(f"Deleted {deleted_doc['filename']}", color="info", dismissable=True)
318
+ logger.info(f"Document deleted: {deleted_doc['filename']}")
319
+ doc_list_items = [
320
+ html.Li(
321
+ [
322
+ html.Span(doc['filename'], style={"marginRight": "8px"}),
323
+ dbc.Button("Delete", id={"type": "delete-doc-btn", "index": doc_id}, color="danger", size="sm", n_clicks=0)
324
+ ],
325
+ style={"display": "flex", "justifyContent": "space-between", "alignItems": "center", "marginBottom": "5px"}
326
+ )
327
+ for doc_id, doc in uploaded_documents.items()
328
+ ]
329
+ new_preview_content = "" if not uploaded_documents else no_update
330
+
331
+ # If no uploaded documents, block actions
332
+ if len(uploaded_documents) == 0 and triggered_id not in ["upload-document", "btn-send-chat"]:
333
+ feedback = dbc.Alert("Please upload a document before performing actions.", color="warning", dismissable=True)
334
+ logger.warning("Attempted action without documents.")
335
+ return doc_list_items, new_preview_content, feedback, loading_message, new_chat_history
336
+
337
+ # Handle chat
338
+ if triggered_id == "btn-send-chat" and chat_input and chat_input.strip():
339
+ if not isinstance(new_chat_history, list):
340
+ new_chat_history = []
341
+ new_chat_history.append(html.Div([
342
+ html.Strong("You: "), html.Span(chat_input)
343
+ ], style={"marginBottom": "0.25rem"}))
344
+ feedback = dbc.Alert("Chat message sent. Instructions will be used in next action.", color="info", dismissable=True)
345
+ logger.info(f"Chat message sent: {chat_input}")
346
+ # Clear chat input (handled client-side)
347
+
348
+ # Identify last chat instructions to use
349
+ last_chat = ""
350
+ if isinstance(new_chat_history, list) and new_chat_history:
351
+ for item in reversed(new_chat_history):
352
+ if isinstance(item, html.Div):
353
+ children = item.children
354
+ if len(children) > 1 and isinstance(children[1], html.Span):
355
+ last_chat = children[1].children
356
+ break
357
+ elif isinstance(chat_input, str):
358
+ last_chat = chat_input
359
+
360
+ # Action buttons: always require at least one document
361
+ if triggered_id in ["action-shred", "action-generate", "action-compliance", "action-recover", "action-virtual-board", "action-loe"]:
362
+ loading_message = dbc.Alert("Processing request, please wait...", color="primary", dismissable=False, style={"textAlign": "center"})
363
+ doc_id, doc = next(iter(uploaded_documents.items()))
364
+ file_name = doc['filename']
365
+ file_content = doc['content']
366
+ action_type = triggered_id.replace("action-", "").replace("-", " ").title()
367
+
368
+ # Anthropic API call in a thread
369
+ result_holder = {}
370
+
371
+ def threaded_api_call():
372
+ if triggered_id == "action-shred":
373
+ prompt = (
374
+ "Shred this document into requirements, organized by section. "
375
+ "Identify requirements by action words (shall, will, perform, etc). "
376
+ "Output as spreadsheet: PWS Section, Requirement."
377
+ )
378
+ task_type = "Shred"
379
+ elif triggered_id == "action-generate":
380
+ prompt = (
381
+ "Generate a detailed proposal response, organized by section/subsection. "
382
+ "Focus on approach, steps, workflow, people, processes, technology. "
383
+ "Include research validation and citations. Address Red Review findings."
384
+ )
385
+ task_type = "Generate Proposal Response"
386
+ elif triggered_id == "action-compliance":
387
+ prompt = (
388
+ "Check compliance of the proposal response against the shredded requirements. "
389
+ "Produce a spreadsheet: PWS number, requirement, finding, recommendation."
390
+ )
391
+ task_type = "Check Compliance"
392
+ elif triggered_id == "action-recover":
393
+ prompt = (
394
+ "Using the compliance spreadsheet, improve the document sections. "
395
+ "Address recommendations without materially changing content. "
396
+ "Organize improvements by PWS section headers/subheaders."
397
+ )
398
+ task_type = "Recover Document"
399
+ elif triggered_id == "action-virtual-board":
400
+ prompt = (
401
+ "Evaluate the proposal based on requirements and evaluation criteria. "
402
+ "Generate a section-by-section evaluation spreadsheet using ratings: "
403
+ "unsatisfactory, satisfactory, good, very good, excellent. Include explanations. "
404
+ "Base evaluation on sections L and M."
405
+ )
406
+ task_type = "Virtual Board"
407
+ elif triggered_id == "action-loe":
408
+ prompt = (
409
+ "Estimate Level of Effort for the proposal. Output spreadsheet: "
410
+ "PWS task area, brief description, labor categories, estimated hours per category."
411
+ )
412
+ task_type = "Estimate LOE"
413
+ else:
414
+ prompt = ""
415
+ task_type = "Unknown"
416
+ result_holder["result"] = anthropic_api_call(prompt, files=[file_content], task_type=task_type, extra_instructions=last_chat or "")
417
+
418
+ thread = threading.Thread(target=threaded_api_call)
419
+ thread.start()
420
+ thread.join()
421
+
422
+ result = result_holder.get("result", "[No result]")
423
+ generated_content[triggered_id] = result
424
+ new_preview_content = f"{action_type} Output for {file_name}:\n\n{result}"
425
+ feedback = dbc.Alert(f"{action_type} completed.", color="success", dismissable=True)
426
+ logger.info(f"{action_type} completed for {file_name}")
427
+
428
+ return doc_list_items, new_preview_content, feedback, loading_message, new_chat_history
429
+
430
+ if __name__ == '__main__':
431
+ print("Starting the Dash application...")
432
+ app.run(debug=True, host='0.0.0.0', port=7860, threaded=True)
433
+ print("Dash application has finished running.")