Spaces:
Paused
Paused
Update app.py via AI Editor
Browse files
app.py
CHANGED
@@ -1,721 +1,232 @@
|
|
1 |
-
import base64
|
2 |
-
import io
|
3 |
import os
|
|
|
|
|
4 |
import threading
|
5 |
-
import time
|
6 |
-
from typing import List, Tuple
|
7 |
-
import re
|
8 |
import pandas as pd
|
|
|
9 |
from docx import Document
|
10 |
-
from
|
11 |
import dash
|
12 |
import dash_bootstrap_components as dbc
|
13 |
-
from dash import html, dcc, Input, Output, State,
|
14 |
-
import
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
content_type, content_string = contents.split(',')
|
30 |
decoded = base64.b64decode(content_string)
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
elif filename.lower().endswith('.pdf'):
|
38 |
-
pdf = PdfReader(BytesIO(decoded))
|
39 |
-
text = ""
|
40 |
-
for page in pdf.pages:
|
41 |
-
text += page.extract_text()
|
42 |
-
return text
|
43 |
-
else:
|
44 |
-
return f"Unsupported file format: {filename}. Please upload a PDF or DOCX file."
|
45 |
-
except Exception as e:
|
46 |
-
return f"Error processing document: {str(e)}"
|
47 |
-
|
48 |
-
def generate_loe(document: str, is_file: bool = False) -> Tuple[str, pd.DataFrame]:
|
49 |
-
prompt = f"""
|
50 |
-
Analyze the following document and provide a Level of Effort (LOE) breakdown:
|
51 |
-
Document:
|
52 |
-
{document}
|
53 |
-
For each section header in the document:
|
54 |
-
1. Identify each and every task to be completed from the document from each heading
|
55 |
-
2. Determine the appropriate labor categories for each task
|
56 |
-
3. Generously Over Estimate the number of hours required for each labor category to complete the task as management reserve is needed
|
57 |
-
Provide a detailed breakdown and then summarize the information in a tabular format with the following columns:
|
58 |
-
- Task Summary
|
59 |
-
- Labor Categories
|
60 |
-
- Hours per Labor Category
|
61 |
-
- Total Hours
|
62 |
-
Present the detailed breakdown first, followed by the summary table.
|
63 |
-
Ensure the table is properly formatted with | as column separators and a header row.
|
64 |
-
"""
|
65 |
-
response = model.generate_content(prompt)
|
66 |
-
response_text = response.text
|
67 |
-
|
68 |
-
# Extract the table from the response
|
69 |
-
table_start = response_text.find("| Task Summary |")
|
70 |
-
table_end = response_text.find("\n\n", table_start)
|
71 |
-
table_text = response_text[table_start:table_end]
|
72 |
-
|
73 |
-
# Convert the table to a pandas DataFrame
|
74 |
-
try:
|
75 |
-
if not table_text.strip():
|
76 |
-
raise pd.errors.EmptyDataError("No table found in the response")
|
77 |
-
df = pd.read_csv(StringIO(table_text), sep='|', skipinitialspace=True).dropna(axis=1, how='all')
|
78 |
-
df.columns = df.columns.str.strip()
|
79 |
-
except pd.errors.EmptyDataError:
|
80 |
-
# If no table is found or it's empty, create a default DataFrame
|
81 |
-
df = pd.DataFrame(columns=['Task Summary', 'Labor Categories', 'Hours per Labor Category', 'Total Hours'])
|
82 |
-
response_text += "\n\nNote: No detailed LOE table could be generated from the AI response."
|
83 |
-
|
84 |
-
return response_text, df
|
85 |
-
|
86 |
-
# Convert the table to a pandas DataFrame
|
87 |
-
try:
|
88 |
-
if not table_text.strip():
|
89 |
-
raise pd.errors.EmptyDataError("No table found in the response")
|
90 |
-
df = pd.read_csv(StringIO(table_text), sep='|', skipinitialspace=True).dropna(axis=1, how='all')
|
91 |
-
df.columns = df.columns.str.strip()
|
92 |
-
except pd.errors.EmptyDataError:
|
93 |
-
# If no table is found or it's empty, create a default DataFrame
|
94 |
-
df = pd.DataFrame(columns=['Task Summary', 'Labor Categories', 'Hours per Labor Category', 'Total Hours'])
|
95 |
-
response_text += "\n\nNote: No detailed LOE table could be generated from the AI response."
|
96 |
-
|
97 |
-
return response_text, df
|
98 |
-
|
99 |
-
def generate_outline(text: str, instructions: str) -> str:
|
100 |
-
prompt = f"""
|
101 |
-
Analyze the following Project Work Statement (PWS) and create an outline
|
102 |
-
focusing on sections the indicate specific tasks and L&M (for compliance and writing guide). Extract the main headers, subheaders, and specific
|
103 |
-
requirements in each section. Pay special attention to requirements indicated
|
104 |
-
by words like "shall", "will", "must", and similar imperative language.
|
105 |
-
Additional instructions: {instructions}
|
106 |
-
Document text:
|
107 |
-
{text}
|
108 |
-
Provide the outline in a structured format, clearly highlighting the specific
|
109 |
-
requirements and their associated sections.
|
110 |
-
"""
|
111 |
-
response = model.generate_content(prompt)
|
112 |
-
return response.text
|
113 |
-
|
114 |
-
def generate_pink_team_document(outline: str, instructions: str) -> str:
|
115 |
-
prompt = f"""
|
116 |
-
Based on the following outline of a Project Work Statement (PWS):
|
117 |
-
{outline}
|
118 |
-
Additional instructions: {instructions}
|
119 |
-
Create a detailed response document as if MicroHealth is responding to this PWS.
|
120 |
-
Follow these guidelines:
|
121 |
-
1. Use Wikipedia style writing with active voice. Be firm with the approach, no soft words like could be, may be, should, might. Use definitve language.
|
122 |
-
2. For each requirement, describe in detail how MicroHealth will innovate to address it.
|
123 |
-
3. Explain the industry best practices that will be applied and the workflow to accomplish the steps in the best practice to address the requirement.
|
124 |
-
4. Provide measurable outcomes for the customer.
|
125 |
-
5. ALWAYS nLimit the use of bullet points and write predominantly in paragraph format.
|
126 |
-
6. Ensure a logical flow of steps taken by MicroHealth for each requirement.
|
127 |
-
7. Where applicable, describe the labor category or labor categories that perform the task as part of the process
|
128 |
-
8. Analyze the given requirement for MicroHealth and describe the proposed solution, incorporating relevant industry best practices.
|
129 |
-
9. Focus on Implementation: Ensure responses primarily detail the "how" of the approach, emphasizing steps, industry standards, best practices, and processes informed by authoritative sources such as GAO reports.
|
130 |
-
10. Value Proposition: Align with the customer's mission and objectives.
|
131 |
-
11. Persuasive Writing: Use strong, active language.
|
132 |
-
12. Implementation Focus: Detail specific implementation processes.
|
133 |
-
13. Quantifiable Impact: Use metrics and success stories to demonstrate value.
|
134 |
-
Generate a comprehensive response that showcases MicroHealth's expertise and approach.
|
135 |
-
"""
|
136 |
-
response = model.generate_content(prompt)
|
137 |
-
return response.text
|
138 |
-
|
139 |
-
def evaluate_compliance(document: str, requirements: str) -> str:
|
140 |
-
prompt = f"""
|
141 |
-
Evaluate the following document against the requirements from sections L&M of the PWS:
|
142 |
-
Document:
|
143 |
-
{document}
|
144 |
-
Requirements:
|
145 |
-
{requirements}
|
146 |
-
Provide a compliance report by section number, highlighting:
|
147 |
-
1. Areas that need improvement
|
148 |
-
2. Suggestions on how MicroHealth can better respond to the requirements
|
149 |
-
3. Best industry practices that should be applied
|
150 |
-
4. Measurable outcomes that should be included
|
151 |
-
5. Organize by document section headers and numbers
|
152 |
-
Format the report clearly by section number.
|
153 |
-
"""
|
154 |
-
response = model.generate_content(prompt)
|
155 |
-
return response.text
|
156 |
-
|
157 |
-
def generate_red_document(document: str, compliance_report: str, instructions: str) -> str:
|
158 |
-
prompt = f"""
|
159 |
-
Based on the following document and compliance report:
|
160 |
-
Original Document:
|
161 |
-
{document}
|
162 |
-
Compliance Report:
|
163 |
-
{compliance_report}
|
164 |
-
Additional instructions: {instructions}
|
165 |
-
Generate a revised "Red Team" document that addresses all issues found in the compliance report.
|
166 |
-
Follow these guidelines:
|
167 |
-
1. Use Wikipedia style writing with active voice. Be firm with the approach, no soft words like could be, may be, should, might. Use definitive language.
|
168 |
-
2. For each requirement, describe in detail how MicroHealth will innovate to address it.
|
169 |
-
3. Explain the industry best practices that will be applied and the workflow to accomplish the steps in the best practice to address the requirement.
|
170 |
-
4. Provide measurable outcomes for the customer.
|
171 |
-
5. ALWAYS Limit the use of bullet points and write predominantly in paragraph format.
|
172 |
-
6. Ensure a logical flow of steps taken by MicroHealth for each requirement.
|
173 |
-
7. Where applicable, describe the labor category or labor categories that perform the task as part of the process
|
174 |
-
8. Analyze the given requirement for MicroHealth and describe the proposed solution, incorporating relevant industry best practices.
|
175 |
-
9. Focus on Implementation: Ensure responses primarily detail the "how" of the approach, emphasizing steps, industry standards, best practices, and processes informed by authoritative sources such as GAO reports.
|
176 |
-
10. Value Proposition: Align with the customer's mission and objectives.
|
177 |
-
11. Persuasive Writing: Use strong, active language.
|
178 |
-
12. Implementation Focus: Detail specific implementation processes.
|
179 |
-
13. Quantifiable Impact: Use metrics and success stories to demonstrate value.
|
180 |
-
Generate a comprehensive response that showcases MicroHealth's expertise and approach.
|
181 |
-
|
182 |
-
"""
|
183 |
-
response = model.generate_content(prompt)
|
184 |
-
return response.text
|
185 |
-
|
186 |
-
def generate_loe(document: str, is_file: bool = False) -> Tuple[str, pd.DataFrame]:
|
187 |
-
if is_file:
|
188 |
-
# Process the uploaded document
|
189 |
-
document_text = process_document(document, document.split(',')[0])
|
190 |
-
else:
|
191 |
-
document_text = document
|
192 |
-
|
193 |
-
prompt = f"""
|
194 |
-
Analyze the following document and provide a Level of Effort (LOE) breakdown:
|
195 |
-
Document:
|
196 |
-
{document_text}
|
197 |
-
For each section header in the document:
|
198 |
-
1. Identify the tasks to be completed
|
199 |
-
2. Determine the appropriate labor categories for each task
|
200 |
-
3. Estimate the number of hours required for each labor category to complete the task
|
201 |
-
Provide a detailed breakdown and then summarize the information in a tabular format with the following columns:
|
202 |
-
- Task Summary
|
203 |
-
- Labor Categories
|
204 |
-
- Hours per Labor Category
|
205 |
-
- Total Hours
|
206 |
-
Present the detailed breakdown first, followed by the summary table.
|
207 |
-
Ensure the table is properly formatted with | as column separators and a header row.
|
208 |
-
"""
|
209 |
-
response = model.generate_content(prompt)
|
210 |
-
response_text = response.text
|
211 |
-
|
212 |
-
# Extract the table from the response
|
213 |
-
table_start = response_text.find("| Task Summary |")
|
214 |
-
table_end = response_text.find("\n\n", table_start)
|
215 |
-
table_text = response_text[table_start:table_end]
|
216 |
-
|
217 |
-
# Convert the table to a pandas DataFrame
|
218 |
-
try:
|
219 |
-
if not table_text.strip():
|
220 |
-
raise pd.errors.EmptyDataError("No table found in the response")
|
221 |
-
df = pd.read_csv(StringIO(table_text), sep='|', skipinitialspace=True).dropna(axis=1, how='all')
|
222 |
-
df.columns = df.columns.str.strip()
|
223 |
-
except pd.errors.EmptyDataError:
|
224 |
-
# If no table is found or it's empty, create a default DataFrame
|
225 |
-
df = pd.DataFrame(columns=['Task Summary', 'Labor Categories', 'Hours per Labor Category', 'Total Hours'])
|
226 |
-
response_text += "\n\nNote: No detailed LOE table could be generated from the AI response."
|
227 |
-
|
228 |
-
return response_text, df
|
229 |
-
|
230 |
-
# Layout
|
231 |
-
app.layout = dbc.Container([
|
232 |
-
html.H1("MicroHealth PWS Analysis and Response Generator", className="my-4"),
|
233 |
-
dbc.Tabs([
|
234 |
-
dbc.Tab(label="Shred", tab_id="shred", children=[
|
235 |
-
dbc.Textarea(
|
236 |
-
id='shred-instructions',
|
237 |
-
placeholder="Enter any additional instructions for shredding the document...",
|
238 |
-
style={'height': '100px', 'marginBottom': '10px'}
|
239 |
-
),
|
240 |
-
dcc.Upload(
|
241 |
-
id='upload-document',
|
242 |
-
children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
|
243 |
-
style={
|
244 |
-
'width': '100%',
|
245 |
-
'height': '60px',
|
246 |
-
'lineHeight': '60px',
|
247 |
-
'borderWidth': '1px',
|
248 |
-
'borderStyle': 'dashed',
|
249 |
-
'borderRadius': '5px',
|
250 |
-
'textAlign': 'center',
|
251 |
-
'margin': '10px'
|
252 |
-
},
|
253 |
-
multiple=False
|
254 |
-
),
|
255 |
-
dbc.Spinner(html.Div(id='shred-output')),
|
256 |
-
dbc.Button("Download Outline", id="download-shred", className="mt-3"),
|
257 |
-
dcc.Download(id="download-shred-doc")
|
258 |
-
]),
|
259 |
-
dbc.Tab(label="Pink", tab_id="pink", children=[
|
260 |
-
dbc.Textarea(
|
261 |
-
id='pink-instructions',
|
262 |
-
placeholder="Enter any additional instructions for generating the Pink Team document...",
|
263 |
-
style={'height': '100px', 'marginBottom': '10px'}
|
264 |
-
),
|
265 |
-
dbc.Button("Generate Pink Team Document", id="generate-pink", className="mt-3"),
|
266 |
-
dbc.Spinner(html.Div(id='pink-output')),
|
267 |
-
dbc.Button("Download Pink Team Document", id="download-pink", className="mt-3"),
|
268 |
-
dcc.Download(id="download-pink-doc")
|
269 |
-
]),
|
270 |
-
dbc.Tab(label="P.Review", tab_id="p-review", children=[
|
271 |
-
dcc.Upload(
|
272 |
-
id='upload-p-review',
|
273 |
-
children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
|
274 |
-
style={
|
275 |
-
'width': '100%',
|
276 |
-
'height': '60px',
|
277 |
-
'lineHeight': '60px',
|
278 |
-
'borderWidth': '1px',
|
279 |
-
'borderStyle': 'dashed',
|
280 |
-
'borderRadius': '5px',
|
281 |
-
'textAlign': 'center',
|
282 |
-
'margin': '10px'
|
283 |
-
},
|
284 |
-
multiple=False
|
285 |
-
),
|
286 |
-
dbc.Button("Evaluate Compliance", id="evaluate-p-review", className="mt-3"),
|
287 |
-
dbc.Spinner(html.Div(id='p-review-output')),
|
288 |
-
dbc.Button("Download P.Review Report", id="download-p-review", className="mt-3"),
|
289 |
-
dcc.Download(id="download-p-review-doc")
|
290 |
-
]),
|
291 |
-
dbc.Tab(label="Red", tab_id="red", children=[
|
292 |
-
dbc.Textarea(
|
293 |
-
id='red-instructions',
|
294 |
-
placeholder="Enter any additional instructions for generating the Red Team document...",
|
295 |
-
style={'height': '100px', 'marginBottom': '10px'}
|
296 |
-
),
|
297 |
-
dcc.Upload(
|
298 |
-
id='upload-red',
|
299 |
-
children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
|
300 |
-
style={
|
301 |
-
'width': '100%',
|
302 |
-
'height': '60px',
|
303 |
-
'lineHeight': '60px',
|
304 |
-
'borderWidth': '1px',
|
305 |
-
'borderStyle': 'dashed',
|
306 |
-
'borderRadius': '5px',
|
307 |
-
'textAlign': 'center',
|
308 |
-
'margin': '10px'
|
309 |
-
},
|
310 |
-
multiple=False
|
311 |
-
),
|
312 |
-
dbc.Button("Generate Red Team Document", id="generate-red", className="mt-3"),
|
313 |
-
dbc.Spinner(html.Div(id='red-output')),
|
314 |
-
dbc.Button("Download Red Team Document", id="download-red", className="mt-3"),
|
315 |
-
dcc.Download(id="download-red-doc")
|
316 |
-
]),
|
317 |
-
dbc.Tab(label="R.Review", tab_id="r-review", children=[
|
318 |
-
dcc.Upload(
|
319 |
-
id='upload-r-review',
|
320 |
-
children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
|
321 |
-
style={
|
322 |
-
'width': '100%',
|
323 |
-
'height': '60px',
|
324 |
-
'lineHeight': '60px',
|
325 |
-
'borderWidth': '1px',
|
326 |
-
'borderStyle': 'dashed',
|
327 |
-
'borderRadius': '5px',
|
328 |
-
'textAlign': 'center',
|
329 |
-
'margin': '10px'
|
330 |
-
},
|
331 |
-
multiple=False
|
332 |
-
),
|
333 |
-
dbc.Button("Evaluate Compliance", id="evaluate-r-review", className="mt-3"),
|
334 |
-
dbc.Spinner(html.Div(id='r-review-output')),
|
335 |
-
dbc.Button("Download R.Review Report", id="download-r-review", className="mt-3"),
|
336 |
-
dcc.Download(id="download-r-review-doc")
|
337 |
-
]),
|
338 |
-
dbc.Tab(label="G.Review", tab_id="g-review", children=[
|
339 |
-
dcc.Upload(
|
340 |
-
id='upload-g-review',
|
341 |
-
children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
|
342 |
-
style={
|
343 |
-
'width': '100%',
|
344 |
-
'height': '60px',
|
345 |
-
'lineHeight': '60px',
|
346 |
-
'borderWidth': '1px',
|
347 |
-
'borderStyle': 'dashed',
|
348 |
-
'borderRadius': '5px',
|
349 |
-
'textAlign': 'center',
|
350 |
-
'margin': '10px'
|
351 |
-
},
|
352 |
-
multiple=False
|
353 |
-
),
|
354 |
-
dbc.Button("Evaluate Compliance", id="evaluate-g-review", className="mt-3"),
|
355 |
-
dbc.Spinner(html.Div(id='g-review-output')),
|
356 |
-
dbc.Button("Download G.Review Report", id="download-g-review", className="mt-3"),
|
357 |
-
dcc.Download(id="download-g-review-doc")
|
358 |
-
]),
|
359 |
-
dbc.Tab(label="LOE", tab_id="loe", children=[
|
360 |
-
dcc.Upload(
|
361 |
-
id='upload-loe',
|
362 |
-
children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
|
363 |
-
style={
|
364 |
-
'width': '100%',
|
365 |
-
'height': '60px',
|
366 |
-
'lineHeight': '60px',
|
367 |
-
'borderWidth': '1px',
|
368 |
-
'borderStyle': 'dashed',
|
369 |
-
'borderRadius': '5px',
|
370 |
-
'textAlign': 'center',
|
371 |
-
'margin': '10px'
|
372 |
-
},
|
373 |
-
multiple=False
|
374 |
-
),
|
375 |
-
dbc.Button("Generate LOE", id="generate-loe", className="mt-3"),
|
376 |
-
dbc.Spinner(html.Div(id='loe-output')),
|
377 |
-
dbc.Button("Download LOE Report", id="download-loe", className="mt-3"),
|
378 |
-
dcc.Download(id="download-loe-doc")
|
379 |
-
]),
|
380 |
-
], id="tabs", active_tab="shred"),
|
381 |
-
])
|
382 |
-
|
383 |
-
@app.callback(
|
384 |
-
Output('shred-output', 'children'),
|
385 |
-
Input('upload-document', 'contents'),
|
386 |
-
State('upload-document', 'filename'),
|
387 |
-
State('shred-instructions', 'value')
|
388 |
-
)
|
389 |
-
def update_shred_output(contents, filename, instructions):
|
390 |
-
if contents is None:
|
391 |
-
return "Upload a document to begin."
|
392 |
-
|
393 |
-
text = process_document(contents, filename)
|
394 |
-
outline = generate_outline(text, instructions or "")
|
395 |
-
return dcc.Markdown(outline)
|
396 |
-
|
397 |
-
@app.callback(
|
398 |
-
Output('pink-output', 'children'),
|
399 |
-
Input('generate-pink', 'n_clicks'),
|
400 |
-
State('shred-output', 'children'),
|
401 |
-
State('pink-instructions', 'value')
|
402 |
-
)
|
403 |
-
def update_pink_output(n_clicks, shred_output, instructions):
|
404 |
-
if n_clicks is None or shred_output is None:
|
405 |
-
return "Generate an outline in the Shred tab first."
|
406 |
-
|
407 |
-
pink_doc = generate_pink_team_document(shred_output, instructions or "")
|
408 |
-
return dcc.Markdown(pink_doc)
|
409 |
-
|
410 |
-
@app.callback(
|
411 |
-
Output('p-review-output', 'children'),
|
412 |
-
Input('evaluate-p-review', 'n_clicks'),
|
413 |
-
State('upload-p-review', 'contents'),
|
414 |
-
State('upload-p-review', 'filename'),
|
415 |
-
State('pink-output', 'children'),
|
416 |
-
State('shred-output', 'children')
|
417 |
-
)
|
418 |
-
def update_p_review_output(n_clicks, contents, filename, pink_doc, requirements):
|
419 |
-
if n_clicks is None:
|
420 |
-
return "Click 'Evaluate Compliance' to begin."
|
421 |
-
|
422 |
-
if contents:
|
423 |
-
document = process_document(contents, filename)
|
424 |
-
elif pink_doc:
|
425 |
-
document = pink_doc
|
426 |
-
else:
|
427 |
-
return "Please upload a document or generate a Pink Team document first."
|
428 |
-
|
429 |
-
compliance_report = evaluate_compliance(document, requirements)
|
430 |
-
return dcc.Markdown(compliance_report)
|
431 |
-
|
432 |
-
@app.callback(
|
433 |
-
Output('g-review-output', 'children'),
|
434 |
-
Input('evaluate-g-review', 'n_clicks'),
|
435 |
-
State('upload-g-review', 'contents'),
|
436 |
-
State('upload-g-review', 'filename'),
|
437 |
-
State('shred-output', 'children')
|
438 |
-
)
|
439 |
-
def update_g_review_output(n_clicks, contents, filename, requirements):
|
440 |
-
if n_clicks is None:
|
441 |
-
return "Click 'Evaluate Compliance' to begin."
|
442 |
-
|
443 |
-
if contents is None:
|
444 |
-
return "Please upload a document first."
|
445 |
-
|
446 |
-
document = process_document(contents, filename)
|
447 |
-
compliance_report = evaluate_compliance(document, requirements)
|
448 |
-
return dcc.Markdown(compliance_report)
|
449 |
-
|
450 |
-
@app.callback(
|
451 |
-
Output('loe-output', 'children'),
|
452 |
-
Input('generate-loe', 'n_clicks'),
|
453 |
-
State('upload-loe', 'contents'),
|
454 |
-
State('shred-output', 'children')
|
455 |
-
)
|
456 |
-
def update_loe_output(n_clicks, upload_contents, shred_output):
|
457 |
-
if n_clicks is None:
|
458 |
-
return "Click 'Generate LOE' to begin."
|
459 |
-
|
460 |
-
try:
|
461 |
-
if upload_contents:
|
462 |
-
loe_text, loe_df = generate_loe(upload_contents, is_file=True)
|
463 |
-
elif shred_output:
|
464 |
-
loe_text, loe_df = generate_loe(shred_output)
|
465 |
-
else:
|
466 |
-
return "Please upload a document or complete the Shred tab first."
|
467 |
-
|
468 |
-
return [
|
469 |
-
dcc.Markdown(loe_text),
|
470 |
-
dash_table.DataTable(
|
471 |
-
data=loe_df.to_dict('records'),
|
472 |
-
columns=[{'name': i, 'id': i} for i in loe_df.columns],
|
473 |
-
style_table={'overflowX': 'auto'},
|
474 |
-
style_cell={'textAlign': 'left', 'padding': '5px'},
|
475 |
-
style_header={'backgroundColor': 'rgb(230, 230, 230)', 'fontWeight': 'bold'}
|
476 |
-
)
|
477 |
-
]
|
478 |
-
except Exception as e:
|
479 |
-
return f"An error occurred: {str(e)}"
|
480 |
-
|
481 |
-
@app.callback(
|
482 |
-
Output('red-output', 'children'),
|
483 |
-
Input('generate-red', 'n_clicks'),
|
484 |
-
State('upload-red', 'contents'),
|
485 |
-
State('upload-red', 'filename'),
|
486 |
-
State('p-review-output', 'children'),
|
487 |
-
State('red-instructions', 'value')
|
488 |
-
)
|
489 |
-
def update_red_output(n_clicks, contents, filename, p_review_output, instructions):
|
490 |
-
if n_clicks is None:
|
491 |
-
return "Click 'Generate Red Team Document' to begin."
|
492 |
-
|
493 |
-
if contents:
|
494 |
-
document = process_document(contents, filename)
|
495 |
-
elif p_review_output:
|
496 |
-
document = p_review_output
|
497 |
else:
|
498 |
-
return "
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
)
|
511 |
-
|
512 |
-
if
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
compliance_report = evaluate_compliance(document, requirements)
|
523 |
-
return dcc.Markdown(compliance_report)
|
524 |
|
525 |
def parse_markdown(doc, content):
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
for para in paragraphs:
|
530 |
-
# Check for headers
|
531 |
-
header_match = re.match(r'^(#{1,6})\s+(.+)$', para)
|
532 |
-
if header_match:
|
533 |
-
level = len(header_match.group(1))
|
534 |
-
text = header_match.group(2)
|
535 |
-
doc.add_heading(text, level=level)
|
536 |
-
else:
|
537 |
-
p = doc.add_paragraph()
|
538 |
-
# Split paragraph into runs
|
539 |
-
runs = re.split(r'(\*\*|\*|__|\~\~)', para)
|
540 |
-
is_bold = is_italic = is_underline = is_strikethrough = False
|
541 |
-
for run in runs:
|
542 |
-
if run == '**' or run == '__':
|
543 |
-
is_bold = not is_bold
|
544 |
-
elif run == '*':
|
545 |
-
is_italic = not is_italic
|
546 |
-
elif run == '~~':
|
547 |
-
is_strikethrough = not is_strikethrough
|
548 |
-
else:
|
549 |
-
r = p.add_run(run)
|
550 |
-
r.bold = is_bold
|
551 |
-
r.italic = is_italic
|
552 |
-
r.underline = is_underline
|
553 |
-
r.font.strike = is_strikethrough
|
554 |
|
555 |
def create_docx(content):
|
556 |
doc = Document()
|
557 |
-
|
558 |
-
# Add styles
|
559 |
-
styles = doc.styles
|
560 |
-
style_names = [style.name for style in styles]
|
561 |
-
if 'Code' not in style_names:
|
562 |
-
code_style = styles.add_style('Code', WD_STYLE_TYPE.PARAGRAPH)
|
563 |
-
code_font = code_style.font
|
564 |
-
code_font.name = 'Courier New'
|
565 |
-
code_font.size = Pt(10)
|
566 |
-
|
567 |
parse_markdown(doc, content)
|
568 |
return doc
|
569 |
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
)
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
)
|
604 |
-
|
605 |
-
|
606 |
-
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
611 |
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
|
633 |
-
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
)
|
639 |
-
def download_r_review(n_clicks, r_review_output):
|
640 |
-
if r_review_output is None:
|
641 |
-
return dash.no_update
|
642 |
-
doc = create_docx(r_review_output)
|
643 |
-
buffer = BytesIO()
|
644 |
-
doc.save(buffer)
|
645 |
-
return dcc.send_bytes(buffer.getvalue(), "r_review_report.docx")
|
646 |
-
|
647 |
-
@app.callback(
|
648 |
-
Output("download-g-review-doc", "data"),
|
649 |
-
Input("download-g-review", "n_clicks"),
|
650 |
-
State('g-review-output', 'children'),
|
651 |
-
prevent_initial_call=True,
|
652 |
-
)
|
653 |
-
def download_g_review(n_clicks, g_review_output):
|
654 |
-
if g_review_output is None:
|
655 |
-
return dash.no_update
|
656 |
-
doc = create_docx(g_review_output)
|
657 |
-
buffer = BytesIO()
|
658 |
-
doc.save(buffer)
|
659 |
-
return dcc.send_bytes(buffer.getvalue(), "g_review_report.docx")
|
660 |
|
661 |
@app.callback(
|
662 |
-
Output(
|
663 |
-
Input(
|
664 |
-
State('loe-output', 'children'),
|
665 |
-
prevent_initial_call=True,
|
666 |
)
|
667 |
-
def
|
668 |
-
|
669 |
-
return dash.no_update
|
670 |
-
loe_text = loe_output[0]['props']['children']
|
671 |
-
doc = create_docx(loe_text)
|
672 |
-
buffer = BytesIO()
|
673 |
-
doc.save(buffer)
|
674 |
-
return dcc.send_bytes(buffer.getvalue(), "loe_report.docx")
|
675 |
-
|
676 |
-
from dash import callback_context
|
677 |
|
678 |
@app.callback(
|
679 |
-
Output('
|
680 |
-
|
681 |
-
Input('
|
682 |
-
|
683 |
-
State('
|
684 |
-
|
|
|
|
|
685 |
)
|
686 |
-
def
|
|
|
|
|
687 |
ctx = callback_context
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
701 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
702 |
else:
|
703 |
-
|
704 |
-
|
705 |
-
return [
|
706 |
-
dcc.Markdown(loe_text),
|
707 |
-
dash_table.DataTable(
|
708 |
-
data=loe_df.to_dict('records'),
|
709 |
-
columns=[{'name': i, 'id': i} for i in loe_df.columns],
|
710 |
-
style_table={'overflowX': 'auto'},
|
711 |
-
style_cell={'textAlign': 'left', 'padding': '5px'},
|
712 |
-
style_header={'backgroundColor': 'rgb(230, 230, 230)', 'fontWeight': 'bold'}
|
713 |
-
)
|
714 |
-
]
|
715 |
-
except Exception as e:
|
716 |
-
return f"An error occurred: {str(e)}"
|
717 |
|
718 |
if __name__ == '__main__':
|
719 |
print("Starting the Dash application...")
|
720 |
-
app.run(debug=True, host='0.0.0.0', port=7860)
|
721 |
print("Dash application has finished running.")
|
|
|
|
|
|
|
1 |
import os
|
2 |
+
import base64
|
3 |
+
import logging
|
4 |
import threading
|
|
|
|
|
|
|
5 |
import pandas as pd
|
6 |
+
from io import BytesIO, StringIO
|
7 |
from docx import Document
|
8 |
+
from PyPDF2 import PdfReader
|
9 |
import dash
|
10 |
import dash_bootstrap_components as dbc
|
11 |
+
from dash import html, dcc, Input, Output, State, dash_table, callback_context
|
12 |
+
import anthropic
|
13 |
+
|
14 |
+
logging.basicConfig(level=logging.INFO)
|
15 |
+
ANTHROPIC_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
|
16 |
+
anthropic_client = anthropic.Anthropic(api_key=ANTHROPIC_KEY)
|
17 |
+
CLAUDE3_SONNET_MODEL = "claude-3-7-sonnet-20250219"
|
18 |
+
CLAUDE3_MAX_CONTEXT_TOKENS = 200_000
|
19 |
+
CLAUDE3_MAX_OUTPUT_TOKENS = 64_000
|
20 |
+
|
21 |
+
document_types = {
|
22 |
+
"Shred": "Ignore all other instructions and generate only requirements spreadsheet of the Project Work Statement (PWS) identified by action words like shall, will, perform etc. by pws section, requirement. Do not write as if you're responding to the proposal. Its a spreadsheet to distill the requirements, not microhealth's approach",
|
23 |
+
"Pink": "Create a highly detailed Pink Team document based on the PWS outline. Your goal is to be compliant and compelling. Focus on describing the approach and how it will be done, the steps, workflow, people, processes and technology based on well known industry standards to accomplish the task. Be sure to demonstrate innovation.",
|
24 |
+
"Pink Review": "Ignore all other instructions and generate and evaluate compliance of the Pink Team document against the requirements and output only a spreadsheet of non compliant findings by pws number, the goal of that pws section, what made it non compliant and your recommendations for recovery. you must also take into account section L&M of the document which is the evaluation criteria to be sure we address them.",
|
25 |
+
"Red": "Produce a highly detailed Red Team document based on the Pink Review by pws sections. Your goal is to be compliant and compelling by recovering all the findings in Pink Review. Focus on describing the approach and how it will be done, the steps, workflow, people, processes and technology to accomplish the task. Be sure to refer to research that validates the approach and cite sources with measurable outcomes",
|
26 |
+
"Red Review": "Ignore all other instructions and generate and evaluate compliance of the Red Team document against the requirements and output a only a spreadsheet of non compliant findings by pws number, the goal of that pws section, what made it non compliant and your recommendations for recovery. you must also take into account section L&M of the document which is the evaluation criteria to be sure we address them",
|
27 |
+
"Gold": "Create a highly detailed Gold Team document based on the PWS response by pws sections. Your goal is to be compliant and compelling by recovering all the findings in Red Review. Focus on describing the approach and how it will be done, the steps, workflow, people, processes and technology to accomplish the task. Be sure to refer to research that validates the approach and cite sources with measurable outcomes and improve on innovations of the approach",
|
28 |
+
"Gold Review": "Ignore all other instructions and generate and perform a final compliance review against the requirements and output only a spreadsheet of non compliant findings by pws number, the goal of that pws section, what made it non compliant and your recommendations for recovery. you must also take into account section L&M of the document which is the evaluation criteria to be sure we address them",
|
29 |
+
"Virtual Board": "Ignore all other instructions and generate and based on the requirements and in particular the evaulation criteria, you will evaluate the proposal as if you were a contracting office and provide section by section evaluation as unsatisfactory, satisfactory, good, very good, excellent and why and produce only spreadsheet",
|
30 |
+
"LOE": "Ignore all other instructions and generate and generate a Level of Effort (LOE) breakdown and produce only spreadsheet"
|
31 |
+
}
|
32 |
+
|
33 |
+
def process_document(contents, filename):
|
34 |
content_type, content_string = contents.split(',')
|
35 |
decoded = base64.b64decode(content_string)
|
36 |
+
if filename.lower().endswith('.docx'):
|
37 |
+
doc = Document(BytesIO(decoded))
|
38 |
+
return "\n".join([p.text for p in doc.paragraphs])
|
39 |
+
elif filename.lower().endswith('.pdf'):
|
40 |
+
pdf = PdfReader(BytesIO(decoded))
|
41 |
+
return "".join(page.extract_text() or "" for page in pdf.pages)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
else:
|
43 |
+
return f"Unsupported file format: {filename}"
|
44 |
+
|
45 |
+
def call_claude(prompt, max_tokens=2048):
|
46 |
+
res = anthropic_client.messages.create(
|
47 |
+
model=CLAUDE3_SONNET_MODEL,
|
48 |
+
max_tokens=max_tokens,
|
49 |
+
temperature=0.1,
|
50 |
+
system="You are a world class proposal consultant and proposal manager.",
|
51 |
+
messages=[{"role": "user", "content": prompt}]
|
52 |
+
)
|
53 |
+
return res.content[0].text if hasattr(res, "content") else str(res)
|
54 |
+
|
55 |
+
def spreadsheet_to_df(text):
|
56 |
+
lines = [l.strip() for l in text.splitlines() if '|' in l]
|
57 |
+
if not lines: return pd.DataFrame()
|
58 |
+
header = lines[0].strip('|').split('|')
|
59 |
+
data = [l.strip('|').split('|') for l in lines[1:]]
|
60 |
+
return pd.DataFrame(data, columns=[h.strip() for h in header])
|
61 |
+
|
62 |
+
def generate_content(document, doc_type):
|
63 |
+
prompt = f"{document_types[doc_type]}\n\nDocument:\n{document}\n\nOutput only one spreadsheet table, use | as column separator."
|
64 |
+
response = call_claude(prompt, max_tokens=4096)
|
65 |
+
df = spreadsheet_to_df(response)
|
66 |
+
return response, df
|
|
|
|
|
67 |
|
68 |
def parse_markdown(doc, content):
|
69 |
+
for para in content.split('\n\n'):
|
70 |
+
doc.add_paragraph(para)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
def create_docx(content):
|
73 |
doc = Document()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
parse_markdown(doc, content)
|
75 |
return doc
|
76 |
|
77 |
+
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP], suppress_callback_exceptions=True)
|
78 |
+
app.title = "MicroHealth PWS Analyzer"
|
79 |
+
|
80 |
+
nav_items = [
|
81 |
+
dbc.NavLink("Shred", href="#", id="nav-shred"),
|
82 |
+
dbc.NavLink("Pink", href="#", id="nav-pink"),
|
83 |
+
dbc.NavLink("Pink Review", href="#", id="nav-pink-review"),
|
84 |
+
dbc.NavLink("Red", href="#", id="nav-red"),
|
85 |
+
dbc.NavLink("Red Review", href="#", id="nav-red-review"),
|
86 |
+
dbc.NavLink("Gold", href="#", id="nav-gold"),
|
87 |
+
dbc.NavLink("Gold Review", href="#", id="nav-gold-review"),
|
88 |
+
dbc.NavLink("Virtual Board", href="#", id="nav-virtual-board"),
|
89 |
+
dbc.NavLink("LOE", href="#", id="nav-loe"),
|
90 |
+
]
|
91 |
+
|
92 |
+
def make_upload(btn_id):
|
93 |
+
return dcc.Upload(
|
94 |
+
id=f'{btn_id}-upload',
|
95 |
+
children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
|
96 |
+
style={'width': '100%', 'height': '60px', 'lineHeight': '60px', 'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px'},
|
97 |
+
multiple=False
|
98 |
+
)
|
99 |
+
|
100 |
+
def make_textarea(btn_id, placeholder):
|
101 |
+
return dbc.Textarea(
|
102 |
+
id=f'{btn_id}-instructions',
|
103 |
+
placeholder=placeholder,
|
104 |
+
style={'height': '80px', 'marginBottom': '10px', 'width': '100%', 'whiteSpace': 'pre-wrap', 'overflowWrap': 'break-word'}
|
105 |
+
)
|
106 |
+
|
107 |
+
def make_tab(tab_id, label):
|
108 |
+
return dbc.Card(
|
109 |
+
dbc.CardBody([
|
110 |
+
make_textarea(tab_id, f"Instructions for {label} (optional)"),
|
111 |
+
make_upload(tab_id),
|
112 |
+
dbc.Button(f"Generate {label}", id=f'{tab_id}-btn', className="mt-2 btn-primary", n_clicks=0),
|
113 |
+
dcc.Loading(html.Div(id=f'{tab_id}-output'), type="default", parent_style={'justifyContent': 'center'}),
|
114 |
+
dbc.Button(f"Download {label} Report", id=f"{tab_id}-download-btn", className="mt-2 btn-secondary", n_clicks=0),
|
115 |
+
dcc.Download(id=f"{tab_id}-download")
|
116 |
+
]), className="mb-4"
|
117 |
+
)
|
118 |
+
|
119 |
+
main_tabs = [
|
120 |
+
{"id": "shred", "label": "Shred"},
|
121 |
+
{"id": "pink", "label": "Pink"},
|
122 |
+
{"id": "pink-review", "label": "Pink Review"},
|
123 |
+
{"id": "red", "label": "Red"},
|
124 |
+
{"id": "red-review", "label": "Red Review"},
|
125 |
+
{"id": "gold", "label": "Gold"},
|
126 |
+
{"id": "gold-review", "label": "Gold Review"},
|
127 |
+
{"id": "virtual-board", "label": "Virtual Board"},
|
128 |
+
{"id": "loe", "label": "LOE"},
|
129 |
+
]
|
130 |
|
131 |
+
app.layout = dbc.Container([
|
132 |
+
html.H1("MicroHealth PWS Analysis and Response Generator", className="my-3"),
|
133 |
+
dbc.Row([
|
134 |
+
dbc.Col(
|
135 |
+
dbc.Card(
|
136 |
+
dbc.CardBody([
|
137 |
+
html.Div(nav_items, className="nav flex-column"),
|
138 |
+
])
|
139 |
+
), width=3, style={'minWidth': '220px'}
|
140 |
+
),
|
141 |
+
dbc.Col(
|
142 |
+
html.Div([
|
143 |
+
dcc.Tabs(
|
144 |
+
id="main-tabs",
|
145 |
+
value="shred",
|
146 |
+
children=[dcc.Tab(label=tab["label"], value=tab["id"]) for tab in main_tabs],
|
147 |
+
className="mb-3"
|
148 |
+
),
|
149 |
+
html.Div(id="main-content")
|
150 |
+
]),
|
151 |
+
width=9
|
152 |
+
)
|
153 |
+
])
|
154 |
+
], fluid=True)
|
155 |
+
|
156 |
+
tab_cards = {tab["id"]: make_tab(tab["id"], tab["label"]) for tab in main_tabs}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
|
158 |
@app.callback(
|
159 |
+
Output('main-content', 'children'),
|
160 |
+
Input('main-tabs', 'value')
|
|
|
|
|
161 |
)
|
162 |
+
def render_tab(tab):
|
163 |
+
return tab_cards[tab]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
164 |
|
165 |
@app.callback(
|
166 |
+
[Output(f'{tab_id}-output', 'children') for tab_id in tab_cards] +
|
167 |
+
[Output(f"{tab_id}-download", "data") for tab_id in tab_cards],
|
168 |
+
[Input(f'{tab_id}-btn', 'n_clicks') for tab_id in tab_cards] +
|
169 |
+
[Input(f"{tab_id}-download-btn", "n_clicks") for tab_id in tab_cards],
|
170 |
+
[State(f'{tab_id}-upload', 'contents') for tab_id in tab_cards] +
|
171 |
+
[State(f'{tab_id}-upload', 'filename') for tab_id in tab_cards] +
|
172 |
+
[State(f'{tab_id}-instructions', 'value') for tab_id in tab_cards] +
|
173 |
+
[State(f'{tab_id}-output', 'children') for tab_id in tab_cards]
|
174 |
)
|
175 |
+
def handle_all_tabs(*args):
|
176 |
+
n = len(tab_cards)
|
177 |
+
outputs = [None] * (n * 2)
|
178 |
ctx = callback_context
|
179 |
+
if not ctx.triggered: return outputs
|
180 |
+
trig = ctx.triggered[0]['prop_id']
|
181 |
+
for idx, tab_id in enumerate(tab_cards):
|
182 |
+
gen_btn = f"{tab_id}-btn.n_clicks"
|
183 |
+
dl_btn = f"{tab_id}-download-btn.n_clicks"
|
184 |
+
out_idx = idx
|
185 |
+
dl_idx = idx + n
|
186 |
+
upload_idx = idx
|
187 |
+
filename_idx = idx + n
|
188 |
+
instr_idx = idx + 2 * n
|
189 |
+
prev_output_idx = idx + 3 * n
|
190 |
+
|
191 |
+
if trig == gen_btn:
|
192 |
+
upload = args[upload_idx]
|
193 |
+
filename = args[filename_idx]
|
194 |
+
instr = args[instr_idx] or ""
|
195 |
+
doc_type = tab_id.replace('-', ' ').title().replace(' ', '')
|
196 |
+
doc_type = next((k for k in document_types if k.lower().replace(' ', '') == tab_id.replace('-', '')), tab_id.title())
|
197 |
+
if upload and filename:
|
198 |
+
doc = process_document(upload, filename)
|
199 |
+
else:
|
200 |
+
doc = ""
|
201 |
+
if doc or tab_id == "virtual-board":
|
202 |
+
content, df = generate_content(doc, doc_type)
|
203 |
+
if not df.empty:
|
204 |
+
outputs[out_idx] = dash_table.DataTable(
|
205 |
+
data=df.to_dict('records'),
|
206 |
+
columns=[{'name': i, 'id': i} for i in df.columns],
|
207 |
+
style_table={'overflowX': 'auto'},
|
208 |
+
style_cell={'textAlign': 'left', 'padding': '5px'},
|
209 |
+
style_header={'fontWeight': 'bold'}
|
210 |
+
)
|
211 |
+
else:
|
212 |
+
outputs[out_idx] = dcc.Markdown(content)
|
213 |
+
else:
|
214 |
+
outputs[out_idx] = "Please upload a document to begin."
|
215 |
+
elif trig == dl_btn:
|
216 |
+
prev_output = args[prev_output_idx]
|
217 |
+
if prev_output and hasattr(prev_output, 'props') and 'data' in prev_output.props:
|
218 |
+
df = pd.DataFrame(prev_output.props['data'])
|
219 |
+
buffer = BytesIO()
|
220 |
+
df.to_csv(buffer, index=False)
|
221 |
+
outputs[dl_idx] = dcc.send_bytes(buffer.getvalue(), f"{tab_id}_report.csv")
|
222 |
+
elif prev_output:
|
223 |
+
buffer = BytesIO(prev_output.encode("utf-8") if isinstance(prev_output, str) else b"")
|
224 |
+
outputs[dl_idx] = dcc.send_bytes(buffer.getvalue(), f"{tab_id}_report.txt")
|
225 |
else:
|
226 |
+
outputs[dl_idx] = None
|
227 |
+
return outputs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
228 |
|
229 |
if __name__ == '__main__':
|
230 |
print("Starting the Dash application...")
|
231 |
+
threading.Thread(target=lambda: app.run(debug=True, host='0.0.0.0', port=7860, threaded=True)).start()
|
232 |
print("Dash application has finished running.")
|