bluenevus commited on
Commit
9341c86
·
1 Parent(s): 9f4cd2a

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +186 -94
app.py CHANGED
@@ -6,7 +6,7 @@ from docx import Document
6
  from io import BytesIO, StringIO
7
  import dash
8
  import dash_bootstrap_components as dbc
9
- from dash import html, dcc, Input, Output, State, callback_context
10
  from docx.shared import Pt
11
  from docx.enum.style import WD_STYLE_TYPE
12
  from PyPDF2 import PdfReader
@@ -14,23 +14,19 @@ import openai
14
  import logging
15
  import threading
16
 
17
- # Logging configuration
18
  logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
19
 
20
- # Initialize Dash app
21
  app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
22
 
23
- # Configure OpenAI
24
  openai.api_key = os.environ.get("OPENAI_API_KEY", "")
25
 
26
- # Global variables
27
  uploaded_files = {}
28
  current_document = None
29
  document_type = None
30
  shredded_document = None
31
  pink_review_document = None
 
32
 
33
- # Document types and their descriptions
34
  document_types = {
35
  "Shred": "Generate a 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",
36
  "Pink": "Create a Pink Team document based on the PWS outline. Your goal is to be compliant and compelling.",
@@ -43,44 +39,34 @@ document_types = {
43
  "LOE": "Generate a Level of Effort (LOE) breakdown as a spreadsheet"
44
  }
45
 
46
- app.layout = dbc.Container([
47
- dbc.Row([
48
- dbc.Col([
49
- html.H4("Proposal Documents", className="mt-3 mb-4"),
50
  html.Div([
51
  html.Div(className="blinking-dot", style={'margin':'0 auto','width':'16px','height':'16px'}),
52
  ], style={'textAlign':'center', 'marginBottom':'10px'}),
53
- dcc.Upload(
54
- id='upload-document',
55
- children=html.Div([
56
- 'Drag and Drop or ',
57
- html.A('Select Files')
58
- ]),
59
- style={
60
- 'width': '100%',
61
- 'height': '60px',
62
- 'lineHeight': '60px',
63
- 'borderWidth': '1px',
64
- 'borderStyle': 'dashed',
65
- 'borderRadius': '5px',
66
- 'textAlign': 'center',
67
- 'margin': '10px 0'
68
- },
69
- multiple=True
70
  ),
71
- html.Div(id='file-list'),
 
 
72
  html.Hr(),
73
- html.Div([
74
- dbc.Button(
75
- doc_type,
76
- id=f'btn-{doc_type.lower().replace("_", "-")}',
77
- color="link",
78
- className="mb-2 w-100 text-left custom-button",
79
- style={'overflow': 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap'}
80
- ) for doc_type in document_types.keys()
81
- ])
82
- ], width=3),
83
- dbc.Col([
 
84
  html.Div([
85
  html.Div(className="blinking-dot", style={'margin':'0 auto','width':'16px','height':'16px'}),
86
  ], style={'textAlign':'center', 'marginBottom':'10px'}),
@@ -94,10 +80,11 @@ app.layout = dbc.Container([
94
  dbc.Button("Download Document", id="btn-download", color="success", className="mt-3"),
95
  dcc.Download(id="download-document"),
96
  html.Hr(),
97
- html.Div(id='pink-review-upload', style={'display': 'none'}, children=[
 
98
  dcc.Upload(
99
- id='upload-pink-review',
100
- children=html.Div(['Drag and Drop or ', html.A('Select Pink Review File')]),
101
  style={
102
  'width': '100%',
103
  'height': '60px',
@@ -110,8 +97,19 @@ app.layout = dbc.Container([
110
  },
111
  multiple=False
112
  ),
113
- html.Div(id='pink-review-file-name')
114
- ]),
 
 
 
 
 
 
 
 
 
 
 
115
  dcc.Loading(
116
  id="chat-loading",
117
  type="dot",
@@ -121,6 +119,47 @@ app.layout = dbc.Container([
121
  html.Div(id="chat-output")
122
  ]
123
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  ], width=9)
125
  ])
126
  ], fluid=True)
@@ -175,7 +214,7 @@ def update_output(list_of_contents, list_of_names, existing_files):
175
  @app.callback(
176
  Output('file-list', 'children', allow_duplicate=True),
177
  Output('status-bar', 'children', allow_duplicate=True),
178
- Input({'type': 'remove-file', 'index': dash.ALL}, 'n_clicks'),
179
  State('file-list', 'children'),
180
  prevent_initial_call=True
181
  )
@@ -190,6 +229,23 @@ def remove_file(n_clicks, existing_files):
190
  logging.info(f"Removed file: {removed_file}")
191
  return [file for file in existing_files if file['props']['children'][1]['props']['children'] != removed_file], "Document removed. Please upload a document and click 'Shred' to begin."
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  def generate_document(document_type, file_contents):
194
  prompt = f"""Generate a {document_type} based on the following project artifacts:
195
  {' '.join(file_contents)}
@@ -243,70 +299,106 @@ Now, generate the {document_type}:
243
  Output('document-preview', 'children'),
244
  Output('loading-output', 'children'),
245
  Output('status-bar', 'children', allow_duplicate=True),
246
- Output('pink-review-upload', 'style'),
247
- [Input(f'btn-{doc_type.lower().replace("_", "-")}', 'n_clicks') for doc_type in document_types.keys()],
248
- State('pink-review-file-name', 'children'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  prevent_initial_call=True
250
  )
251
- def generate_document_preview(*args):
252
  global current_document, document_type, shredded_document, pink_review_document
253
- ctx = dash.callback_context
254
  if not ctx.triggered:
255
  raise dash.exceptions.PreventUpdate
256
- button_id = ctx.triggered[0]['prop_id'].split('.')[0]
257
- document_type = button_id.replace('btn-', '').replace('-', '_').title()
258
- pink_review_file = args[-1]
259
-
260
- if not uploaded_files and document_type != "Shred":
261
- return html.Div("Please upload and shred a document first."), "", "Please upload and shred a document first.", {'display': 'none'}
262
 
263
- if document_type == "Shred":
264
- if not uploaded_files:
265
- return html.Div("Please upload a document before shredding."), "", "Please upload a document before shredding.", {'display': 'none'}
266
- file_contents = list(uploaded_files.values())
267
- try:
268
- shredded_document = generate_document(document_type, file_contents)
269
- return dcc.Markdown(shredded_document), f"{document_type} generated", "Document shredded. You can now proceed with other operations.", {'display': 'none'}
270
- except Exception as e:
271
- logging.error(f"Error generating document: {str(e)}")
272
- return html.Div(f"Error generating document: {str(e)}"), "Error", "An error occurred while shredding the document.", {'display': 'none'}
273
 
274
  if shredded_document is None:
275
- return html.Div("Please shred a document first."), "", "Please shred a document first.", {'display': 'none'}
276
 
277
- if document_type == "Pink Review":
278
- return html.Div("Please upload a Pink Team document or use the generated one."), "", "Please upload a Pink Team document or use the generated one.", {'display': 'block'}
279
 
280
- if document_type in ["Red", "Red Review"] and pink_review_document is None:
281
- return html.Div("Please complete Pink Review first."), "", "Please complete Pink Review first.", {'display': 'none'}
282
-
283
- try:
284
- if document_type == "Pink Review" and pink_review_file:
285
- current_document = generate_document(document_type, [pink_review_file, shredded_document])
286
- elif document_type in ["Red", "Red Review"]:
287
- current_document = generate_document(document_type, [pink_review_document, shredded_document])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  else:
289
- current_document = generate_document(document_type, [shredded_document])
290
 
291
- if document_type == "Pink Review":
 
 
292
  pink_review_document = current_document
293
-
294
- logging.info(f"{document_type} document generated successfully.")
295
- return dcc.Markdown(current_document), f"{document_type} generated", f"{document_type} document generated successfully.", {'display': 'none'}
 
 
 
296
  except Exception as e:
297
  logging.error(f"Error generating document: {str(e)}")
298
- return html.Div(f"Error generating document: {str(e)}"), "Error", "An error occurred while generating the document.", {'display': 'none'}
299
-
300
- @app.callback(
301
- Output('pink-review-file-name', 'children'),
302
- Input('upload-pink-review', 'contents'),
303
- State('upload-pink-review', 'filename')
304
- )
305
- def update_pink_review_filename(contents, filename):
306
- if contents is not None:
307
- logging.info(f"Pink Review file uploaded: {filename}")
308
- return filename
309
- return ""
310
 
311
  @app.callback(
312
  Output('chat-output', 'children'),
 
6
  from io import BytesIO, StringIO
7
  import dash
8
  import dash_bootstrap_components as dbc
9
+ from dash import html, dcc, Input, Output, State, callback_context, MATCH, ALL
10
  from docx.shared import Pt
11
  from docx.enum.style import WD_STYLE_TYPE
12
  from PyPDF2 import PdfReader
 
14
  import logging
15
  import threading
16
 
 
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
18
 
 
19
  app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
20
 
 
21
  openai.api_key = os.environ.get("OPENAI_API_KEY", "")
22
 
 
23
  uploaded_files = {}
24
  current_document = None
25
  document_type = None
26
  shredded_document = None
27
  pink_review_document = None
28
+ uploaded_doc_contents = {}
29
 
 
30
  document_types = {
31
  "Shred": "Generate a 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",
32
  "Pink": "Create a Pink Team document based on the PWS outline. Your goal is to be compliant and compelling.",
 
39
  "LOE": "Generate a Level of Effort (LOE) breakdown as a spreadsheet"
40
  }
41
 
42
+ def get_right_col_content(selected_type):
43
+ if selected_type == "Shred":
44
+ return [
 
45
  html.Div([
46
  html.Div(className="blinking-dot", style={'margin':'0 auto','width':'16px','height':'16px'}),
47
  ], style={'textAlign':'center', 'marginBottom':'10px'}),
48
+ html.Div(id='status-bar', className="alert alert-info", style={'marginBottom': '20px'}),
49
+ dcc.Loading(
50
+ id="loading-indicator",
51
+ type="dot",
52
+ children=[html.Div(id="loading-output")]
 
 
 
 
 
 
 
 
 
 
 
 
53
  ),
54
+ html.Div(id='document-preview', className="border p-3 mb-3"),
55
+ dbc.Button("Download Document", id="btn-download", color="success", className="mt-3"),
56
+ dcc.Download(id="download-document"),
57
  html.Hr(),
58
+ dcc.Loading(
59
+ id="chat-loading",
60
+ type="dot",
61
+ children=[
62
+ dbc.Input(id="chat-input", type="text", placeholder="Chat with AI to update document...", className="mb-2", style={'whiteSpace':'pre-wrap'}),
63
+ dbc.Button("Send", id="btn-send-chat", color="primary", className="mb-3"),
64
+ html.Div(id="chat-output")
65
+ ]
66
+ )
67
+ ]
68
+ else:
69
+ return [
70
  html.Div([
71
  html.Div(className="blinking-dot", style={'margin':'0 auto','width':'16px','height':'16px'}),
72
  ], style={'textAlign':'center', 'marginBottom':'10px'}),
 
80
  dbc.Button("Download Document", id="btn-download", color="success", className="mt-3"),
81
  dcc.Download(id="download-document"),
82
  html.Hr(),
83
+ html.Div([
84
+ html.Label(f"Upload {selected_type} Document"),
85
  dcc.Upload(
86
+ id={'type': 'upload-doc-type', 'index': selected_type},
87
+ children=html.Div(['Drag and Drop or ', html.A('Select File')]),
88
  style={
89
  'width': '100%',
90
  'height': '60px',
 
97
  },
98
  multiple=False
99
  ),
100
+ html.Div(id={'type': 'uploaded-doc-name', 'index': selected_type}),
101
+ dbc.RadioItems(
102
+ id={'type': 'radio-doc-source', 'index': selected_type},
103
+ options=[
104
+ {'label': 'Loaded Document', 'value': 'loaded'},
105
+ {'label': 'Uploaded Document', 'value': 'uploaded'}
106
+ ],
107
+ value='loaded',
108
+ inline=True,
109
+ className="mb-2"
110
+ ),
111
+ dbc.Button("Generate Document", id={'type': 'btn-generate-doc', 'index': selected_type}, color="primary", className="mb-3"),
112
+ ], id={'type': 'doc-type-controls', 'index': selected_type}),
113
  dcc.Loading(
114
  id="chat-loading",
115
  type="dot",
 
119
  html.Div(id="chat-output")
120
  ]
121
  )
122
+ ]
123
+
124
+ app.layout = dbc.Container([
125
+ dbc.Row([
126
+ dbc.Col([
127
+ html.H4("Proposal Documents", className="mt-3 mb-4"),
128
+ html.Div([
129
+ html.Div(className="blinking-dot", style={'margin':'0 auto','width':'16px','height':'16px'}),
130
+ ], style={'textAlign':'center', 'marginBottom':'10px'}),
131
+ dcc.Upload(
132
+ id='upload-document',
133
+ children=html.Div([
134
+ 'Drag and Drop or ',
135
+ html.A('Select Files')
136
+ ]),
137
+ style={
138
+ 'width': '100%',
139
+ 'height': '60px',
140
+ 'lineHeight': '60px',
141
+ 'borderWidth': '1px',
142
+ 'borderStyle': 'dashed',
143
+ 'borderRadius': '5px',
144
+ 'textAlign': 'center',
145
+ 'margin': '10px 0'
146
+ },
147
+ multiple=True
148
+ ),
149
+ html.Div(id='file-list'),
150
+ html.Hr(),
151
+ html.Div([
152
+ dbc.Button(
153
+ doc_type,
154
+ id={'type': 'btn-doc-type', 'index': doc_type},
155
+ color="link",
156
+ className="mb-2 w-100 text-left custom-button",
157
+ style={'overflow': 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap'}
158
+ ) for doc_type in document_types.keys()
159
+ ])
160
+ ], width=3),
161
+ dbc.Col([
162
+ html.Div(id='right-col-content')
163
  ], width=9)
164
  ])
165
  ], fluid=True)
 
214
  @app.callback(
215
  Output('file-list', 'children', allow_duplicate=True),
216
  Output('status-bar', 'children', allow_duplicate=True),
217
+ Input({'type': 'remove-file', 'index': ALL}, 'n_clicks'),
218
  State('file-list', 'children'),
219
  prevent_initial_call=True
220
  )
 
229
  logging.info(f"Removed file: {removed_file}")
230
  return [file for file in existing_files if file['props']['children'][1]['props']['children'] != removed_file], "Document removed. Please upload a document and click 'Shred' to begin."
231
 
232
+ @app.callback(
233
+ Output('right-col-content', 'children'),
234
+ [Input({'type': 'btn-doc-type', 'index': ALL}, 'n_clicks')],
235
+ [State({'type': 'btn-doc-type', 'index': ALL}, 'id')]
236
+ )
237
+ def update_right_col(n_clicks_list, btn_ids):
238
+ triggered = callback_context.triggered
239
+ if not triggered or all(x is None for x in n_clicks_list):
240
+ selected_type = "Shred"
241
+ else:
242
+ idx = [i for i, x in enumerate(n_clicks_list) if x]
243
+ if idx:
244
+ selected_type = btn_ids[idx[-1]]['index']
245
+ else:
246
+ selected_type = "Shred"
247
+ return get_right_col_content(selected_type)
248
+
249
  def generate_document(document_type, file_contents):
250
  prompt = f"""Generate a {document_type} based on the following project artifacts:
251
  {' '.join(file_contents)}
 
299
  Output('document-preview', 'children'),
300
  Output('loading-output', 'children'),
301
  Output('status-bar', 'children', allow_duplicate=True),
302
+ Input({'type': 'btn-doc-type', 'index': 'Shred'}, 'n_clicks'),
303
+ prevent_initial_call=True
304
+ )
305
+ def generate_shred_doc(n_clicks):
306
+ global current_document, document_type, shredded_document
307
+ if not uploaded_files:
308
+ return html.Div("Please upload a document before shredding."), "", "Please upload a document before shredding."
309
+ file_contents = list(uploaded_files.values())
310
+ try:
311
+ shredded_document = generate_document("Shred", file_contents)
312
+ current_document = shredded_document
313
+ return dcc.Markdown(shredded_document), "Shred generated", "Document shredded. You can now proceed with other operations."
314
+ except Exception as e:
315
+ logging.error(f"Error generating document: {str(e)}")
316
+ return html.Div(f"Error generating document: {str(e)}"), "Error", "An error occurred while shredding the document."
317
+
318
+ @app.callback(
319
+ Output({'type': 'uploaded-doc-name', 'index': MATCH}, 'children'),
320
+ Output({'type': 'upload-doc-type', 'index': MATCH}, 'contents'),
321
+ Input({'type': 'upload-doc-type', 'index': MATCH}, 'contents'),
322
+ State({'type': 'upload-doc-type', 'index': MATCH}, 'filename'),
323
+ State({'type': 'upload-doc-type', 'index': MATCH}, 'id')
324
+ )
325
+ def update_uploaded_doc_name(contents, filename, id_dict):
326
+ if contents is not None:
327
+ uploaded_doc_contents[id_dict['index']] = (contents, filename)
328
+ logging.info(f"{id_dict['index']} file uploaded: {filename}")
329
+ return filename, contents
330
+ return "", None
331
+
332
+ @app.callback(
333
+ Output('document-preview', 'children', allow_duplicate=True),
334
+ Output('loading-output', 'children', allow_duplicate=True),
335
+ Output('status-bar', 'children', allow_duplicate=True),
336
+ Input({'type': 'btn-generate-doc', 'index': ALL}, 'n_clicks'),
337
+ State({'type': 'btn-generate-doc', 'index': ALL}, 'id'),
338
+ State({'type': 'radio-doc-source', 'index': ALL}, 'value'),
339
+ State({'type': 'upload-doc-type', 'index': ALL}, 'contents'),
340
+ State({'type': 'upload-doc-type', 'index': ALL}, 'filename'),
341
  prevent_initial_call=True
342
  )
343
+ def generate_other_doc(n_clicks_list, btn_ids, radio_values, upload_contents, upload_filenames):
344
  global current_document, document_type, shredded_document, pink_review_document
345
+ ctx = callback_context
346
  if not ctx.triggered:
347
  raise dash.exceptions.PreventUpdate
348
+ idx = [i for i, x in enumerate(n_clicks_list) if x]
349
+ if not idx:
350
+ raise dash.exceptions.PreventUpdate
351
+ idx = idx[-1]
352
+ doc_type = btn_ids[idx]['index']
353
+ document_type = doc_type
354
 
355
+ if doc_type == "Shred":
356
+ raise dash.exceptions.PreventUpdate
 
 
 
 
 
 
 
 
357
 
358
  if shredded_document is None:
359
+ return html.Div("Please shred a document first."), "", "Please shred a document first."
360
 
361
+ source = radio_values[idx] if radio_values and len(radio_values) > idx else 'loaded'
362
+ doc_content = None
363
 
364
+ if source == 'uploaded':
365
+ if upload_contents and len(upload_contents) > idx and upload_contents[idx] and upload_filenames and len(upload_filenames) > idx and upload_filenames[idx]:
366
+ doc_content = process_document(upload_contents[idx], upload_filenames[idx])
367
+ else:
368
+ return html.Div("Please upload a document to use as source."), "", "Please upload a document to use as source."
369
+ else:
370
+ if doc_type == "Pink":
371
+ doc_content = shredded_document
372
+ elif doc_type == "Pink Review":
373
+ doc_content = pink_review_document if pink_review_document else ""
374
+ elif doc_type == "Red":
375
+ doc_content = pink_review_document if pink_review_document else ""
376
+ elif doc_type == "Red Review":
377
+ doc_content = pink_review_document if pink_review_document else ""
378
+ elif doc_type == "Gold":
379
+ doc_content = shredded_document
380
+ elif doc_type == "Gold Review":
381
+ doc_content = shredded_document
382
+ elif doc_type == "Virtual Board":
383
+ doc_content = shredded_document
384
+ elif doc_type == "LOE":
385
+ doc_content = shredded_document
386
  else:
387
+ doc_content = shredded_document
388
 
389
+ try:
390
+ if doc_type == "Pink Review":
391
+ current_document = generate_document(doc_type, [doc_content, shredded_document])
392
  pink_review_document = current_document
393
+ elif doc_type in ["Red", "Red Review"]:
394
+ current_document = generate_document(doc_type, [doc_content, shredded_document])
395
+ else:
396
+ current_document = generate_document(doc_type, [doc_content])
397
+ logging.info(f"{doc_type} document generated successfully.")
398
+ return dcc.Markdown(current_document), f"{doc_type} generated", f"{doc_type} document generated successfully."
399
  except Exception as e:
400
  logging.error(f"Error generating document: {str(e)}")
401
+ return html.Div(f"Error generating document: {str(e)}"), "Error", "An error occurred while generating the document."
 
 
 
 
 
 
 
 
 
 
 
402
 
403
  @app.callback(
404
  Output('chat-output', 'children'),