bluenevus commited on
Commit
9c3653d
·
1 Parent(s): 4e193ad

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +159 -18
app.py CHANGED
@@ -222,6 +222,7 @@ def right_main_static():
222
  app.layout = html.Div([
223
  dcc.Store(id="session-id", storage_type="local"),
224
  dcc.Location(id="url"),
 
225
  html.Div([
226
  html.Div(left_navbar_static(), id='left-navbar', style={"width": "30vw", "height": "100vh", "position": "fixed", "left": 0, "top": 0, "zIndex": 2, "overflowY": "auto"}),
227
  html.Div(right_main_static(), id='right-main', style={"marginLeft": "30vw", "width": "70vw", "overflowY": "auto"})
@@ -234,6 +235,20 @@ def _is_supported_doc(filename):
234
  ext = os.path.splitext(filename)[1].lower()
235
  return ext in [".txt", ".pdf", ".md", ".docx"]
236
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  @app.callback(
238
  Output("session-id", "data"),
239
  Input("url", "href"),
@@ -254,27 +269,80 @@ def assign_session_id(_):
254
  Output("stream-interval", "disabled"),
255
  Output("stream-interval", "n_intervals"),
256
  Output("user-input", "value"),
 
257
  Input("session-id", "data"),
258
  Input("send-btn", "n_clicks"),
259
  Input("file-upload", "contents"),
260
  Input("new-chat-btn", "n_clicks"),
261
  Input("stream-interval", "n_intervals"),
 
262
  State("file-upload", "filename"),
263
  State("user-input", "value"),
 
 
264
  prevent_initial_call=False
265
  )
266
- def main_callback(session_id, send_clicks, file_contents, new_chat_clicks, stream_n, file_names, user_input):
267
  trigger = callback_context.triggered[0]['prop_id'].split('.')[0] if callback_context.triggered else ""
268
- if not session_id:
269
- session_id = get_session_id()
270
  session_lock = get_session_lock(session_id)
271
  with session_lock:
272
  load_session_state(session_id)
273
  state = get_session_state(session_id)
274
  error = ""
275
  start_streaming = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
- # Handle File Upload
 
278
  if trigger == "file-upload" and file_contents and file_names:
279
  uploads = []
280
  if not isinstance(file_contents, list):
@@ -290,10 +358,69 @@ def main_callback(session_id, send_clicks, file_contents, new_chat_clicks, strea
290
  with open(fp, "wb") as f:
291
  f.write(base64.b64decode(data))
292
  uploads.append({"name": fname, "is_img": is_img, "path": fp})
 
 
 
 
 
 
 
 
 
 
 
293
  state["uploads"].extend(uploads)
294
  save_session_state(session_id)
295
  logger.info(f"Session {session_id}: Uploaded files {[u['name'] for u in uploads]}")
296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  # Handle Send
298
  if trigger == "send-btn" and user_input and user_input.strip():
299
  state["messages"].append({"role": "user", "content": user_input})
@@ -377,11 +504,15 @@ def main_callback(session_id, send_clicks, file_contents, new_chat_clicks, strea
377
  upload_cards = [uploaded_file_card(os.path.basename(f["name"]), f["is_img"]) for f in state.get("uploads", [])]
378
  chat_history_items = [
379
  html.Li(
380
- html.Span(
381
  chat["name"],
382
- style={"fontSize": "0.92rem", "fontWeight": "bold" if i == len(state.get("chat_histories", []))-1 else "normal"}
 
 
 
383
  )
384
- ) for i, chat in enumerate(state.get("chat_histories", [])[-6:])
 
385
  ]
386
  return (
387
  upload_cards,
@@ -390,7 +521,8 @@ def main_callback(session_id, send_clicks, file_contents, new_chat_clicks, strea
390
  "",
391
  False,
392
  stream_n+1,
393
- ""
 
394
  )
395
  else:
396
  chat_cards = []
@@ -399,11 +531,15 @@ def main_callback(session_id, send_clicks, file_contents, new_chat_clicks, strea
399
  upload_cards = [uploaded_file_card(os.path.basename(f["name"]), f["is_img"]) for f in state.get("uploads", [])]
400
  chat_history_items = [
401
  html.Li(
402
- html.Span(
403
  chat["name"],
404
- style={"fontSize": "0.92rem", "fontWeight": "bold" if i == len(state.get("chat_histories", []))-1 else "normal"}
 
 
 
405
  )
406
- ) for i, chat in enumerate(state.get("chat_histories", [])[-6:])
 
407
  ]
408
  return (
409
  upload_cards,
@@ -412,7 +548,8 @@ def main_callback(session_id, send_clicks, file_contents, new_chat_clicks, strea
412
  "",
413
  True,
414
  0,
415
- ""
 
416
  )
417
 
418
  # Default: Build Uploads, Chat History and Chat Window
@@ -422,11 +559,15 @@ def main_callback(session_id, send_clicks, file_contents, new_chat_clicks, strea
422
  upload_cards = [uploaded_file_card(os.path.basename(f["name"]), f["is_img"]) for f in uploads]
423
  chat_history_items = [
424
  html.Li(
425
- html.Span(
426
  chat["name"],
427
- style={"fontSize": "0.92rem", "fontWeight": "bold" if i == len(chat_histories)-1 else "normal"}
 
 
 
428
  )
429
- ) for i, chat in enumerate(chat_histories[-6:])
 
430
  ]
431
  chat_cards = []
432
  for msg in chat_history:
@@ -434,11 +575,11 @@ def main_callback(session_id, send_clicks, file_contents, new_chat_clicks, strea
434
  if state.get("streaming", False):
435
  if state.get("stream_buffer", ""):
436
  chat_cards.append(chat_message_card(state["stream_buffer"], is_user=False))
437
- return upload_cards, chat_history_items, chat_cards, error, False, 0, ""
438
  # Always clear input after send
439
  if trigger == "send-btn":
440
- return upload_cards, chat_history_items, chat_cards, error, (not state.get("streaming", False)), 0, ""
441
- return upload_cards, chat_history_items, chat_cards, error, (not state.get("streaming", False)), 0, user_input or ""
442
 
443
  @app_flask.after_request
444
  def set_session_cookie(resp):
 
222
  app.layout = html.Div([
223
  dcc.Store(id="session-id", storage_type="local"),
224
  dcc.Location(id="url"),
225
+ dcc.Store(id="selected-history", data=None),
226
  html.Div([
227
  html.Div(left_navbar_static(), id='left-navbar', style={"width": "30vw", "height": "100vh", "position": "fixed", "left": 0, "top": 0, "zIndex": 2, "overflowY": "auto"}),
228
  html.Div(right_main_static(), id='right-main', style={"marginLeft": "30vw", "width": "70vw", "overflowY": "auto"})
 
235
  ext = os.path.splitext(filename)[1].lower()
236
  return ext in [".txt", ".pdf", ".md", ".docx"]
237
 
238
+ def _extract_text_from_upload(filepath, ext):
239
+ # Only .txt and .md are extracted for now
240
+ if ext in [".txt", ".md"]:
241
+ try:
242
+ with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
243
+ text = f.read()
244
+ return text
245
+ except Exception as e:
246
+ logger.error(f"Error reading {filepath}: {e}")
247
+ return ""
248
+ # For .pdf/.docx, could add extraction with extra dependencies
249
+ else:
250
+ return ""
251
+
252
  @app.callback(
253
  Output("session-id", "data"),
254
  Input("url", "href"),
 
269
  Output("stream-interval", "disabled"),
270
  Output("stream-interval", "n_intervals"),
271
  Output("user-input", "value"),
272
+ Output("selected-history", "data"),
273
  Input("session-id", "data"),
274
  Input("send-btn", "n_clicks"),
275
  Input("file-upload", "contents"),
276
  Input("new-chat-btn", "n_clicks"),
277
  Input("stream-interval", "n_intervals"),
278
+ Input({"type": "chat-history-item", "index": dash.ALL}, "n_clicks"),
279
  State("file-upload", "filename"),
280
  State("user-input", "value"),
281
+ State("selected-history", "data"),
282
+ State("chat-history-list", "children"),
283
  prevent_initial_call=False
284
  )
285
+ def main_callback(session_id, send_clicks, file_contents, new_chat_clicks, stream_n, chat_history_clicks, file_names, user_input, selected_history, chat_history_list_children):
286
  trigger = callback_context.triggered[0]['prop_id'].split('.')[0] if callback_context.triggered else ""
287
+ session_id = session_id or get_session_id()
 
288
  session_lock = get_session_lock(session_id)
289
  with session_lock:
290
  load_session_state(session_id)
291
  state = get_session_state(session_id)
292
  error = ""
293
  start_streaming = False
294
+ chat_histories = state.get("chat_histories", [])
295
+ uploads = state.get("uploads", [])
296
+
297
+ # Handle clickable chat history loading
298
+ history_index_clicked = None
299
+ if trigger == "" and chat_history_clicks:
300
+ for idx, n in enumerate(chat_history_clicks):
301
+ if n:
302
+ history_index_clicked = idx
303
+ break
304
+ elif trigger.startswith("{\"type\":\"chat-history-item\""):
305
+ for idx, n in enumerate(chat_history_clicks):
306
+ if n:
307
+ history_index_clicked = idx
308
+ break
309
+ if history_index_clicked is not None and history_index_clicked < len(chat_histories):
310
+ selected_dialog = chat_histories[history_index_clicked]["dialog"]
311
+ state["messages"] = selected_dialog.copy()
312
+ state["stream_buffer"] = ""
313
+ state["streaming"] = False
314
+ save_session_state(session_id)
315
+ logger.info(f"Session {session_id}: Loaded chat history index {history_index_clicked}")
316
+ # Rebuild downstream outputs with this dialog
317
+ chat_cards = []
318
+ for msg in state["messages"]:
319
+ chat_cards.append(chat_message_card(msg['content'], is_user=(msg['role'] == "user")))
320
+ upload_cards = [uploaded_file_card(os.path.basename(f["name"]), f["is_img"]) for f in state.get("uploads", [])]
321
+ chat_history_items = [
322
+ html.Li(
323
+ dbc.Button(
324
+ chat["name"],
325
+ id={"type": "chat-history-item", "index": i},
326
+ color="tertiary",
327
+ outline=False,
328
+ style={"width": "100%", "textAlign": "left", "fontWeight": "bold" if i == len(chat_histories)-1 else "normal", "fontSize": "0.92rem", "marginBottom": "0.2rem"}
329
+ )
330
+ )
331
+ for i, chat in enumerate(chat_histories[-6:])
332
+ ]
333
+ return (
334
+ upload_cards,
335
+ chat_history_items,
336
+ chat_cards,
337
+ "",
338
+ True,
339
+ 0,
340
+ "",
341
+ history_index_clicked
342
+ )
343
 
344
+ # Handle File Upload -- now, if supported, send to OpenAI as system message
345
+ file_was_uploaded_and_sent = False
346
  if trigger == "file-upload" and file_contents and file_names:
347
  uploads = []
348
  if not isinstance(file_contents, list):
 
358
  with open(fp, "wb") as f:
359
  f.write(base64.b64decode(data))
360
  uploads.append({"name": fname, "is_img": is_img, "path": fp})
361
+ # If document, extract text and send to OpenAI as a message (system or user)
362
+ if _is_supported_doc(n) and not is_img:
363
+ text = _extract_text_from_upload(fp, ext)
364
+ if text.strip():
365
+ doc_intro = f"(User uploaded document '{n}'; content below):\n\n{text[:3800]}"
366
+ # Add as user message and trigger streaming
367
+ state["messages"].append({"role": "user", "content": doc_intro})
368
+ state["streaming"] = True
369
+ state["stream_buffer"] = ""
370
+ file_was_uploaded_and_sent = True
371
+ logger.info(f"Session {session_id}: Uploaded doc '{n}' sent to OpenAI")
372
  state["uploads"].extend(uploads)
373
  save_session_state(session_id)
374
  logger.info(f"Session {session_id}: Uploaded files {[u['name'] for u in uploads]}")
375
 
376
+ # If a supported doc was uploaded, start streaming OpenAI response
377
+ if file_was_uploaded_and_sent:
378
+ def run_stream(session_id, messages):
379
+ try:
380
+ system_prompt = load_system_prompt()
381
+ msg_list = [{"role": "system", "content": system_prompt}]
382
+ for m in messages:
383
+ msg_list.append({"role": m["role"], "content": m["content"]})
384
+ response = openai.ChatCompletion.create(
385
+ model="gpt-3.5-turbo",
386
+ messages=msg_list,
387
+ max_tokens=700,
388
+ temperature=0.2,
389
+ stream=True,
390
+ )
391
+ reply = ""
392
+ for chunk in response:
393
+ delta = chunk["choices"][0]["delta"]
394
+ content = delta.get("content", "")
395
+ if content:
396
+ reply += content
397
+ session_lock = get_session_lock(session_id)
398
+ with session_lock:
399
+ load_session_state(session_id)
400
+ state = get_session_state(session_id)
401
+ state["stream_buffer"] = reply
402
+ save_session_state(session_id)
403
+ session_lock = get_session_lock(session_id)
404
+ with session_lock:
405
+ load_session_state(session_id)
406
+ state = get_session_state(session_id)
407
+ state["messages"].append({"role": "assistant", "content": reply})
408
+ state["stream_buffer"] = ""
409
+ state["streaming"] = False
410
+ save_session_state(session_id)
411
+ logger.info(f"Session {session_id}: Doc Q&A: Assistant: {reply}")
412
+ except Exception as e:
413
+ session_lock = get_session_lock(session_id)
414
+ with session_lock:
415
+ load_session_state(session_id)
416
+ state = get_session_state(session_id)
417
+ state["streaming"] = False
418
+ state["stream_buffer"] = ""
419
+ save_session_state(session_id)
420
+ logger.error(f"Session {session_id}: Streaming error (doc upload): {e}")
421
+ threading.Thread(target=run_stream, args=(session_id, list(state["messages"])), daemon=True).start()
422
+ start_streaming = True
423
+
424
  # Handle Send
425
  if trigger == "send-btn" and user_input and user_input.strip():
426
  state["messages"].append({"role": "user", "content": user_input})
 
504
  upload_cards = [uploaded_file_card(os.path.basename(f["name"]), f["is_img"]) for f in state.get("uploads", [])]
505
  chat_history_items = [
506
  html.Li(
507
+ dbc.Button(
508
  chat["name"],
509
+ id={"type": "chat-history-item", "index": i},
510
+ color="tertiary",
511
+ outline=False,
512
+ style={"width": "100%", "textAlign": "left", "fontWeight": "bold" if i == len(state.get("chat_histories", []) )-1 else "normal", "fontSize": "0.92rem", "marginBottom": "0.2rem"}
513
  )
514
+ )
515
+ for i, chat in enumerate(state.get("chat_histories", [])[-6:])
516
  ]
517
  return (
518
  upload_cards,
 
521
  "",
522
  False,
523
  stream_n+1,
524
+ "",
525
+ selected_history
526
  )
527
  else:
528
  chat_cards = []
 
531
  upload_cards = [uploaded_file_card(os.path.basename(f["name"]), f["is_img"]) for f in state.get("uploads", [])]
532
  chat_history_items = [
533
  html.Li(
534
+ dbc.Button(
535
  chat["name"],
536
+ id={"type": "chat-history-item", "index": i},
537
+ color="tertiary",
538
+ outline=False,
539
+ style={"width": "100%", "textAlign": "left", "fontWeight": "bold" if i == len(state.get("chat_histories", []))-1 else "normal", "fontSize": "0.92rem", "marginBottom": "0.2rem"}
540
  )
541
+ )
542
+ for i, chat in enumerate(state.get("chat_histories", [])[-6:])
543
  ]
544
  return (
545
  upload_cards,
 
548
  "",
549
  True,
550
  0,
551
+ "",
552
+ selected_history
553
  )
554
 
555
  # Default: Build Uploads, Chat History and Chat Window
 
559
  upload_cards = [uploaded_file_card(os.path.basename(f["name"]), f["is_img"]) for f in uploads]
560
  chat_history_items = [
561
  html.Li(
562
+ dbc.Button(
563
  chat["name"],
564
+ id={"type": "chat-history-item", "index": i},
565
+ color="tertiary",
566
+ outline=False,
567
+ style={"width": "100%", "textAlign": "left", "fontWeight": "bold" if i == len(chat_histories)-1 else "normal", "fontSize": "0.92rem", "marginBottom": "0.2rem"}
568
  )
569
+ )
570
+ for i, chat in enumerate(chat_histories[-6:])
571
  ]
572
  chat_cards = []
573
  for msg in chat_history:
 
575
  if state.get("streaming", False):
576
  if state.get("stream_buffer", ""):
577
  chat_cards.append(chat_message_card(state["stream_buffer"], is_user=False))
578
+ return upload_cards, chat_history_items, chat_cards, error, False, 0, "", selected_history
579
  # Always clear input after send
580
  if trigger == "send-btn":
581
+ return upload_cards, chat_history_items, chat_cards, error, (not state.get("streaming", False)), 0, "", selected_history
582
+ return upload_cards, chat_history_items, chat_cards, error, (not state.get("streaming", False)), 0, user_input or "", selected_history
583
 
584
  @app_flask.after_request
585
  def set_session_cookie(resp):