Spaces:
Paused
Paused
Update app.py via AI Editor
Browse files
app.py
CHANGED
@@ -10,37 +10,28 @@ 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")
|
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,
|
@@ -49,7 +40,6 @@ app = dash.Dash(
|
|
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 |
|
@@ -64,7 +54,6 @@ def save_uploaded_file(file_content, filename):
|
|
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"
|
@@ -80,7 +69,6 @@ def anthropic_api_call(prompt, files=None, task_type=None, extra_instructions=""
|
|
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}")
|
@@ -92,7 +80,6 @@ def anthropic_api_call(prompt, files=None, task_type=None, extra_instructions=""
|
|
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]
|
@@ -103,7 +90,6 @@ def parse_contents(contents, filename):
|
|
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 |
[
|
@@ -245,7 +231,6 @@ def main_layout():
|
|
245 |
|
246 |
app.layout = main_layout
|
247 |
|
248 |
-
# Callbacks
|
249 |
@app.callback(
|
250 |
Output("uploaded-doc-list", "children"),
|
251 |
Output("preview-content", "children"),
|
@@ -285,7 +270,6 @@ def main_callback(
|
|
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)
|
@@ -296,7 +280,6 @@ def main_callback(
|
|
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(
|
@@ -308,7 +291,6 @@ def main_callback(
|
|
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())
|
@@ -328,13 +310,11 @@ def main_callback(
|
|
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 = []
|
@@ -343,9 +323,7 @@ def main_callback(
|
|
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):
|
@@ -357,7 +335,6 @@ def main_callback(
|
|
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()))
|
@@ -365,7 +342,20 @@ def main_callback(
|
|
365 |
file_content = doc['content']
|
366 |
action_type = triggered_id.replace("action-", "").replace("-", " ").title()
|
367 |
|
368 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
369 |
result_holder = {}
|
370 |
|
371 |
def threaded_api_call():
|
@@ -373,27 +363,31 @@ def main_callback(
|
|
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":
|
@@ -401,18 +395,21 @@ def main_callback(
|
|
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)
|
|
|
10 |
import time
|
11 |
from flask import Flask
|
12 |
|
|
|
13 |
import requests
|
14 |
|
|
|
15 |
ALLOWED_EXTENSIONS = ('pdf', 'doc', 'docx', 'txt')
|
16 |
|
|
|
17 |
logging.basicConfig(
|
18 |
format="%(asctime)s %(levelname)s:%(message)s",
|
19 |
level=logging.INFO
|
20 |
)
|
21 |
logger = logging.getLogger(__name__)
|
22 |
|
|
|
23 |
uploaded_documents = {}
|
24 |
generated_content = {}
|
25 |
|
|
|
26 |
ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages"
|
27 |
+
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "YOUR_ANTHROPIC_API_KEY")
|
28 |
|
|
|
29 |
server = Flask(__name__)
|
30 |
|
|
|
31 |
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
|
32 |
|
|
|
33 |
external_stylesheets = [dbc.themes.BOOTSTRAP]
|
34 |
|
|
|
35 |
app = dash.Dash(
|
36 |
__name__,
|
37 |
server=server,
|
|
|
40 |
title="Proposal Writing Assistant"
|
41 |
)
|
42 |
|
|
|
43 |
def allowed_file(filename):
|
44 |
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
45 |
|
|
|
54 |
|
55 |
def anthropic_api_call(prompt, files=None, task_type=None, extra_instructions=""):
|
56 |
logger.info(f"Calling Anthropic API for task: {task_type}")
|
|
|
57 |
headers = {
|
58 |
"x-api-key": ANTHROPIC_API_KEY,
|
59 |
"content-type": "application/json"
|
|
|
69 |
try:
|
70 |
# response = requests.post(ANTHROPIC_API_URL, headers=headers, json=data, timeout=120)
|
71 |
# result = response.json().get('content', ['[Anthropic response placeholder]'])[0]
|
|
|
72 |
time.sleep(2)
|
73 |
result = f"[Simulated response for {task_type}]"
|
74 |
logger.info(f"Anthropic API success for task: {task_type}")
|
|
|
80 |
def parse_contents(contents, filename):
|
81 |
content_type, content_string = contents.split(',')
|
82 |
decoded = base64.b64decode(content_string)
|
|
|
83 |
try:
|
84 |
if filename.lower().endswith('.txt'):
|
85 |
preview = decoded.decode('utf-8')[:2048]
|
|
|
90 |
logger.error(f"Could not decode file {filename}: {e}")
|
91 |
return f"Error decoding {filename}"
|
92 |
|
|
|
93 |
def navbar():
|
94 |
return dbc.Card(
|
95 |
[
|
|
|
231 |
|
232 |
app.layout = main_layout
|
233 |
|
|
|
234 |
@app.callback(
|
235 |
Output("uploaded-doc-list", "children"),
|
236 |
Output("preview-content", "children"),
|
|
|
270 |
new_chat_history = chat_history if chat_history else []
|
271 |
doc_list_items = []
|
272 |
|
|
|
273 |
if triggered_id == "upload-document" and upload_contents and upload_filename:
|
274 |
if not allowed_file(upload_filename):
|
275 |
feedback = dbc.Alert("Unsupported file type. Please upload PDF, Word, or TXT.", color="danger", dismissable=True)
|
|
|
280 |
feedback = dbc.Alert(f"Uploaded {upload_filename}", color="success", dismissable=True)
|
281 |
logger.info(f"File uploaded: {upload_filename}")
|
282 |
|
|
|
283 |
for doc_id, doc in uploaded_documents.items():
|
284 |
doc_list_items.append(
|
285 |
html.Li(
|
|
|
291 |
)
|
292 |
)
|
293 |
|
|
|
294 |
if isinstance(delete_doc_clicks, list) and any(delete_doc_clicks):
|
295 |
idx = delete_doc_clicks.index(max(delete_doc_clicks))
|
296 |
doc_ids = list(uploaded_documents.keys())
|
|
|
310 |
]
|
311 |
new_preview_content = "" if not uploaded_documents else no_update
|
312 |
|
|
|
313 |
if len(uploaded_documents) == 0 and triggered_id not in ["upload-document", "btn-send-chat"]:
|
314 |
feedback = dbc.Alert("Please upload a document before performing actions.", color="warning", dismissable=True)
|
315 |
logger.warning("Attempted action without documents.")
|
316 |
return doc_list_items, new_preview_content, feedback, loading_message, new_chat_history
|
317 |
|
|
|
318 |
if triggered_id == "btn-send-chat" and chat_input and chat_input.strip():
|
319 |
if not isinstance(new_chat_history, list):
|
320 |
new_chat_history = []
|
|
|
323 |
], style={"marginBottom": "0.25rem"}))
|
324 |
feedback = dbc.Alert("Chat message sent. Instructions will be used in next action.", color="info", dismissable=True)
|
325 |
logger.info(f"Chat message sent: {chat_input}")
|
|
|
326 |
|
|
|
327 |
last_chat = ""
|
328 |
if isinstance(new_chat_history, list) and new_chat_history:
|
329 |
for item in reversed(new_chat_history):
|
|
|
335 |
elif isinstance(chat_input, str):
|
336 |
last_chat = chat_input
|
337 |
|
|
|
338 |
if triggered_id in ["action-shred", "action-generate", "action-compliance", "action-recover", "action-virtual-board", "action-loe"]:
|
339 |
loading_message = dbc.Alert("Processing request, please wait...", color="primary", dismissable=False, style={"textAlign": "center"})
|
340 |
doc_id, doc = next(iter(uploaded_documents.items()))
|
|
|
342 |
file_content = doc['content']
|
343 |
action_type = triggered_id.replace("action-", "").replace("-", " ").title()
|
344 |
|
345 |
+
# DECODE the document content for use in the prompt
|
346 |
+
try:
|
347 |
+
content_type, content_string = file_content.split(',')
|
348 |
+
decoded = base64.b64decode(content_string)
|
349 |
+
if file_name.lower().endswith('.txt'):
|
350 |
+
document_text = decoded.decode('utf-8', errors='replace')
|
351 |
+
else:
|
352 |
+
document_text = f"[Start of document {file_name} as base64]\n{decoded[:350].hex()}...[truncated]\n[End of document]"
|
353 |
+
except Exception as e:
|
354 |
+
logger.error(f"Could not decode document {file_name} for Anthropic: {e}")
|
355 |
+
document_text = f"[Could not decode {file_name}]"
|
356 |
+
|
357 |
+
logger.info(f"Sending document content of length {len(document_text)} to Anthropic for {action_type}")
|
358 |
+
|
359 |
result_holder = {}
|
360 |
|
361 |
def threaded_api_call():
|
|
|
363 |
prompt = (
|
364 |
"Shred this document into requirements, organized by section. "
|
365 |
"Identify requirements by action words (shall, will, perform, etc). "
|
366 |
+
"Output as spreadsheet: PWS Section, Requirement.\n\n"
|
367 |
+
f"Document Content:\n{document_text}\n"
|
368 |
)
|
369 |
task_type = "Shred"
|
370 |
elif triggered_id == "action-generate":
|
371 |
prompt = (
|
372 |
"Generate a detailed proposal response, organized by section/subsection. "
|
373 |
"Focus on approach, steps, workflow, people, processes, technology. "
|
374 |
+
"Include research validation and citations. Address Red Review findings.\n\n"
|
375 |
+
f"Document Content:\n{document_text}\n"
|
376 |
)
|
377 |
task_type = "Generate Proposal Response"
|
378 |
elif triggered_id == "action-compliance":
|
379 |
prompt = (
|
380 |
"Check compliance of the proposal response against the shredded requirements. "
|
381 |
+
"Produce a spreadsheet: PWS number, requirement, finding, recommendation.\n\n"
|
382 |
+
f"Proposal Response Document Content:\n{document_text}\n"
|
383 |
)
|
384 |
task_type = "Check Compliance"
|
385 |
elif triggered_id == "action-recover":
|
386 |
prompt = (
|
387 |
"Using the compliance spreadsheet, improve the document sections. "
|
388 |
"Address recommendations without materially changing content. "
|
389 |
+
"Organize improvements by PWS section headers/subheaders.\n\n"
|
390 |
+
f"Document Content:\n{document_text}\n"
|
391 |
)
|
392 |
task_type = "Recover Document"
|
393 |
elif triggered_id == "action-virtual-board":
|
|
|
395 |
"Evaluate the proposal based on requirements and evaluation criteria. "
|
396 |
"Generate a section-by-section evaluation spreadsheet using ratings: "
|
397 |
"unsatisfactory, satisfactory, good, very good, excellent. Include explanations. "
|
398 |
+
"Base evaluation on sections L and M.\n\n"
|
399 |
+
f"Document Content:\n{document_text}\n"
|
400 |
)
|
401 |
task_type = "Virtual Board"
|
402 |
elif triggered_id == "action-loe":
|
403 |
prompt = (
|
404 |
"Estimate Level of Effort for the proposal. Output spreadsheet: "
|
405 |
+
"PWS task area, brief description, labor categories, estimated hours per category.\n\n"
|
406 |
+
f"Document Content:\n{document_text}\n"
|
407 |
)
|
408 |
task_type = "Estimate LOE"
|
409 |
else:
|
410 |
prompt = ""
|
411 |
task_type = "Unknown"
|
412 |
+
logger.info(f"Prompt to Anthropic for {action_type}: {prompt[:400]}...[truncated]")
|
413 |
result_holder["result"] = anthropic_api_call(prompt, files=[file_content], task_type=task_type, extra_instructions=last_chat or "")
|
414 |
|
415 |
thread = threading.Thread(target=threaded_api_call)
|