bluenevus commited on
Commit
c7006a6
·
1 Parent(s): 20f1559

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +161 -53
app.py CHANGED
@@ -11,6 +11,8 @@ import dash_bootstrap_components as dbc
11
  from dash import html, dcc, Input, Output, State, dash_table, callback_context
12
 
13
  logging.basicConfig(level=logging.INFO)
 
 
14
  ANTHROPIC_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
15
  import anthropic
16
  anthropic_client = anthropic.Anthropic(api_key=ANTHROPIC_KEY)
@@ -31,36 +33,52 @@ document_types = {
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
@@ -104,25 +122,47 @@ def make_textarea(btn_id, 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
  tab_cards = {tab["id"]: make_tab(tab["id"], tab["label"]) for tab in main_tabs}
120
 
121
  nav_items = [
122
  dbc.NavLink(tab["label"], href="#", id=f"nav-{tab['id']}", active=(tab["id"] == "shred")) for tab in main_tabs
123
  ]
124
 
125
- # Render all tab cards, only one visible at a time
126
  def all_tabs_div():
127
  return html.Div(
128
  [
@@ -173,6 +213,45 @@ def display_tab(*nav_clicks):
173
  styles.append({"display": "none"})
174
  return styles
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  @app.callback(
177
  [Output(f'{tab_id}-output', 'children') for tab_id in tab_cards] +
178
  [Output(f"{tab_id}-download", "data") for tab_id in tab_cards],
@@ -181,14 +260,17 @@ def display_tab(*nav_clicks):
181
  [State(f'{tab_id}-upload', 'contents') for tab_id in tab_cards] +
182
  [State(f'{tab_id}-upload', 'filename') for tab_id in tab_cards] +
183
  [State(f'{tab_id}-instructions', 'value') for tab_id in tab_cards] +
184
- [State(f'{tab_id}-output', 'children') for tab_id in tab_cards]
 
185
  )
186
  def handle_all_tabs(*args):
187
  n = len(tab_cards)
188
  outputs = [None] * (n * 2)
189
  ctx = callback_context
190
- if not ctx.triggered: return outputs
 
191
  trig = ctx.triggered[0]['prop_id']
 
192
  for idx, tab_id in enumerate(tab_cards):
193
  gen_btn = f"{tab_id}-btn.n_clicks"
194
  dl_btn = f"{tab_id}-download-btn.n_clicks"
@@ -198,31 +280,57 @@ def handle_all_tabs(*args):
198
  filename_idx = idx + n
199
  instr_idx = idx + 2 * n
200
  prev_output_idx = idx + 3 * n
 
201
 
202
  if trig == gen_btn:
203
- upload = args[upload_idx]
204
- filename = args[filename_idx]
205
- instr = args[instr_idx] or ""
206
- doc_type = tab_id.replace('-', ' ').title().replace(' ', '')
207
- doc_type = next((k for k in document_types if k.lower().replace(' ', '') == tab_id.replace('-', '')), tab_id.title())
208
- if upload and filename:
209
- doc = process_document(upload, filename)
210
- else:
211
- doc = ""
212
- if doc or tab_id == "virtual-board":
213
- content, df = generate_content(doc, doc_type)
214
- if not df.empty:
215
- outputs[out_idx] = dash_table.DataTable(
216
- data=df.to_dict('records'),
217
- columns=[{'name': i, 'id': i} for i in df.columns],
218
- style_table={'overflowX': 'auto'},
219
- style_cell={'textAlign': 'left', 'padding': '5px'},
220
- style_header={'fontWeight': 'bold'}
221
- )
 
 
 
222
  else:
223
- outputs[out_idx] = dcc.Markdown(content)
224
  else:
225
- outputs[out_idx] = "Please upload a document to begin."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  elif trig == dl_btn:
227
  prev_output = args[prev_output_idx]
228
  if prev_output and hasattr(prev_output, 'props') and 'data' in prev_output.props:
 
11
  from dash import html, dcc, Input, Output, State, dash_table, callback_context
12
 
13
  logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger("microhealth-pws")
15
+
16
  ANTHROPIC_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
17
  import anthropic
18
  anthropic_client = anthropic.Anthropic(api_key=ANTHROPIC_KEY)
 
33
  }
34
 
35
  def process_document(contents, filename):
36
+ try:
37
+ content_type, content_string = contents.split(',')
38
+ decoded = base64.b64decode(content_string)
39
+ if filename.lower().endswith('.docx'):
40
+ doc = Document(BytesIO(decoded))
41
+ text = "\n".join([p.text for p in doc.paragraphs])
42
+ return text
43
+ elif filename.lower().endswith('.pdf'):
44
+ pdf = PdfReader(BytesIO(decoded))
45
+ text = "".join(page.extract_text() or "" for page in pdf.pages)
46
+ return text
47
+ else:
48
+ return f"Unsupported file format: {filename}"
49
+ except Exception as e:
50
+ logger.error(f"Error processing document {filename}: {e}")
51
+ return f"Failed to process document: {e}"
52
 
53
  def call_claude(prompt, max_tokens=2048):
54
+ try:
55
+ res = anthropic_client.messages.create(
56
+ model=CLAUDE3_SONNET_MODEL,
57
+ max_tokens=max_tokens,
58
+ temperature=0.1,
59
+ system="You are a world class proposal consultant and proposal manager.",
60
+ messages=[{"role": "user", "content": prompt}]
61
+ )
62
+ logger.info("Anthropic API call successful.")
63
+ return res.content[0].text if hasattr(res, "content") else str(res)
64
+ except Exception as e:
65
+ logger.error(f"Anthropic API error: {e}")
66
+ return f"Anthropic API error: {e}"
67
 
68
  def spreadsheet_to_df(text):
69
  lines = [l.strip() for l in text.splitlines() if '|' in l]
70
+ if not lines:
71
+ return pd.DataFrame()
72
  header = lines[0].strip('|').split('|')
73
  data = [l.strip('|').split('|') for l in lines[1:]]
74
  return pd.DataFrame(data, columns=[h.strip() for h in header])
75
 
76
+ def generate_content(document, doc_type, instructions=""):
77
+ prompt = f"{document_types[doc_type]}\n\n"
78
+ if instructions:
79
+ prompt += f"Additional Instructions:\n{instructions}\n\n"
80
+ prompt += f"Document:\n{document}\n\nOutput only one spreadsheet table, use | as column separator."
81
+ logger.info(f"Generating content for {doc_type} with prompt length {len(prompt)}")
82
  response = call_claude(prompt, max_tokens=4096)
83
  df = spreadsheet_to_df(response)
84
  return response, df
 
122
  style={'height': '80px', 'marginBottom': '10px', 'width': '100%', 'whiteSpace': 'pre-wrap', 'overflowWrap': 'break-word'}
123
  )
124
 
125
+ def make_shred_doc_preview():
126
  return dbc.Card(
127
  dbc.CardBody([
128
+ html.Div(id="shred-upload-preview", style={"whiteSpace": "pre-wrap", "overflowWrap": "break-word"}),
129
+ dbc.Button("Delete Document", id="shred-delete-btn", className="mt-2 btn-tertiary", n_clicks=0)
130
+ ]), className="mb-2", id="shred-doc-preview-card", style={"display": "none"}
 
 
 
 
131
  )
132
 
133
+ def make_tab(tab_id, label):
134
+ if tab_id == "shred":
135
+ # Insert doc preview card for shred
136
+ return dbc.Card(
137
+ dbc.CardBody([
138
+ make_textarea(tab_id, f"Instructions for {label} (optional)"),
139
+ make_upload(tab_id),
140
+ make_shred_doc_preview(),
141
+ dbc.Button(f"Generate {label}", id=f'{tab_id}-btn', className="mt-2 btn-primary", n_clicks=0),
142
+ dcc.Loading(html.Div(id=f'{tab_id}-output'), id="loading", type="default", parent_style={'justifyContent': 'center'}),
143
+ dbc.Button(f"Download {label} Report", id=f"{tab_id}-download-btn", className="mt-2 btn-secondary", n_clicks=0),
144
+ dcc.Download(id=f"{tab_id}-download"),
145
+ dcc.Store(id="shred-upload-store")
146
+ ]), className="mb-4"
147
+ )
148
+ else:
149
+ return dbc.Card(
150
+ dbc.CardBody([
151
+ make_textarea(tab_id, f"Instructions for {label} (optional)"),
152
+ make_upload(tab_id),
153
+ dbc.Button(f"Generate {label}", id=f'{tab_id}-btn', className="mt-2 btn-primary", n_clicks=0),
154
+ dcc.Loading(html.Div(id=f'{tab_id}-output'), type="default", parent_style={'justifyContent': 'center'}),
155
+ dbc.Button(f"Download {label} Report", id=f"{tab_id}-download-btn", className="mt-2 btn-secondary", n_clicks=0),
156
+ dcc.Download(id=f"{tab_id}-download")
157
+ ]), className="mb-4"
158
+ )
159
+
160
  tab_cards = {tab["id"]: make_tab(tab["id"], tab["label"]) for tab in main_tabs}
161
 
162
  nav_items = [
163
  dbc.NavLink(tab["label"], href="#", id=f"nav-{tab['id']}", active=(tab["id"] == "shred")) for tab in main_tabs
164
  ]
165
 
 
166
  def all_tabs_div():
167
  return html.Div(
168
  [
 
213
  styles.append({"display": "none"})
214
  return styles
215
 
216
+ @app.callback(
217
+ [
218
+ Output("shred-upload-store", "data"),
219
+ Output("shred-upload-preview", "children"),
220
+ Output("shred-doc-preview-card", "style"),
221
+ ],
222
+ [
223
+ Input("shred-upload", "contents"),
224
+ Input("shred-delete-btn", "n_clicks")
225
+ ],
226
+ [
227
+ State("shred-upload", "filename"),
228
+ State("shred-upload-store", "data"),
229
+ ],
230
+ prevent_initial_call=True
231
+ )
232
+ def update_shred_upload(contents, delete_clicks, filename, stored_data):
233
+ triggered = callback_context.triggered
234
+ logger.info("Shred upload callback triggered.")
235
+ if not triggered:
236
+ return dash.no_update, dash.no_update, dash.no_update
237
+ trig_id = triggered[0]["prop_id"].split(".")[0]
238
+ if trig_id == "shred-upload":
239
+ if contents and filename:
240
+ logger.info(f"Document uploaded in Shred: {filename}")
241
+ text = process_document(contents, filename)
242
+ preview = html.Div([
243
+ html.B(f"Uploaded: {filename}"),
244
+ html.Br(),
245
+ html.Div(text[:2000] + ("..." if len(text) > 2000 else ""), style={"whiteSpace": "pre-wrap", "overflowWrap": "break-word", "fontSize": "small"})
246
+ ])
247
+ return {"contents": contents, "filename": filename, "preview": text[:2000]}, preview, {"display": "block"}
248
+ else:
249
+ return None, "", {"display": "none"}
250
+ elif trig_id == "shred-delete-btn":
251
+ logger.info("Shred document deleted by user.")
252
+ return None, "", {"display": "none"}
253
+ return dash.no_update, dash.no_update, dash.no_update
254
+
255
  @app.callback(
256
  [Output(f'{tab_id}-output', 'children') for tab_id in tab_cards] +
257
  [Output(f"{tab_id}-download", "data") for tab_id in tab_cards],
 
260
  [State(f'{tab_id}-upload', 'contents') for tab_id in tab_cards] +
261
  [State(f'{tab_id}-upload', 'filename') for tab_id in tab_cards] +
262
  [State(f'{tab_id}-instructions', 'value') for tab_id in tab_cards] +
263
+ [State(f'{tab_id}-output', 'children') for tab_id in tab_cards] +
264
+ [State("shred-upload-store", "data")]
265
  )
266
  def handle_all_tabs(*args):
267
  n = len(tab_cards)
268
  outputs = [None] * (n * 2)
269
  ctx = callback_context
270
+ if not ctx.triggered:
271
+ return outputs
272
  trig = ctx.triggered[0]['prop_id']
273
+ logger.info(f"Main callback triggered by {trig}")
274
  for idx, tab_id in enumerate(tab_cards):
275
  gen_btn = f"{tab_id}-btn.n_clicks"
276
  dl_btn = f"{tab_id}-download-btn.n_clicks"
 
280
  filename_idx = idx + n
281
  instr_idx = idx + 2 * n
282
  prev_output_idx = idx + 3 * n
283
+ shred_upload_store_idx = 4 * n
284
 
285
  if trig == gen_btn:
286
+ logger.info(f"Generate button pressed for {tab_id}")
287
+ if tab_id == "shred":
288
+ # Use stored doc for Shred
289
+ shred_data = args[shred_upload_store_idx]
290
+ instr = args[instr_idx] or ""
291
+ if shred_data and "contents" in shred_data and "filename" in shred_data:
292
+ doc = process_document(shred_data["contents"], shred_data["filename"])
293
+ logger.info(f"Shred document will be sent to Anthropic with instructions: {instr}")
294
+ content, df = generate_content(doc, "Shred", instr)
295
+ if not df.empty:
296
+ outputs[out_idx] = dash_table.DataTable(
297
+ data=df.to_dict('records'),
298
+ columns=[{'name': i, 'id': i} for i in df.columns],
299
+ style_table={'overflowX': 'auto'},
300
+ style_cell={'textAlign': 'left', 'padding': '5px'},
301
+ style_header={'fontWeight': 'bold'}
302
+ )
303
+ else:
304
+ outputs[out_idx] = html.Div([
305
+ html.B("Anthropic Response Preview:"),
306
+ dcc.Markdown(content)
307
+ ])
308
  else:
309
+ outputs[out_idx] = "Please upload a document to begin."
310
  else:
311
+ upload = args[upload_idx]
312
+ filename = args[filename_idx]
313
+ instr = args[instr_idx] or ""
314
+ doc_type = tab_id.replace('-', ' ').title().replace(' ', '')
315
+ doc_type = next((k for k in document_types if k.lower().replace(' ', '') == tab_id.replace('-', '')), tab_id.title())
316
+ if upload and filename:
317
+ doc = process_document(upload, filename)
318
+ else:
319
+ doc = ""
320
+ if doc or tab_id == "virtual-board":
321
+ content, df = generate_content(doc, doc_type, instr)
322
+ if not df.empty:
323
+ outputs[out_idx] = dash_table.DataTable(
324
+ data=df.to_dict('records'),
325
+ columns=[{'name': i, 'id': i} for i in df.columns],
326
+ style_table={'overflowX': 'auto'},
327
+ style_cell={'textAlign': 'left', 'padding': '5px'},
328
+ style_header={'fontWeight': 'bold'}
329
+ )
330
+ else:
331
+ outputs[out_idx] = dcc.Markdown(content)
332
+ else:
333
+ outputs[out_idx] = "Please upload a document to begin."
334
  elif trig == dl_btn:
335
  prev_output = args[prev_output_idx]
336
  if prev_output and hasattr(prev_output, 'props') and 'data' in prev_output.props: