SarahXia0405 commited on
Commit
8bc61c0
·
verified ·
1 Parent(s): 91fab96

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +198 -70
app.py CHANGED
@@ -4,12 +4,8 @@ import base64
4
  from collections import defaultdict
5
  from typing import List, Dict
6
 
7
- import time
8
- import gradio as gr
9
-
10
-
11
  import gradio as gr
12
- from langsmith import Client
13
 
14
  from config import (
15
  DEFAULT_MODEL,
@@ -38,7 +34,7 @@ from syllabus_utils import extract_course_topics_from_file
38
  # ================== Assets ==================
39
  CLARE_LOGO_PATH = "clare_mascot.png"
40
  CLARE_RUN_PATH = "Clare_Run.png"
41
- CLARE_READING_PATH = "Clare_reading.png"
42
 
43
  # ================== Base64 Helper ==================
44
  def image_to_base64(image_path: str) -> str:
@@ -185,7 +181,7 @@ Currently: English & 简体中文.
185
  """
186
  }
187
 
188
- # ================== CSS ==================
189
  CUSTOM_CSS = """
190
  /* --- Main Header --- */
191
  .header-container { padding: 10px 20px; background-color: #ffffff; border-bottom: 2px solid #f3f4f6; margin-bottom: 15px; display: flex; align-items: center; }
@@ -269,7 +265,11 @@ else:
269
  ls_client = Client()
270
  LS_DATASET_NAME = "clare_user_events"
271
 
 
272
  def log_event(data: Dict):
 
 
 
273
  try:
274
  inputs = {
275
  "question": data.get("question"),
@@ -279,11 +279,10 @@ def log_event(data: Dict):
279
  outputs = {
280
  "answer": data.get("answer"),
281
  }
282
- # ✅ 只排除 question / answer,其余都进 metadata
283
  metadata = {
284
  k: v
285
  for k, v in data.items()
286
- if k not in ("question", "answer")
287
  }
288
 
289
  ls_client.create_example(
@@ -296,7 +295,6 @@ def log_event(data: Dict):
296
  print("LangSmith log failed:", e)
297
 
298
 
299
-
300
  # ===== Reference Formatting Helper =====
301
  def format_references(
302
  rag_chunks: List[Dict], max_files: int = 2, max_sections_per_file: int = 3
@@ -330,6 +328,7 @@ def format_references(
330
  return ""
331
  return "\n".join(lines)
332
 
 
333
  def is_academic_query(message: str) -> bool:
334
  if not message:
335
  return False
@@ -375,46 +374,6 @@ def is_academic_query(message: str) -> bool:
375
  return False
376
 
377
  return True
378
- # ======= Chatbot like/dislike handler (per-message, ChatGPT-style) =======
379
- def handle_like(
380
- data: "gr.LikeData", # 或者直接去掉类型标注也可以
381
- history,
382
- user_id_val,
383
- mode_val,
384
- model_name_val,
385
- lang_pref,
386
- ):
387
- """
388
- data.index: 第几条消息被点
389
- data.value: "like" / "dislike"
390
- history: 当前 Chatbot 的 [ [user, assistant], ... ]
391
- """
392
- try:
393
- idx = data.index
394
- value = data.value # "like" or "dislike"
395
- if history is None or idx < 0 or idx >= len(history):
396
- print("[Like] invalid index", idx)
397
- return
398
-
399
- question, answer = history[idx]
400
- student_id = user_id_val or "ANON"
401
-
402
- log_event(
403
- {
404
- "experiment_id": "RESP_AI_W10",
405
- "student_id": student_id,
406
- "event_type": value, # "like" / "dislike"
407
- "timestamp": time.time(),
408
- "question": question,
409
- "answer": answer,
410
- "model_name": model_name_val,
411
- "language": lang_pref,
412
- "learning_mode": mode_val,
413
- }
414
- )
415
- print(f"[Like] event logged: index={idx}, value={value}")
416
- except Exception as e:
417
- print("handle_like error:", e)
418
 
419
 
420
  # ================== Gradio App ==================
@@ -428,6 +387,9 @@ with gr.Blocks(
428
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
429
  rag_chunks_state = gr.State(preloaded_chunks or [])
430
 
 
 
 
431
  user_name_state = gr.State("")
432
  user_id_state = gr.State("")
433
 
@@ -482,7 +444,6 @@ with gr.Blocks(
482
  interactive=False,
483
  )
484
 
485
- # User Guide
486
  with gr.Accordion(
487
  "User Guide", open=True, elem_classes="main-user-guide"
488
  ):
@@ -573,24 +534,31 @@ with gr.Blocks(
573
  avatar_images=(None, CLARE_LOGO_PATH),
574
  show_label=False,
575
  type="tuples",
576
- likeable=True,
577
  )
578
 
579
- # ChatGPT-style per-message like/dislike
580
- chatbot.like(
581
- fn=handle_like,
582
- inputs=[
583
- chatbot, # history
584
- user_id_state,
585
- learning_mode,
586
- model_name,
587
- language_preference,
588
- ],
589
- outputs=[], # 我们这里只写日志,不改 UI
590
- )
591
 
 
 
 
 
 
 
 
 
 
 
 
 
592
 
593
- # 用户输入
594
  user_input = gr.Textbox(
595
  label="Your Input",
596
  placeholder="Please log in on the right before asking Clare anything...",
@@ -600,7 +568,6 @@ with gr.Blocks(
600
  interactive=False,
601
  )
602
 
603
- # Upload / File type / Memory Line
604
  with gr.Row():
605
  with gr.Column(scale=2):
606
  syllabus_file = gr.File(
@@ -697,6 +664,7 @@ with gr.Blocks(
697
  )
698
 
699
  # ================== Login Flow ==================
 
700
  def show_inputs():
701
  return {
702
  login_state_1: gr.update(visible=False),
@@ -731,6 +699,11 @@ with gr.Blocks(
731
  learning_mode: gr.update(interactive=False),
732
  model_name: gr.update(interactive=False),
733
  docs_btn: gr.update(interactive=False),
 
 
 
 
 
734
  }
735
 
736
  info_html = f"""
@@ -761,6 +734,11 @@ with gr.Blocks(
761
  learning_mode: gr.update(interactive=True),
762
  model_name: gr.update(interactive=False),
763
  docs_btn: gr.update(interactive=True),
 
 
 
 
 
764
  }
765
 
766
  login_confirm_btn.click(
@@ -785,6 +763,11 @@ with gr.Blocks(
785
  learning_mode,
786
  model_name,
787
  docs_btn,
 
 
 
 
 
788
  ],
789
  )
790
 
@@ -813,6 +796,11 @@ with gr.Blocks(
813
  language_preference: gr.update(interactive=False),
814
  learning_mode: gr.update(interactive=False),
815
  docs_btn: gr.update(interactive=False),
 
 
 
 
 
816
  }
817
 
818
  logout_btn.click(
@@ -837,10 +825,16 @@ with gr.Blocks(
837
  language_preference,
838
  learning_mode,
839
  docs_btn,
 
 
 
 
 
840
  ],
841
  )
842
 
843
  # ================== Main Logic ==================
 
844
  def update_course_and_rag(file, doc_type_val):
845
  local_topics = preloaded_topics or []
846
  local_chunks = preloaded_chunks or []
@@ -909,7 +903,7 @@ with gr.Blocks(
909
  weaknesses or [],
910
  cognitive_state or {"confusion": 0, "mastery": 0},
911
  )
912
- return "", new_history, weaknesses, cognitive_state, new_status
913
 
914
  resolved_lang = detect_language(message or "", lang_pref)
915
 
@@ -919,7 +913,7 @@ with gr.Blocks(
919
  weaknesses or [],
920
  cognitive_state or {"confusion": 0, "mastery": 0},
921
  )
922
- return "", chat_history, weaknesses, cognitive_state, new_status
923
 
924
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
925
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
@@ -981,7 +975,7 @@ with gr.Blocks(
981
 
982
  new_status = render_session_status(mode_val, weaknesses, cognitive_state)
983
 
984
- return "", new_history, weaknesses, cognitive_state, new_status
985
 
986
  user_input.submit(
987
  respond,
@@ -1004,6 +998,8 @@ with gr.Blocks(
1004
  weakness_state,
1005
  cognitive_state_state,
1006
  session_status,
 
 
1007
  ],
1008
  )
1009
 
@@ -1123,6 +1119,136 @@ with gr.Blocks(
1123
  [chatbot, weakness_state, cognitive_state_state, session_status],
1124
  )
1125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1126
  # ===== Export / Summary =====
1127
  export_btn.click(
1128
  lambda h, c, m, w, cog: export_conversation(h, c, m, w, cog),
@@ -1149,7 +1275,7 @@ with gr.Blocks(
1149
  def clear_all():
1150
  empty_state = {"confusion": 0, "mastery": 0}
1151
  default_status = render_session_status("Concept Explainer", [], empty_state)
1152
- return [], [], empty_state, [], "", default_status
1153
 
1154
  clear_btn.click(
1155
  clear_all,
@@ -1161,6 +1287,8 @@ with gr.Blocks(
1161
  rag_chunks_state,
1162
  result_display,
1163
  session_status,
 
 
1164
  ],
1165
  queue=False,
1166
  )
 
4
  from collections import defaultdict
5
  from typing import List, Dict
6
 
 
 
 
 
7
  import gradio as gr
8
+ from langsmith import Client # LangSmith 客户端
9
 
10
  from config import (
11
  DEFAULT_MODEL,
 
34
  # ================== Assets ==================
35
  CLARE_LOGO_PATH = "clare_mascot.png"
36
  CLARE_RUN_PATH = "Clare_Run.png"
37
+ CLARE_READING_PATH = "Clare_reading.png" # 确保存在
38
 
39
  # ================== Base64 Helper ==================
40
  def image_to_base64(image_path: str) -> str:
 
181
  """
182
  }
183
 
184
+ # ================== CSS 样式表 ==================
185
  CUSTOM_CSS = """
186
  /* --- Main Header --- */
187
  .header-container { padding: 10px 20px; background-color: #ffffff; border-bottom: 2px solid #f3f4f6; margin-bottom: 15px; display: flex; align-items: center; }
 
265
  ls_client = Client()
266
  LS_DATASET_NAME = "clare_user_events"
267
 
268
+
269
  def log_event(data: Dict):
270
+ """
271
+ 把日志写入 LangSmith Dataset (clare_user_events)
272
+ """
273
  try:
274
  inputs = {
275
  "question": data.get("question"),
 
279
  outputs = {
280
  "answer": data.get("answer"),
281
  }
 
282
  metadata = {
283
  k: v
284
  for k, v in data.items()
285
+ if k not in ("question", "answer", "event_type")
286
  }
287
 
288
  ls_client.create_example(
 
295
  print("LangSmith log failed:", e)
296
 
297
 
 
298
  # ===== Reference Formatting Helper =====
299
  def format_references(
300
  rag_chunks: List[Dict], max_files: int = 2, max_sections_per_file: int = 3
 
328
  return ""
329
  return "\n".join(lines)
330
 
331
+
332
  def is_academic_query(message: str) -> bool:
333
  if not message:
334
  return False
 
374
  return False
375
 
376
  return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
 
379
  # ================== Gradio App ==================
 
387
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
388
  rag_chunks_state = gr.State(preloaded_chunks or [])
389
 
390
+ last_question_state = gr.State("")
391
+ last_answer_state = gr.State("")
392
+
393
  user_name_state = gr.State("")
394
  user_id_state = gr.State("")
395
 
 
444
  interactive=False,
445
  )
446
 
 
447
  with gr.Accordion(
448
  "User Guide", open=True, elem_classes="main-user-guide"
449
  ):
 
534
  avatar_images=(None, CLARE_LOGO_PATH),
535
  show_label=False,
536
  type="tuples",
 
537
  )
538
 
539
+ # Rating bar (last answer)
540
+ gr.Markdown("#### Rate Clare’s last answer")
541
+ with gr.Row():
542
+ thumb_up_btn = gr.Button(
543
+ "👍 Helpful", size="sm", interactive=False
544
+ )
545
+ thumb_down_btn = gr.Button(
546
+ "👎 Not helpful", size="sm", interactive=False
547
+ )
 
 
 
548
 
549
+ feedback_toggle_btn = gr.Button(
550
+ "Give detailed feedback", size="sm", variant="secondary", interactive=False
551
+ )
552
+ feedback_text = gr.Textbox(
553
+ label="What worked well or what was wrong?",
554
+ placeholder="Optional: describe what you liked / what was confusing or incorrect.",
555
+ lines=3,
556
+ visible=False,
557
+ )
558
+ feedback_submit_btn = gr.Button(
559
+ "Submit Feedback", size="sm", variant="primary", visible=False, interactive=False
560
+ )
561
 
 
562
  user_input = gr.Textbox(
563
  label="Your Input",
564
  placeholder="Please log in on the right before asking Clare anything...",
 
568
  interactive=False,
569
  )
570
 
 
571
  with gr.Row():
572
  with gr.Column(scale=2):
573
  syllabus_file = gr.File(
 
664
  )
665
 
666
  # ================== Login Flow ==================
667
+
668
  def show_inputs():
669
  return {
670
  login_state_1: gr.update(visible=False),
 
699
  learning_mode: gr.update(interactive=False),
700
  model_name: gr.update(interactive=False),
701
  docs_btn: gr.update(interactive=False),
702
+ thumb_up_btn: gr.update(interactive=False),
703
+ thumb_down_btn: gr.update(interactive=False),
704
+ feedback_toggle_btn: gr.update(interactive=False),
705
+ feedback_text: gr.update(visible=False, value=""),
706
+ feedback_submit_btn: gr.update(interactive=False, visible=False),
707
  }
708
 
709
  info_html = f"""
 
734
  learning_mode: gr.update(interactive=True),
735
  model_name: gr.update(interactive=False),
736
  docs_btn: gr.update(interactive=True),
737
+ thumb_up_btn: gr.update(interactive=True),
738
+ thumb_down_btn: gr.update(interactive=True),
739
+ feedback_toggle_btn: gr.update(interactive=True),
740
+ feedback_text: gr.update(visible=False, value=""),
741
+ feedback_submit_btn: gr.update(interactive=True, visible=False),
742
  }
743
 
744
  login_confirm_btn.click(
 
763
  learning_mode,
764
  model_name,
765
  docs_btn,
766
+ thumb_up_btn,
767
+ thumb_down_btn,
768
+ feedback_toggle_btn,
769
+ feedback_text,
770
+ feedback_submit_btn,
771
  ],
772
  )
773
 
 
796
  language_preference: gr.update(interactive=False),
797
  learning_mode: gr.update(interactive=False),
798
  docs_btn: gr.update(interactive=False),
799
+ thumb_up_btn: gr.update(interactive=False),
800
+ thumb_down_btn: gr.update(interactive=False),
801
+ feedback_toggle_btn: gr.update(interactive=False),
802
+ feedback_text: gr.update(visible=False, value=""),
803
+ feedback_submit_btn: gr.update(interactive=False, visible=False),
804
  }
805
 
806
  logout_btn.click(
 
825
  language_preference,
826
  learning_mode,
827
  docs_btn,
828
+ thumb_up_btn,
829
+ thumb_down_btn,
830
+ feedback_toggle_btn,
831
+ feedback_text,
832
+ feedback_submit_btn,
833
  ],
834
  )
835
 
836
  # ================== Main Logic ==================
837
+
838
  def update_course_and_rag(file, doc_type_val):
839
  local_topics = preloaded_topics or []
840
  local_chunks = preloaded_chunks or []
 
903
  weaknesses or [],
904
  cognitive_state or {"confusion": 0, "mastery": 0},
905
  )
906
+ return "", new_history, weaknesses, cognitive_state, new_status, "", ""
907
 
908
  resolved_lang = detect_language(message or "", lang_pref)
909
 
 
913
  weaknesses or [],
914
  cognitive_state or {"confusion": 0, "mastery": 0},
915
  )
916
+ return "", chat_history, weaknesses, cognitive_state, new_status, "", ""
917
 
918
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
919
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
 
975
 
976
  new_status = render_session_status(mode_val, weaknesses, cognitive_state)
977
 
978
+ return "", new_history, weaknesses, cognitive_state, new_status, message, answer
979
 
980
  user_input.submit(
981
  respond,
 
998
  weakness_state,
999
  cognitive_state_state,
1000
  session_status,
1001
+ last_question_state,
1002
+ last_answer_state,
1003
  ],
1004
  )
1005
 
 
1119
  [chatbot, weakness_state, cognitive_state_state, session_status],
1120
  )
1121
 
1122
+ # ===== Feedback Handlers (thumb + detailed) =====
1123
+ def show_feedback_box():
1124
+ return {
1125
+ feedback_text: gr.update(visible=True),
1126
+ feedback_submit_btn: gr.update(visible=True),
1127
+ }
1128
+
1129
+ feedback_toggle_btn.click(
1130
+ show_feedback_box,
1131
+ None,
1132
+ [feedback_text, feedback_submit_btn],
1133
+ )
1134
+
1135
+ def send_thumb_up(last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref):
1136
+ if not last_q and not last_a:
1137
+ print("No last QA to log for thumbs_up.")
1138
+ return
1139
+ try:
1140
+ log_event(
1141
+ {
1142
+ "experiment_id": "RESP_AI_W10",
1143
+ "student_id": user_id_val or "ANON",
1144
+ "event_type": "like",
1145
+ "timestamp": time.time(),
1146
+ "question": last_q,
1147
+ "answer": last_a,
1148
+ "model_name": model_name_val,
1149
+ "language": lang_pref,
1150
+ "learning_mode": mode_val,
1151
+ }
1152
+ )
1153
+ print("[Feedback] thumbs_up logged to LangSmith.")
1154
+ except Exception as e:
1155
+ print("thumb_up log error:", e)
1156
+
1157
+ def send_thumb_down(last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref):
1158
+ if not last_q and not last_a:
1159
+ print("No last QA to log for thumbs_down.")
1160
+ return
1161
+ try:
1162
+ log_event(
1163
+ {
1164
+ "experiment_id": "RESP_AI_W10",
1165
+ "student_id": user_id_val or "ANON",
1166
+ "event_type": "dislike",
1167
+ "timestamp": time.time(),
1168
+ "question": last_q,
1169
+ "answer": last_a,
1170
+ "model_name": model_name_val,
1171
+ "language": lang_pref,
1172
+ "learning_mode": mode_val,
1173
+ }
1174
+ )
1175
+ print("[Feedback] thumbs_down logged to LangSmith.")
1176
+ except Exception as e:
1177
+ print("thumb_down log error:", e)
1178
+
1179
+ thumb_up_btn.click(
1180
+ send_thumb_up,
1181
+ [
1182
+ last_question_state,
1183
+ last_answer_state,
1184
+ user_id_state,
1185
+ learning_mode,
1186
+ model_name,
1187
+ language_preference,
1188
+ ],
1189
+ [],
1190
+ )
1191
+
1192
+ thumb_down_btn.click(
1193
+ send_thumb_down,
1194
+ [
1195
+ last_question_state,
1196
+ last_answer_state,
1197
+ user_id_state,
1198
+ learning_mode,
1199
+ model_name,
1200
+ language_preference,
1201
+ ],
1202
+ [],
1203
+ )
1204
+
1205
+ def submit_detailed_feedback(
1206
+ text, last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref
1207
+ ):
1208
+ if not text or not text.strip():
1209
+ return gr.update(
1210
+ value="",
1211
+ placeholder="Please enter some feedback before submitting.",
1212
+ )
1213
+
1214
+ try:
1215
+ log_event(
1216
+ {
1217
+ "experiment_id": "RESP_AI_W10",
1218
+ "student_id": user_id_val or "ANON",
1219
+ "event_type": "detailed_feedback",
1220
+ "timestamp": time.time(),
1221
+ "question": last_q,
1222
+ "answer": last_a,
1223
+ "feedback_text": text.strip(),
1224
+ "model_name": model_name_val,
1225
+ "language": lang_pref,
1226
+ "learning_mode": mode_val,
1227
+ }
1228
+ )
1229
+ print("[Feedback] detailed_feedback logged to LangSmith.")
1230
+ except Exception as e:
1231
+ print("detailed_feedback log error:", e)
1232
+
1233
+ return gr.update(
1234
+ value="",
1235
+ placeholder="Thanks! Your feedback has been recorded.",
1236
+ )
1237
+
1238
+ feedback_submit_btn.click(
1239
+ submit_detailed_feedback,
1240
+ [
1241
+ feedback_text,
1242
+ last_question_state,
1243
+ last_answer_state,
1244
+ user_id_state,
1245
+ learning_mode,
1246
+ model_name,
1247
+ language_preference,
1248
+ ],
1249
+ [feedback_text],
1250
+ )
1251
+
1252
  # ===== Export / Summary =====
1253
  export_btn.click(
1254
  lambda h, c, m, w, cog: export_conversation(h, c, m, w, cog),
 
1275
  def clear_all():
1276
  empty_state = {"confusion": 0, "mastery": 0}
1277
  default_status = render_session_status("Concept Explainer", [], empty_state)
1278
+ return [], [], empty_state, [], "", default_status, "", ""
1279
 
1280
  clear_btn.click(
1281
  clear_all,
 
1287
  rag_chunks_state,
1288
  result_display,
1289
  session_status,
1290
+ last_question_state,
1291
+ last_answer_state,
1292
  ],
1293
  queue=False,
1294
  )