bluenevus commited on
Commit
cb6077d
·
verified ·
1 Parent(s): 103a652

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +173 -676
app.py CHANGED
@@ -1,18 +1,13 @@
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 io import BytesIO
11
  import dash
12
  import dash_bootstrap_components as dbc
13
- from dash import html, dcc, Input, Output, State, ctx, dash_table, callback_context
14
  import google.generativeai as genai
15
- from docx import Document
16
  from docx.shared import Pt
17
  from docx.enum.style import WD_STYLE_TYPE
18
  from PyPDF2 import PdfReader
@@ -25,10 +20,84 @@ app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
25
  genai.configure(api_key=os.environ["GEMINI_API_KEY"])
26
  model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25')
27
 
28
- def process_document(contents: str, filename: str) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  content_type, content_string = contents.split(',')
30
  decoded = base64.b64decode(content_string)
31
-
32
  try:
33
  if filename.lower().endswith('.docx'):
34
  doc = Document(BytesIO(decoded))
@@ -45,703 +114,131 @@ def process_document(contents: str, filename: str) -> str:
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
-
52
- Document:
53
- {document}
54
-
55
- For each section header in the document:
56
- 1. Identify each and every task to be completed from the document from each heading
57
- 2. Determine the appropriate labor categories for each task
58
- 3. Generously Over Estimate the number of hours required for each labor category to complete the task as management reserve is needed
59
-
60
- Provide a detailed breakdown and then summarize the information in a tabular format with the following columns:
61
- - Task Summary
62
- - Labor Categories
63
- - Hours per Labor Category
64
- - Total Hours
65
-
66
- Present the detailed breakdown first, followed by the summary table.
67
- Ensure the table is properly formatted with | as column separators and a header row.
68
- """
69
- response = model.generate_content(prompt)
70
- response_text = response.text
71
-
72
- # Extract the table from the response
73
- table_start = response_text.find("| Task Summary |")
74
- table_end = response_text.find("\n\n", table_start)
75
- table_text = response_text[table_start:table_end]
76
-
77
- # Convert the table to a pandas DataFrame
78
- try:
79
- if not table_text.strip():
80
- raise pd.errors.EmptyDataError("No table found in the response")
81
- df = pd.read_csv(StringIO(table_text), sep='|', skipinitialspace=True).dropna(axis=1, how='all')
82
- df.columns = df.columns.str.strip()
83
- except pd.errors.EmptyDataError:
84
- # If no table is found or it's empty, create a default DataFrame
85
- df = pd.DataFrame(columns=['Task Summary', 'Labor Categories', 'Hours per Labor Category', 'Total Hours'])
86
- response_text += "\n\nNote: No detailed LOE table could be generated from the AI response."
87
-
88
- return response_text, df
89
-
90
- # Convert the table to a pandas DataFrame
91
- try:
92
- if not table_text.strip():
93
- raise pd.errors.EmptyDataError("No table found in the response")
94
- df = pd.read_csv(StringIO(table_text), sep='|', skipinitialspace=True).dropna(axis=1, how='all')
95
- df.columns = df.columns.str.strip()
96
- except pd.errors.EmptyDataError:
97
- # If no table is found or it's empty, create a default DataFrame
98
- df = pd.DataFrame(columns=['Task Summary', 'Labor Categories', 'Hours per Labor Category', 'Total Hours'])
99
- response_text += "\n\nNote: No detailed LOE table could be generated from the AI response."
100
-
101
- return response_text, df
102
-
103
- def generate_outline(text: str, instructions: str) -> str:
104
- prompt = f"""
105
- Analyze the following Project Work Statement (PWS) and create an outline
106
- focusing on sections the indicate specific tasks and L&M (for compliance and writing guide). Extract the main headers, subheaders, and specific
107
- requirements in each section. Pay special attention to requirements indicated
108
- by words like "shall", "will", "must", and similar imperative language.
109
-
110
- Additional instructions: {instructions}
111
-
112
- Document text:
113
- {text}
114
-
115
- Provide the outline in a structured format, clearly highlighting the specific
116
- requirements and their associated sections.
117
- """
118
- response = model.generate_content(prompt)
119
- return response.text
120
-
121
- def generate_pink_team_document(outline: str, instructions: str) -> str:
122
- prompt = f"""
123
- Based on the following outline of a Project Work Statement (PWS):
124
-
125
- {outline}
126
-
127
- Additional instructions: {instructions}
128
-
129
- Create a detailed response document as if MicroHealth is responding to this PWS.
130
- Follow these guidelines:
131
- 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.
132
- 2. For each requirement, describe in detail how MicroHealth will innovate to address it.
133
- 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.
134
- 4. Provide measurable outcomes for the customer.
135
- 5. ALWAYS nLimit the use of bullet points and write predominantly in paragraph format.
136
- 6. Ensure a logical flow of steps taken by MicroHealth for each requirement.
137
- 7. Where applicable, describe the labor category or labor categories that perform the task as part of the process
138
- 8. Analyze the given requirement for MicroHealth and describe the proposed solution, incorporating relevant industry best practices.
139
- 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.
140
- 10. Value Proposition: Align with the customer's mission and objectives.
141
- 11. Persuasive Writing: Use strong, active language.
142
- 12. Implementation Focus: Detail specific implementation processes.
143
- 13. Quantifiable Impact: Use metrics and success stories to demonstrate value.
144
- 14. Formatting Requirements: Adhere to paragraph format for all responses unless specified otherwise by the user's requirements.
145
-
146
- Generate a comprehensive response that showcases MicroHealth's expertise and approach.
147
- """
148
- response = model.generate_content(prompt)
149
- return response.text
150
-
151
- def evaluate_compliance(document: str, requirements: str) -> str:
152
- prompt = f"""
153
- Evaluate the following document against the requirements from sections L&M of the PWS:
154
-
155
- Document:
156
- {document}
157
-
158
- Requirements:
159
- {requirements}
160
-
161
- Provide a compliance report by section number, highlighting:
162
- 1. Areas that need improvement
163
- 2. Suggestions on how MicroHealth can better respond to the requirements
164
- 3. Best industry practices that should be applied
165
- 4. Measurable outcomes that should be included
166
- 5. Organize by document section headers and numbers
167
-
168
- Format the report clearly by section number.
169
- """
170
- response = model.generate_content(prompt)
171
- return response.text
172
-
173
- def generate_red_document(document: str, compliance_report: str, instructions: str) -> str:
174
- prompt = f"""
175
- Based on the following document and compliance report:
176
-
177
- Original Document:
178
- {document}
179
-
180
- Compliance Report:
181
- {compliance_report}
182
-
183
- Additional instructions: {instructions}
184
-
185
- Generate a revised "Red Team" document that addresses all issues found in the compliance report.
186
- Follow these guidelines:
187
- 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.
188
- 2. For each requirement, describe in detail how MicroHealth will innovate to address it.
189
- 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.
190
- 4. Provide measurable outcomes for the customer.
191
- 5. ALWAYS Limit the use of bullet points and write predominantly in paragraph format.
192
- 6. Ensure a logical flow of steps taken by MicroHealth for each requirement.
193
- 7. Where applicable, describe the labor category or labor categories that perform the task as part of the process
194
- 8. Analyze the given requirement for MicroHealth and describe the proposed solution, incorporating relevant industry best practices.
195
- 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.
196
- 10. Value Proposition: Align with the customer's mission and objectives.
197
- 11. Persuasive Writing: Use strong, active language.
198
- 12. Implementation Focus: Detail specific implementation processes.
199
- 13. Quantifiable Impact: Use metrics and success stories to demonstrate value.
200
- 14. Formatting Requirements: Adhere to paragraph format for all responses unless specified otherwise by the user's requirements.
201
-
202
- Generate a comprehensive response that showcases MicroHealth's expertise and approach.
203
-
204
- """
205
- response = model.generate_content(prompt)
206
- return response.text
207
-
208
- def generate_loe(document: str, is_file: bool = False) -> Tuple[str, pd.DataFrame]:
209
- if is_file:
210
- # Process the uploaded document
211
- document_text = process_document(document, document.split(',')[0])
212
- else:
213
- document_text = document
214
-
215
- prompt = f"""
216
- Analyze the following document and provide a Level of Effort (LOE) breakdown:
217
-
218
- Document:
219
- {document_text}
220
-
221
- For each section header in the document:
222
- 1. Identify the tasks to be completed
223
- 2. Determine the appropriate labor categories for each task
224
- 3. Estimate the number of hours required for each labor category to complete the task
225
-
226
- Provide a detailed breakdown and then summarize the information in a tabular format with the following columns:
227
- - Task Summary
228
- - Labor Categories
229
- - Hours per Labor Category
230
- - Total Hours
231
-
232
- Present the detailed breakdown first, followed by the summary table.
233
- Ensure the table is properly formatted with | as column separators and a header row.
234
- """
235
- response = model.generate_content(prompt)
236
- response_text = response.text
237
-
238
- # Extract the table from the response
239
- table_start = response_text.find("| Task Summary |")
240
- table_end = response_text.find("\n\n", table_start)
241
- table_text = response_text[table_start:table_end]
242
-
243
- # Convert the table to a pandas DataFrame
244
- try:
245
- if not table_text.strip():
246
- raise pd.errors.EmptyDataError("No table found in the response")
247
- df = pd.read_csv(StringIO(table_text), sep='|', skipinitialspace=True).dropna(axis=1, how='all')
248
- df.columns = df.columns.str.strip()
249
- except pd.errors.EmptyDataError:
250
- # If no table is found or it's empty, create a default DataFrame
251
- df = pd.DataFrame(columns=['Task Summary', 'Labor Categories', 'Hours per Labor Category', 'Total Hours'])
252
- response_text += "\n\nNote: No detailed LOE table could be generated from the AI response."
253
-
254
- return response_text, df
255
-
256
- # Layout
257
- app.layout = dbc.Container([
258
- html.H1("MicroHealth PWS Analysis and Response Generator", className="my-4"),
259
- dbc.Tabs([
260
- dbc.Tab(label="Shred", tab_id="shred", children=[
261
- dbc.Textarea(
262
- id='shred-instructions',
263
- placeholder="Enter any additional instructions for shredding the document...",
264
- style={'height': '100px', 'marginBottom': '10px'}
265
- ),
266
- dcc.Upload(
267
- id='upload-document',
268
- children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
269
- style={
270
- 'width': '100%',
271
- 'height': '60px',
272
- 'lineHeight': '60px',
273
- 'borderWidth': '1px',
274
- 'borderStyle': 'dashed',
275
- 'borderRadius': '5px',
276
- 'textAlign': 'center',
277
- 'margin': '10px'
278
- },
279
- multiple=False
280
- ),
281
- dbc.Spinner(html.Div(id='shred-output')),
282
- dbc.Button("Download Outline", id="download-shred", className="mt-3"),
283
- dcc.Download(id="download-shred-doc")
284
- ]),
285
- dbc.Tab(label="Pink", tab_id="pink", children=[
286
- dbc.Textarea(
287
- id='pink-instructions',
288
- placeholder="Enter any additional instructions for generating the Pink Team document...",
289
- style={'height': '100px', 'marginBottom': '10px'}
290
- ),
291
- dbc.Button("Generate Pink Team Document", id="generate-pink", className="mt-3"),
292
- dbc.Spinner(html.Div(id='pink-output')),
293
- dbc.Button("Download Pink Team Document", id="download-pink", className="mt-3"),
294
- dcc.Download(id="download-pink-doc")
295
- ]),
296
- dbc.Tab(label="P.Review", tab_id="p-review", children=[
297
- dcc.Upload(
298
- id='upload-p-review',
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("Evaluate Compliance", id="evaluate-p-review", className="mt-3"),
313
- dbc.Spinner(html.Div(id='p-review-output')),
314
- dbc.Button("Download P.Review Report", id="download-p-review", className="mt-3"),
315
- dcc.Download(id="download-p-review-doc")
316
- ]),
317
- dbc.Tab(label="Red", tab_id="red", children=[
318
- dbc.Textarea(
319
- id='red-instructions',
320
- placeholder="Enter any additional instructions for generating the Red Team document...",
321
- style={'height': '100px', 'marginBottom': '10px'}
322
- ),
323
- dcc.Upload(
324
- id='upload-red',
325
- children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
326
- style={
327
- 'width': '100%',
328
- 'height': '60px',
329
- 'lineHeight': '60px',
330
- 'borderWidth': '1px',
331
- 'borderStyle': 'dashed',
332
- 'borderRadius': '5px',
333
- 'textAlign': 'center',
334
- 'margin': '10px'
335
- },
336
- multiple=False
337
- ),
338
- dbc.Button("Generate Red Team Document", id="generate-red", className="mt-3"),
339
- dbc.Spinner(html.Div(id='red-output')),
340
- dbc.Button("Download Red Team Document", id="download-red", className="mt-3"),
341
- dcc.Download(id="download-red-doc")
342
- ]),
343
- dbc.Tab(label="R.Review", tab_id="r-review", children=[
344
- dcc.Upload(
345
- id='upload-r-review',
346
- children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
347
- style={
348
- 'width': '100%',
349
- 'height': '60px',
350
- 'lineHeight': '60px',
351
- 'borderWidth': '1px',
352
- 'borderStyle': 'dashed',
353
- 'borderRadius': '5px',
354
- 'textAlign': 'center',
355
- 'margin': '10px'
356
- },
357
- multiple=False
358
- ),
359
- dbc.Button("Evaluate Compliance", id="evaluate-r-review", className="mt-3"),
360
- dbc.Spinner(html.Div(id='r-review-output')),
361
- dbc.Button("Download R.Review Report", id="download-r-review", className="mt-3"),
362
- dcc.Download(id="download-r-review-doc")
363
- ]),
364
- dbc.Tab(label="G.Review", tab_id="g-review", children=[
365
- dcc.Upload(
366
- id='upload-g-review',
367
- children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
368
- style={
369
- 'width': '100%',
370
- 'height': '60px',
371
- 'lineHeight': '60px',
372
- 'borderWidth': '1px',
373
- 'borderStyle': 'dashed',
374
- 'borderRadius': '5px',
375
- 'textAlign': 'center',
376
- 'margin': '10px'
377
- },
378
- multiple=False
379
- ),
380
- dbc.Button("Evaluate Compliance", id="evaluate-g-review", className="mt-3"),
381
- dbc.Spinner(html.Div(id='g-review-output')),
382
- dbc.Button("Download G.Review Report", id="download-g-review", className="mt-3"),
383
- dcc.Download(id="download-g-review-doc")
384
- ]),
385
- dbc.Tab(label="LOE", tab_id="loe", children=[
386
- dcc.Upload(
387
- id='upload-loe',
388
- children=html.Div(['Drag and Drop or ', html.A('Select Files')]),
389
- style={
390
- 'width': '100%',
391
- 'height': '60px',
392
- 'lineHeight': '60px',
393
- 'borderWidth': '1px',
394
- 'borderStyle': 'dashed',
395
- 'borderRadius': '5px',
396
- 'textAlign': 'center',
397
- 'margin': '10px'
398
- },
399
- multiple=False
400
- ),
401
- dbc.Button("Generate LOE", id="generate-loe", className="mt-3"),
402
- dbc.Spinner(html.Div(id='loe-output')),
403
- dbc.Button("Download LOE Report", id="download-loe", className="mt-3"),
404
- dcc.Download(id="download-loe-doc")
405
- ]),
406
- ], id="tabs", active_tab="shred"),
407
- ])
408
-
409
  @app.callback(
410
- Output('shred-output', 'children'),
411
  Input('upload-document', 'contents'),
412
  State('upload-document', 'filename'),
413
- State('shred-instructions', 'value')
414
  )
415
- def update_shred_output(contents, filename, instructions):
416
- if contents is None:
417
- return "Upload a document to begin."
418
-
419
- text = process_document(contents, filename)
420
- outline = generate_outline(text, instructions or "")
421
- return dcc.Markdown(outline)
 
 
 
 
 
 
 
 
422
 
423
  @app.callback(
424
- Output('pink-output', 'children'),
425
- Input('generate-pink', 'n_clicks'),
426
- State('shred-output', 'children'),
427
- State('pink-instructions', 'value')
428
  )
429
- def update_pink_output(n_clicks, shred_output, instructions):
430
- if n_clicks is None or shred_output is None:
431
- return "Generate an outline in the Shred tab first."
432
-
433
- pink_doc = generate_pink_team_document(shred_output, instructions or "")
434
- return dcc.Markdown(pink_doc)
 
 
 
 
 
 
 
 
 
 
 
 
 
435
 
436
- @app.callback(
437
- Output('p-review-output', 'children'),
438
- Input('evaluate-p-review', 'n_clicks'),
439
- State('upload-p-review', 'contents'),
440
- State('upload-p-review', 'filename'),
441
- State('pink-output', 'children'),
442
- State('shred-output', 'children')
443
- )
444
- def update_p_review_output(n_clicks, contents, filename, pink_doc, requirements):
445
- if n_clicks is None:
446
- return "Click 'Evaluate Compliance' to begin."
447
-
448
- if contents:
449
- document = process_document(contents, filename)
450
- elif pink_doc:
451
- document = pink_doc
452
- else:
453
- return "Please upload a document or generate a Pink Team document first."
454
-
455
- compliance_report = evaluate_compliance(document, requirements)
456
- return dcc.Markdown(compliance_report)
457
 
458
  @app.callback(
459
- Output('g-review-output', 'children'),
460
- Input('evaluate-g-review', 'n_clicks'),
461
- State('upload-g-review', 'contents'),
462
- State('upload-g-review', 'filename'),
463
- State('shred-output', 'children')
464
  )
465
- def update_g_review_output(n_clicks, contents, filename, requirements):
466
- if n_clicks is None:
467
- return "Click 'Evaluate Compliance' to begin."
468
-
469
- if contents is None:
470
- return "Please upload a document first."
 
471
 
472
- document = process_document(contents, filename)
473
- compliance_report = evaluate_compliance(document, requirements)
474
- return dcc.Markdown(compliance_report)
475
 
476
- @app.callback(
477
- Output('loe-output', 'children'),
478
- Input('generate-loe', 'n_clicks'),
479
- State('upload-loe', 'contents'),
480
- State('shred-output', 'children')
481
- )
482
- def update_loe_output(n_clicks, upload_contents, shred_output):
483
- if n_clicks is None:
484
- return "Click 'Generate LOE' to begin."
485
 
486
  try:
487
- if upload_contents:
488
- loe_text, loe_df = generate_loe(upload_contents, is_file=True)
489
- elif shred_output:
490
- loe_text, loe_df = generate_loe(shred_output)
491
- else:
492
- return "Please upload a document or complete the Shred tab first."
493
-
494
- return [
495
- dcc.Markdown(loe_text),
496
- dash_table.DataTable(
497
- data=loe_df.to_dict('records'),
498
- columns=[{'name': i, 'id': i} for i in loe_df.columns],
499
- style_table={'overflowX': 'auto'},
500
- style_cell={'textAlign': 'left', 'padding': '5px'},
501
- style_header={'backgroundColor': 'rgb(230, 230, 230)', 'fontWeight': 'bold'}
502
- )
503
- ]
504
  except Exception as e:
505
- return f"An error occurred: {str(e)}"
 
506
 
507
  @app.callback(
508
- Output('red-output', 'children'),
509
- Input('generate-red', 'n_clicks'),
510
- State('upload-red', 'contents'),
511
- State('upload-red', 'filename'),
512
- State('p-review-output', 'children'),
513
- State('red-instructions', 'value')
514
  )
515
- def update_red_output(n_clicks, contents, filename, p_review_output, instructions):
516
- if n_clicks is None:
517
- return "Click 'Generate Red Team Document' to begin."
 
 
 
 
 
 
 
 
 
 
 
518
 
519
- if contents:
520
- document = process_document(contents, filename)
521
- elif p_review_output:
522
- document = p_review_output
523
- else:
524
- return "Please upload a document or complete the P.Review first."
525
 
526
- red_doc = generate_red_document(document, p_review_output, instructions or "")
527
- return dcc.Markdown(red_doc) # Wrap the output in dcc.Markdown)
528
 
529
  @app.callback(
530
- Output('r-review-output', 'children'),
531
- Input('evaluate-r-review', 'n_clicks'),
532
- State('upload-r-review', 'contents'),
533
- State('upload-r-review', 'filename'),
534
- State('red-output', 'children'),
535
- State('shred-output', 'children')
536
  )
537
- def update_r_review_output(n_clicks, contents, filename, red_doc, requirements):
538
- if n_clicks is None:
539
- return "Click 'Evaluate Compliance' to begin."
540
-
541
- if contents:
542
- document = process_document(contents, filename)
543
- elif red_doc:
544
- document = red_doc
545
- else:
546
- return "Please upload a document or generate a Red Team document first."
547
 
548
- compliance_report = evaluate_compliance(document, requirements)
549
- return dcc.Markdown(compliance_report)
550
-
551
- def parse_markdown(doc, content):
552
- # Split content into paragraphs
553
- paragraphs = content.split('\n\n')
554
-
555
- for para in paragraphs:
556
- # Check for headers
557
- header_match = re.match(r'^(#{1,6})\s+(.+)$', para)
558
- if header_match:
559
- level = len(header_match.group(1))
560
- text = header_match.group(2)
561
- doc.add_heading(text, level=level)
562
- else:
563
- p = doc.add_paragraph()
564
- # Split paragraph into runs
565
- runs = re.split(r'(\*\*|\*|__|\~\~)', para)
566
- is_bold = is_italic = is_underline = is_strikethrough = False
567
- for run in runs:
568
- if run == '**' or run == '__':
569
- is_bold = not is_bold
570
- elif run == '*':
571
- is_italic = not is_italic
572
- elif run == '~~':
573
- is_strikethrough = not is_strikethrough
574
- else:
575
- r = p.add_run(run)
576
- r.bold = is_bold
577
- r.italic = is_italic
578
- r.underline = is_underline
579
- r.font.strike = is_strikethrough
580
-
581
- def create_docx(content):
582
  doc = Document()
 
583
 
584
- # Add styles
585
- styles = doc.styles
586
- style_names = [style.name for style in styles]
587
- if 'Code' not in style_names:
588
- code_style = styles.add_style('Code', WD_STYLE_TYPE.PARAGRAPH)
589
- code_font = code_style.font
590
- code_font.name = 'Courier New'
591
- code_font.size = Pt(10)
592
-
593
- parse_markdown(doc, content)
594
- return doc
595
-
596
- @app.callback(
597
- Output("download-shred-doc", "data"),
598
- Input("download-shred", "n_clicks"),
599
- State('shred-output', 'children'),
600
- prevent_initial_call=True,
601
- )
602
- def download_shred(n_clicks, shred_output):
603
- if shred_output is None:
604
- return dash.no_update
605
- doc = create_docx(shred_output)
606
- buffer = BytesIO()
607
- doc.save(buffer)
608
- return dcc.send_bytes(buffer.getvalue(), "shred_outline.docx")
609
-
610
- @app.callback(
611
- Output("download-pink-doc", "data"),
612
- Input("download-pink", "n_clicks"),
613
- State('pink-output', 'children'),
614
- prevent_initial_call=True,
615
- )
616
- def download_pink(n_clicks, pink_output):
617
- if pink_output is None:
618
- return dash.no_update
619
- doc = create_docx(pink_output)
620
- buffer = BytesIO()
621
- doc.save(buffer)
622
- return dcc.send_bytes(buffer.getvalue(), "pink_team_document.docx")
623
-
624
- @app.callback(
625
- Output("download-p-review-doc", "data"),
626
- Input("download-p-review", "n_clicks"),
627
- State('p-review-output', 'children'),
628
- prevent_initial_call=True,
629
- )
630
- def download_p_review(n_clicks, p_review_output):
631
- if p_review_output is None:
632
- return dash.no_update
633
- doc = create_docx(p_review_output)
634
- buffer = BytesIO()
635
- doc.save(buffer)
636
- return dcc.send_bytes(buffer.getvalue(), "p_review_report.docx")
637
-
638
- @app.callback(
639
- Output("download-red-doc", "data"),
640
- Input("download-red", "n_clicks"),
641
- State('red-output', 'children'),
642
- prevent_initial_call=True,
643
- )
644
- def download_red(n_clicks, red_output):
645
- if red_output is None:
646
- return dash.no_update
647
-
648
- # Extract the content from the Markdown component
649
- if isinstance(red_output, dict) and 'props' in red_output and 'children' in red_output['props']:
650
- content = red_output['props']['children']
651
- else:
652
- content = str(red_output)
653
 
654
- doc = create_docx(content)
655
- buffer = BytesIO()
656
- doc.save(buffer)
657
- return dcc.send_bytes(buffer.getvalue(), "red_team_document.docx")
658
-
659
- @app.callback(
660
- Output("download-r-review-doc", "data"),
661
- Input("download-r-review", "n_clicks"),
662
- State('r-review-output', 'children'),
663
- prevent_initial_call=True,
664
- )
665
- def download_r_review(n_clicks, r_review_output):
666
- if r_review_output is None:
667
- return dash.no_update
668
- doc = create_docx(r_review_output)
669
- buffer = BytesIO()
670
- doc.save(buffer)
671
- return dcc.send_bytes(buffer.getvalue(), "r_review_report.docx")
672
-
673
- @app.callback(
674
- Output("download-g-review-doc", "data"),
675
- Input("download-g-review", "n_clicks"),
676
- State('g-review-output', 'children'),
677
- prevent_initial_call=True,
678
- )
679
- def download_g_review(n_clicks, g_review_output):
680
- if g_review_output is None:
681
- return dash.no_update
682
- doc = create_docx(g_review_output)
683
- buffer = BytesIO()
684
- doc.save(buffer)
685
- return dcc.send_bytes(buffer.getvalue(), "g_review_report.docx")
686
-
687
- @app.callback(
688
- Output("download-loe-doc", "data"),
689
- Input("download-loe", "n_clicks"),
690
- State('loe-output', 'children'),
691
- prevent_initial_call=True,
692
- )
693
- def download_loe(n_clicks, loe_output):
694
- if loe_output is None or isinstance(loe_output, str):
695
- return dash.no_update
696
- loe_text = loe_output[0]['props']['children']
697
- doc = create_docx(loe_text)
698
- buffer = BytesIO()
699
- doc.save(buffer)
700
- return dcc.send_bytes(buffer.getvalue(), "loe_report.docx")
701
-
702
- from dash import callback_context
703
-
704
- @app.callback(
705
- Output('loe-output', 'children', allow_duplicate=True),
706
- Input('generate-loe', 'n_clicks'),
707
- Input('upload-loe', 'contents'),
708
- State('upload-loe', 'filename'),
709
- State('shred-output', 'children'),
710
- prevent_initial_call=True
711
- )
712
- def update_loe_output(n_clicks, upload_contents, upload_filename, shred_output):
713
- ctx = callback_context
714
- triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
715
-
716
- if not ctx.triggered:
717
- return dash.no_update
718
-
719
- try:
720
- if triggered_id in ['generate-loe', 'upload-loe']:
721
- if upload_contents:
722
- document = process_document(upload_contents, upload_filename)
723
- if document.startswith("Unsupported file format") or document.startswith("Error processing document"):
724
- return document
725
- loe_text, loe_df = generate_loe(document)
726
- elif shred_output:
727
- loe_text, loe_df = generate_loe(shred_output)
728
- else:
729
- return "Please upload a document or complete the Shred tab first."
730
-
731
- return [
732
- dcc.Markdown(loe_text),
733
- dash_table.DataTable(
734
- data=loe_df.to_dict('records'),
735
- columns=[{'name': i, 'id': i} for i in loe_df.columns],
736
- style_table={'overflowX': 'auto'},
737
- style_cell={'textAlign': 'left', 'padding': '5px'},
738
- style_header={'backgroundColor': 'rgb(230, 230, 230)', 'fontWeight': 'bold'}
739
- )
740
- ]
741
- except Exception as e:
742
- return f"An error occurred: {str(e)}"
743
 
744
  if __name__ == '__main__':
745
  print("Starting the Dash application...")
746
- app.run(debug=True, host='0.0.0.0', port=7860)
747
  print("Dash application has finished running.")
 
1
  import base64
2
  import io
3
  import os
 
 
 
 
4
  import pandas as pd
5
  from docx import Document
6
  from io import BytesIO
7
  import dash
8
  import dash_bootstrap_components as dbc
9
+ from dash import html, dcc, Input, Output, State, callback_context
10
  import google.generativeai as genai
 
11
  from docx.shared import Pt
12
  from docx.enum.style import WD_STYLE_TYPE
13
  from PyPDF2 import PdfReader
 
20
  genai.configure(api_key=os.environ["GEMINI_API_KEY"])
21
  model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25')
22
 
23
+ # Global variables
24
+ uploaded_files = {}
25
+ current_document = None
26
+ document_type = None
27
+
28
+ # Document types and their descriptions
29
+ document_types = {
30
+ "Shred": "Generate an outline of the Project Work Statement (PWS)",
31
+ "Pink": "Create a Pink Team document based on the PWS outline",
32
+ "P.Review": "Evaluate compliance of the Pink Team document",
33
+ "Red": "Generate a Red Team document based on the P.Review",
34
+ "R.Review": "Evaluate compliance of the Red Team document",
35
+ "G.Review": "Perform a final compliance review",
36
+ "LOE": "Generate a Level of Effort (LOE) breakdown"
37
+ }
38
+
39
+ app.layout = dbc.Container([
40
+ dbc.Row([
41
+ dbc.Col([
42
+ html.H4("Proposal Documents", className="mt-3 mb-4"),
43
+ dcc.Upload(
44
+ id='upload-document',
45
+ children=html.Div([
46
+ 'Drag and Drop or ',
47
+ html.A('Select Files')
48
+ ]),
49
+ style={
50
+ 'width': '100%',
51
+ 'height': '60px',
52
+ 'lineHeight': '60px',
53
+ 'borderWidth': '1px',
54
+ 'borderStyle': 'dashed',
55
+ 'borderRadius': '5px',
56
+ 'textAlign': 'center',
57
+ 'margin': '10px 0'
58
+ },
59
+ multiple=True
60
+ ),
61
+ html.Div(id='file-list'),
62
+ html.Hr(),
63
+ html.Div([
64
+ dbc.Button(
65
+ doc_type,
66
+ id=f'btn-{doc_type.lower().replace(" ", "-")}',
67
+ color="link",
68
+ className="mb-2 w-100 text-left custom-button",
69
+ style={'overflow': 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap'}
70
+ ) for doc_type in document_types.keys()
71
+ ])
72
+ ], width=3),
73
+ dbc.Col([
74
+ html.Div(style={"height": "20px"}), # Added small gap
75
+ dcc.Loading(
76
+ id="loading-indicator",
77
+ type="dot",
78
+ children=[html.Div(id="loading-output")]
79
+ ),
80
+ html.Div(id='document-preview', className="border p-3 mb-3"),
81
+ dbc.Button("Download Document", id="btn-download", color="success", className="mt-3"),
82
+ dcc.Download(id="download-document"),
83
+ html.Hr(),
84
+ html.Div(style={"height": "20px"}), # Added small gap
85
+ dcc.Loading(
86
+ id="chat-loading",
87
+ type="dot",
88
+ children=[
89
+ dbc.Input(id="chat-input", type="text", placeholder="Chat with AI to update document...", className="mb-2"),
90
+ dbc.Button("Send", id="btn-send-chat", color="primary", className="mb-3"),
91
+ html.Div(id="chat-output")
92
+ ]
93
+ )
94
+ ], width=9)
95
+ ])
96
+ ], fluid=True)
97
+
98
+ def process_document(contents, filename):
99
  content_type, content_string = contents.split(',')
100
  decoded = base64.b64decode(content_string)
 
101
  try:
102
  if filename.lower().endswith('.docx'):
103
  doc = Document(BytesIO(decoded))
 
114
  except Exception as e:
115
  return f"Error processing document: {str(e)}"
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  @app.callback(
118
+ Output('file-list', 'children'),
119
  Input('upload-document', 'contents'),
120
  State('upload-document', 'filename'),
121
+ State('file-list', 'children')
122
  )
123
+ def update_output(list_of_contents, list_of_names, existing_files):
124
+ global uploaded_files
125
+ if list_of_contents is not None:
126
+ new_files = []
127
+ for i, (content, name) in enumerate(zip(list_of_contents, list_of_names)):
128
+ file_content = process_document(content, name)
129
+ uploaded_files[name] = file_content
130
+ new_files.append(html.Div([
131
+ html.Button('×', id={'type': 'remove-file', 'index': name}, style={'marginRight': '5px', 'fontSize': '10px'}),
132
+ html.Span(name)
133
+ ]))
134
+ if existing_files is None:
135
+ existing_files = []
136
+ return existing_files + new_files
137
+ return existing_files
138
 
139
  @app.callback(
140
+ Output('file-list', 'children', allow_duplicate=True),
141
+ Input({'type': 'remove-file', 'index': dash.ALL}, 'n_clicks'),
142
+ State('file-list', 'children'),
143
+ prevent_initial_call=True
144
  )
145
+ def remove_file(n_clicks, existing_files):
146
+ global uploaded_files
147
+ ctx = dash.callback_context
148
+ if not ctx.triggered:
149
+ raise dash.exceptions.PreventUpdate
150
+ removed_file = ctx.triggered[0]['prop_id'].split(',')[0].split(':')[-1].strip('}')
151
+ uploaded_files.pop(removed_file, None)
152
+ return [file for file in existing_files if file['props']['children'][1]['props']['children'] != removed_file]
153
+
154
+ def generate_document(document_type, file_contents):
155
+ prompt = f"""Generate a {document_type} based on the following project artifacts:
156
+ {' '.join(file_contents)}
157
+ Instructions:
158
+ 1. Create the {document_type} as a detailed document.
159
+ 2. Use proper formatting and structure.
160
+ 3. Include all necessary sections and details.
161
+ 4. Start the output immediately with the document content.
162
+ Now, generate the {document_type}:
163
+ """
164
 
165
+ response = model.generate_content(prompt)
166
+ return response.text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
  @app.callback(
169
+ Output('document-preview', 'children'),
170
+ Output('loading-output', 'children'),
171
+ [Input(f'btn-{doc_type.lower().replace(" ", "-")}', 'n_clicks') for doc_type in document_types.keys()],
172
+ prevent_initial_call=True
 
173
  )
174
+ def generate_document_preview(*args):
175
+ global current_document, document_type
176
+ ctx = dash.callback_context
177
+ if not ctx.triggered:
178
+ raise dash.exceptions.PreventUpdate
179
+ button_id = ctx.triggered[0]['prop_id'].split('.')[0]
180
+ document_type = button_id.replace('btn-', '').replace('-', ' ').title()
181
 
182
+ if not uploaded_files:
183
+ return html.Div("Please upload project artifacts before generating a document."), ""
 
184
 
185
+ file_contents = list(uploaded_files.values())
 
 
 
 
 
 
 
 
186
 
187
  try:
188
+ current_document = generate_document(document_type, file_contents)
189
+ return dcc.Markdown(current_document), f"{document_type} generated"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  except Exception as e:
191
+ print(f"Error generating document: {str(e)}")
192
+ return html.Div(f"Error generating document: {str(e)}"), "Error"
193
 
194
  @app.callback(
195
+ Output('chat-output', 'children'),
196
+ Output('document-preview', 'children', allow_duplicate=True),
197
+ Input('btn-send-chat', 'n_clicks'),
198
+ State('chat-input', 'value'),
199
+ prevent_initial_call=True
 
200
  )
201
+ def update_document_via_chat(n_clicks, chat_input):
202
+ global current_document, document_type
203
+ if not chat_input or current_document is None:
204
+ raise dash.exceptions.PreventUpdate
205
+
206
+ prompt = f"""Update the following {document_type} based on this instruction: {chat_input}
207
+ Current document:
208
+ {current_document}
209
+ Instructions:
210
+ 1. Provide the updated document content.
211
+ 2. Maintain proper formatting and structure.
212
+ 3. Incorporate the requested changes seamlessly.
213
+ Now, provide the updated {document_type}:
214
+ """
215
 
216
+ response = model.generate_content(prompt)
217
+ current_document = response.text
 
 
 
 
218
 
219
+ return f"Document updated based on: {chat_input}", dcc.Markdown(current_document)
 
220
 
221
  @app.callback(
222
+ Output("download-document", "data"),
223
+ Input("btn-download", "n_clicks"),
224
+ prevent_initial_call=True
 
 
 
225
  )
226
+ def download_document(n_clicks):
227
+ global current_document, document_type
228
+ if current_document is None:
229
+ raise dash.exceptions.PreventUpdate
 
 
 
 
 
 
230
 
231
+ # Create an in-memory Word document
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  doc = Document()
233
+ doc.add_paragraph(current_document)
234
 
235
+ # Save the document to a BytesIO object
236
+ output = BytesIO()
237
+ doc.save(output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
+ return dcc.send_bytes(output.getvalue(), f"{document_type}.docx")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
  if __name__ == '__main__':
242
  print("Starting the Dash application...")
243
+ app.run(debug=False, host='0.0.0.0', port=7860)
244
  print("Dash application has finished running.")