SarahXia0405 commited on
Commit
68499d2
·
verified ·
1 Parent(s): 96c3617

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +128 -97
app.py CHANGED
@@ -34,7 +34,7 @@ from syllabus_utils import extract_course_topics_from_file
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,12 +181,10 @@ Currently: English & 简体中文.
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; }
188
 
189
- /* --- Sidebar Login Panel --- */
190
  .login-panel {
191
  background-color: #e5e7eb;
192
  padding: 15px;
@@ -213,7 +211,6 @@ CUSTOM_CSS = """
213
  font-weight: bold !important;
214
  }
215
 
216
- /* User Guide */
217
  .main-user-guide { border: none !important; background: transparent !important; box-shadow: none !important; }
218
  .main-user-guide > .label-wrap { border: none !important; background: transparent !important; padding: 10px 0 !important; }
219
  .main-user-guide > .label-wrap span { font-size: 1.3rem !important; font-weight: 800 !important; color: #111827 !important; }
@@ -223,24 +220,16 @@ CUSTOM_CSS = """
223
  .clean-accordion > .label-wrap span { font-size: 0.9rem !important; font-weight: 500 !important; color: #374151 !important; }
224
  .clean-accordion > .label-wrap:hover { background-color: #f9fafb !important; }
225
 
226
- /* Action Buttons */
227
  .action-btn { font-weight: bold !important; font-size: 0.9rem !important; position: relative; overflow: visible !important; }
228
- .action-btn:hover::before { content: "See User Guide for details"; position: absolute; bottom: 110%; left: 50%; transform: translateX(-50%); background-color: #333; color: #fff; padding: 5px 10px; border-radius: 5px; font-size: 12px; white-space: nowrap; z-index: 1000; pointer-events: none; opacity: 0; animation: fadeIn 0.2s forwards; }
229
- .action-btn:hover::after { content: ""; position: absolute; bottom: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: #333 transparent transparent transparent; opacity: 0; animation: fadeIn 0.2s forwards; }
230
 
231
- /* Tooltips & Memory Line */
232
  .html-tooltip { border-bottom: 1px dashed #999; cursor: help; position: relative; }
233
- .html-tooltip:hover::before { content: attr(data-tooltip); position: absolute; bottom: 120%; left: 0; background-color: #333; color: #fff; padding: 5px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap; z-index: 100; pointer-events: none; }
234
  .memory-line-box { border: 1px solid #e5e7eb; padding: 12px; border-radius: 8px; background-color: #f9fafb; height: 100%; display: flex; flex-direction: column; justify-content: space-between; }
235
 
236
- /* Results Box Style */
237
  .result-box { border: 1px solid #e5e7eb; background: #ffffff; padding: 10px; border-radius: 8px; height: 100%; }
238
  .result-box .prose { font-size: 0.9rem; }
239
-
240
- @keyframes fadeIn { to { opacity: 1; } }
241
  """
242
 
243
- # ========== Preload Module 10 PDF ==========
244
  MODULE10_PATH = "module10_responsible_ai.pdf"
245
  MODULE10_DOC_TYPE = "Literature Review / Paper"
246
 
@@ -267,10 +256,6 @@ LS_DATASET_NAME = "clare_user_events"
267
 
268
 
269
  def log_event(data: Dict):
270
- """
271
- 写入 LangSmith dataset:clare_user_events
272
- 每个事件是一条 example(inputs / outputs / metadata)
273
- """
274
  try:
275
  inputs = {
276
  "event_type": data.get("event_type"),
@@ -296,7 +281,7 @@ def log_event(data: Dict):
296
  print("LangSmith log failed:", e)
297
 
298
 
299
- # ===== Reference Formatting Helper =====
300
  def format_references(
301
  rag_chunks: List[Dict], max_files: int = 2, max_sections_per_file: int = 3
302
  ) -> str:
@@ -382,17 +367,14 @@ with gr.Blocks(
382
  title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
383
  ) as demo:
384
 
385
- # 全局状态
386
  course_outline_state = gr.State(preloaded_topics or DEFAULT_COURSE_TOPICS)
387
  weakness_state = gr.State([])
388
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
389
  rag_chunks_state = gr.State(preloaded_chunks or [])
390
 
391
- # 最近一次回答(用于 thumbs / 详细反馈)
392
  last_question_state = gr.State("")
393
  last_answer_state = gr.State("")
394
 
395
- # 用户登录状态
396
  user_name_state = gr.State("")
397
  user_id_state = gr.State("")
398
 
@@ -447,7 +429,6 @@ with gr.Blocks(
447
  interactive=False,
448
  )
449
 
450
- # User Guide
451
  with gr.Accordion(
452
  "User Guide", open=True, elem_classes="main-user-guide"
453
  ):
@@ -509,18 +490,12 @@ with gr.Blocks(
509
  gr.Markdown(USER_GUIDE_SECTIONS["faq"])
510
 
511
  gr.Markdown("---")
512
- gr.Button(
513
- "System Settings",
514
- size="sm",
515
- variant="secondary",
516
- interactive=False,
517
- )
518
 
519
  gr.HTML(
520
  """
521
  <div style="font-size: 11px; color: #9ca3af; margin-top: 15px; text-align: left;">
522
- © 2025 Made by <a href="https://www.linkedin.com/in/qinghua-xia-479199252/"
523
- target="_blank" style="color: #6b7280; text-decoration: underline;">Sarah Xia</a>
524
  </div>
525
  """
526
  )
@@ -543,11 +518,10 @@ with gr.Blocks(
543
  height=450,
544
  avatar_images=(None, CLARE_LOGO_PATH),
545
  show_label=False,
546
- bubble_full_width=False, # 只是 warning,不影响
547
  type="tuples",
548
  )
549
 
550
- # === Feedback on last answer ===
551
  gr.Markdown("#### Rate Clare’s last answer")
552
  with gr.Row():
553
  thumb_up_btn = gr.Button(
@@ -565,9 +539,7 @@ with gr.Blocks(
565
  )
566
  feedback_text = gr.Textbox(
567
  label="What worked well or what was wrong?",
568
- placeholder=(
569
- "Optional: describe what you liked / what was confusing or incorrect."
570
- ),
571
  lines=3,
572
  visible=False,
573
  )
@@ -579,28 +551,22 @@ with gr.Blocks(
579
  interactive=False,
580
  )
581
 
582
- # 用户输入
583
  user_input = gr.Textbox(
584
  label="Your Input",
585
- placeholder=(
586
- "Please log in on the right before asking Clare anything..."
587
- ),
588
  show_label=False,
589
  container=True,
590
  autofocus=False,
591
  interactive=False,
592
  )
593
 
594
- # Upload / File type / Memory Line
595
  with gr.Row():
596
  with gr.Column(scale=2):
597
  syllabus_file = gr.File(
598
  file_types=[".docx", ".pdf", ".pptx"],
599
  file_count="single",
600
  height=160,
601
- label=(
602
- "Upload additional Module 10 file (.docx/.pdf/.pptx) — optional"
603
- ),
604
  interactive=False,
605
  )
606
  with gr.Column(scale=1):
@@ -673,22 +639,13 @@ with gr.Blocks(
673
 
674
  gr.Markdown("### Actions")
675
  export_btn = gr.Button(
676
- "Export Conversation",
677
- size="sm",
678
- elem_classes="action-btn",
679
- interactive=False,
680
  )
681
  quiz_btn = gr.Button(
682
- "Let's Try (Micro-Quiz)",
683
- size="sm",
684
- elem_classes="action-btn",
685
- interactive=False,
686
  )
687
  summary_btn = gr.Button(
688
- "Summarization",
689
- size="sm",
690
- elem_classes="action-btn",
691
- interactive=False,
692
  )
693
 
694
  gr.Markdown("### Results")
@@ -699,7 +656,6 @@ with gr.Blocks(
699
  )
700
 
701
  # ================== Login Flow ==================
702
-
703
  def show_inputs():
704
  return {
705
  login_state_1: gr.update(visible=False),
@@ -718,10 +674,7 @@ with gr.Blocks(
718
  login_state_2: gr.update(),
719
  login_state_3: gr.update(),
720
  student_info_html: gr.update(
721
- value=(
722
- "<p style='color:red; font-size:12px;'>Please enter both "
723
- "Name and Email/ID to start.</p>"
724
- )
725
  ),
726
  user_name_state: gr.update(),
727
  user_id_state: gr.update(),
@@ -759,9 +712,7 @@ with gr.Blocks(
759
  user_id_state: id_val,
760
  user_input: gr.update(
761
  interactive=True,
762
- placeholder=(
763
- "Ask about Module 10 concepts, Responsible AI, or let Clare test you..."
764
- ),
765
  ),
766
  clear_btn: gr.update(interactive=True),
767
  export_btn: gr.update(interactive=True),
@@ -774,8 +725,12 @@ with gr.Blocks(
774
  learning_mode: gr.update(interactive=True),
775
  model_name: gr.update(interactive=False),
776
  docs_btn: gr.update(interactive=True),
777
- thumb_up_btn: gr.update(interactive=True),
778
- thumb_down_btn: gr.update(interactive=True),
 
 
 
 
779
  feedback_toggle_btn: gr.update(interactive=True),
780
  feedback_text: gr.update(visible=False, value=""),
781
  feedback_submit_btn: gr.update(interactive=True, visible=False),
@@ -824,9 +779,7 @@ with gr.Blocks(
824
  user_input: gr.update(
825
  value="",
826
  interactive=False,
827
- placeholder=(
828
- "Please log in on the right before asking Clare anything..."
829
- ),
830
  ),
831
  clear_btn: gr.update(interactive=False),
832
  export_btn: gr.update(interactive=False),
@@ -876,11 +829,7 @@ with gr.Blocks(
876
  )
877
 
878
  # ================== Main Logic ==================
879
-
880
  def update_course_and_rag(file, doc_type_val):
881
- """
882
- 上传的文件会在 Module10 预加载的基础上「追加」,而不是替换。
883
- """
884
  local_topics = preloaded_topics or []
885
  local_chunks = preloaded_chunks or []
886
 
@@ -917,7 +866,7 @@ with gr.Blocks(
917
 
918
  def show_loaded_docs(doc_type_val):
919
  gr.Info(
920
- "For this experiment, Clare always includes the pre-loaded Module 10 reading.\n"
921
  f"Additional uploaded {doc_type_val} files will be used as supplementary context.",
922
  title="Loaded Documents",
923
  )
@@ -937,7 +886,14 @@ with gr.Blocks(
937
  doc_type_val,
938
  user_id_val,
939
  ):
940
- # 没登录就提示
 
 
 
 
 
 
 
941
  if not user_id_val:
942
  out_msg = (
943
  "🔒 Please log in with your Student Name and Email/ID on the right "
@@ -949,7 +905,17 @@ with gr.Blocks(
949
  weaknesses or [],
950
  cognitive_state or {"confusion": 0, "mastery": 0},
951
  )
952
- return "", new_history, weaknesses, cognitive_state, new_status, "", ""
 
 
 
 
 
 
 
 
 
 
953
 
954
  resolved_lang = detect_language(message or "", lang_pref)
955
 
@@ -959,12 +925,28 @@ with gr.Blocks(
959
  weaknesses or [],
960
  cognitive_state or {"confusion": 0, "mastery": 0},
961
  )
962
- return "", chat_history, weaknesses, cognitive_state, new_status, "", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
963
 
964
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
965
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
966
 
967
- # RAG:只对学术问题启用
968
  if is_academic_query(message):
969
  rag_context_text, rag_used_chunks = retrieve_relevant_chunks(
970
  message, rag_chunks or []
@@ -988,7 +970,6 @@ with gr.Blocks(
988
  end_ts = time.time()
989
  latency_ms = (end_ts - start_ts) * 1000.0
990
 
991
- # 只在学术型问题上附 References
992
  if is_academic_query(message) and rag_used_chunks:
993
  ref_text = format_references(rag_used_chunks)
994
  else:
@@ -1001,7 +982,6 @@ with gr.Blocks(
1001
  new_history[-1] = [last_user, last_assistant]
1002
  answer = last_assistant
1003
 
1004
- # LangSmith 日志
1005
  student_id = user_id_val or "ANON"
1006
  experiment_id = "RESP_AI_W10"
1007
  try:
@@ -1024,8 +1004,25 @@ with gr.Blocks(
1024
 
1025
  new_status = render_session_status(mode_val, weaknesses, cognitive_state)
1026
 
1027
- # 将当前这一轮的 Q/A 存入 state,供 thumbs / 文字反馈使用
1028
- return "", new_history, weaknesses, cognitive_state, new_status, message, answer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1029
 
1030
  user_input.submit(
1031
  respond,
@@ -1050,10 +1047,12 @@ with gr.Blocks(
1050
  session_status,
1051
  last_question_state,
1052
  last_answer_state,
 
 
1053
  ],
1054
  )
1055
 
1056
- # ===== Micro-Quiz: 直接在主 Chatbot 里进行 =====
1057
  def start_micro_quiz(
1058
  chat_history,
1059
  course_outline,
@@ -1091,7 +1090,8 @@ with gr.Blocks(
1091
  "• Do NOT start a content question until I have answered 1 or 2.\n\n"
1092
  "Step 2 – After I choose the style:\n"
1093
  "• If I choose 1 (multiple-choice):\n"
1094
- " - Ask ONE multiple-choice question at a time, based on Module 10 concepts.\n"
 
1095
  " - Provide 3–4 options (A, B, C, D) and make only one option clearly correct.\n"
1096
  "• If I choose 2 (short-answer):\n"
1097
  " - Ask ONE short-answer question at a time, also based on Module 10 concepts.\n"
@@ -1184,7 +1184,11 @@ with gr.Blocks(
1184
  def send_thumb_up(last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref):
1185
  if not last_q and not last_a:
1186
  print("No last QA to log for thumbs_up.")
1187
- return
 
 
 
 
1188
  try:
1189
  log_event(
1190
  {
@@ -1203,12 +1207,23 @@ with gr.Blocks(
1203
  except Exception as e:
1204
  print("thumb_up log error:", e)
1205
 
 
 
 
 
 
 
 
 
1206
  def send_thumb_down(
1207
  last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref
1208
  ):
1209
  if not last_q and not last_a:
1210
  print("No last QA to log for thumbs_down.")
1211
- return
 
 
 
1212
  try:
1213
  log_event(
1214
  {
@@ -1227,6 +1242,15 @@ with gr.Blocks(
1227
  except Exception as e:
1228
  print("thumb_down log error:", e)
1229
 
 
 
 
 
 
 
 
 
 
1230
  thumb_up_btn.click(
1231
  send_thumb_up,
1232
  [
@@ -1237,7 +1261,7 @@ with gr.Blocks(
1237
  model_name,
1238
  language_preference,
1239
  ],
1240
- [],
1241
  )
1242
 
1243
  thumb_down_btn.click(
@@ -1250,7 +1274,7 @@ with gr.Blocks(
1250
  model_name,
1251
  language_preference,
1252
  ],
1253
- [],
1254
  )
1255
 
1256
  def submit_detailed_feedback(
@@ -1303,13 +1327,7 @@ with gr.Blocks(
1303
  # ===== Export / Summary =====
1304
  export_btn.click(
1305
  lambda h, c, m, w, cog: export_conversation(h, c, m, w, cog),
1306
- [
1307
- chatbot,
1308
- course_outline_state,
1309
- learning_mode,
1310
- weakness_state,
1311
- cognitive_state_state,
1312
- ],
1313
  [result_display],
1314
  )
1315
 
@@ -1332,7 +1350,18 @@ with gr.Blocks(
1332
  def clear_all():
1333
  empty_state = {"confusion": 0, "mastery": 0}
1334
  default_status = render_session_status("Concept Explainer", [], empty_state)
1335
- return [], [], empty_state, [], "", default_status, "", ""
 
 
 
 
 
 
 
 
 
 
 
1336
 
1337
  clear_btn.click(
1338
  clear_all,
@@ -1346,13 +1375,15 @@ with gr.Blocks(
1346
  session_status,
1347
  last_question_state,
1348
  last_answer_state,
 
 
1349
  ],
1350
  queue=False,
1351
  )
1352
 
1353
  if __name__ == "__main__":
1354
  demo.launch(
1355
- share=True, # HF Space 环境需要 share=True 才能访问
1356
  server_name="0.0.0.0",
1357
  server_port=7860,
1358
  )
 
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
  .header-container { padding: 10px 20px; background-color: #ffffff; border-bottom: 2px solid #f3f4f6; margin-bottom: 15px; display: flex; align-items: center; }
187
 
 
188
  .login-panel {
189
  background-color: #e5e7eb;
190
  padding: 15px;
 
211
  font-weight: bold !important;
212
  }
213
 
 
214
  .main-user-guide { border: none !important; background: transparent !important; box-shadow: none !important; }
215
  .main-user-guide > .label-wrap { border: none !important; background: transparent !important; padding: 10px 0 !important; }
216
  .main-user-guide > .label-wrap span { font-size: 1.3rem !important; font-weight: 800 !important; color: #111827 !important; }
 
220
  .clean-accordion > .label-wrap span { font-size: 0.9rem !important; font-weight: 500 !important; color: #374151 !important; }
221
  .clean-accordion > .label-wrap:hover { background-color: #f9fafb !important; }
222
 
 
223
  .action-btn { font-weight: bold !important; font-size: 0.9rem !important; position: relative; overflow: visible !important; }
 
 
224
 
 
225
  .html-tooltip { border-bottom: 1px dashed #999; cursor: help; position: relative; }
 
226
  .memory-line-box { border: 1px solid #e5e7eb; padding: 12px; border-radius: 8px; background-color: #f9fafb; height: 100%; display: flex; flex-direction: column; justify-content: space-between; }
227
 
 
228
  .result-box { border: 1px solid #e5e7eb; background: #ffffff; padding: 10px; border-radius: 8px; height: 100%; }
229
  .result-box .prose { font-size: 0.9rem; }
 
 
230
  """
231
 
232
+ # ========== Preload Module 10 ==========
233
  MODULE10_PATH = "module10_responsible_ai.pdf"
234
  MODULE10_DOC_TYPE = "Literature Review / Paper"
235
 
 
256
 
257
 
258
  def log_event(data: Dict):
 
 
 
 
259
  try:
260
  inputs = {
261
  "event_type": data.get("event_type"),
 
281
  print("LangSmith log failed:", e)
282
 
283
 
284
+ # ===== Reference helper =====
285
  def format_references(
286
  rag_chunks: List[Dict], max_files: int = 2, max_sections_per_file: int = 3
287
  ) -> str:
 
367
  title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
368
  ) as demo:
369
 
 
370
  course_outline_state = gr.State(preloaded_topics or DEFAULT_COURSE_TOPICS)
371
  weakness_state = gr.State([])
372
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
373
  rag_chunks_state = gr.State(preloaded_chunks or [])
374
 
 
375
  last_question_state = gr.State("")
376
  last_answer_state = gr.State("")
377
 
 
378
  user_name_state = gr.State("")
379
  user_id_state = gr.State("")
380
 
 
429
  interactive=False,
430
  )
431
 
 
432
  with gr.Accordion(
433
  "User Guide", open=True, elem_classes="main-user-guide"
434
  ):
 
490
  gr.Markdown(USER_GUIDE_SECTIONS["faq"])
491
 
492
  gr.Markdown("---")
493
+ gr.Button("System Settings", size="sm", variant="secondary", interactive=False)
 
 
 
 
 
494
 
495
  gr.HTML(
496
  """
497
  <div style="font-size: 11px; color: #9ca3af; margin-top: 15px; text-align: left;">
498
+ © 2025 Made by <a href="https://www.linkedin.com/in/qinghua-xia-479199252/" target="_blank" style="color: #6b7280; text-decoration: underline;">Sarah Xia</a>
 
499
  </div>
500
  """
501
  )
 
518
  height=450,
519
  avatar_images=(None, CLARE_LOGO_PATH),
520
  show_label=False,
521
+ bubble_full_width=False,
522
  type="tuples",
523
  )
524
 
 
525
  gr.Markdown("#### Rate Clare’s last answer")
526
  with gr.Row():
527
  thumb_up_btn = gr.Button(
 
539
  )
540
  feedback_text = gr.Textbox(
541
  label="What worked well or what was wrong?",
542
+ placeholder="Optional: describe what you liked / what was confusing or incorrect.",
 
 
543
  lines=3,
544
  visible=False,
545
  )
 
551
  interactive=False,
552
  )
553
 
 
554
  user_input = gr.Textbox(
555
  label="Your Input",
556
+ placeholder="Please log in on the right before asking Clare anything...",
 
 
557
  show_label=False,
558
  container=True,
559
  autofocus=False,
560
  interactive=False,
561
  )
562
 
 
563
  with gr.Row():
564
  with gr.Column(scale=2):
565
  syllabus_file = gr.File(
566
  file_types=[".docx", ".pdf", ".pptx"],
567
  file_count="single",
568
  height=160,
569
+ label="Upload additional Module 10 file (.docx/.pdf/.pptx) — optional",
 
 
570
  interactive=False,
571
  )
572
  with gr.Column(scale=1):
 
639
 
640
  gr.Markdown("### Actions")
641
  export_btn = gr.Button(
642
+ "Export Conversation", size="sm", elem_classes="action-btn", interactive=False
 
 
 
643
  )
644
  quiz_btn = gr.Button(
645
+ "Let's Try (Micro-Quiz)", size="sm", elem_classes="action-btn", interactive=False
 
 
 
646
  )
647
  summary_btn = gr.Button(
648
+ "Summarization", size="sm", elem_classes="action-btn", interactive=False
 
 
 
649
  )
650
 
651
  gr.Markdown("### Results")
 
656
  )
657
 
658
  # ================== Login Flow ==================
 
659
  def show_inputs():
660
  return {
661
  login_state_1: gr.update(visible=False),
 
674
  login_state_2: gr.update(),
675
  login_state_3: gr.update(),
676
  student_info_html: gr.update(
677
+ value="<p style='color:red; font-size:12px;'>Please enter both Name and Email/ID to start.</p>"
 
 
 
678
  ),
679
  user_name_state: gr.update(),
680
  user_id_state: gr.update(),
 
712
  user_id_state: id_val,
713
  user_input: gr.update(
714
  interactive=True,
715
+ placeholder="Ask about Module 10 concepts, Responsible AI, or let Clare test you...",
 
 
716
  ),
717
  clear_btn: gr.update(interactive=True),
718
  export_btn: gr.update(interactive=True),
 
725
  learning_mode: gr.update(interactive=True),
726
  model_name: gr.update(interactive=False),
727
  docs_btn: gr.update(interactive=True),
728
+ thumb_up_btn: gr.update(
729
+ interactive=True, value="👍 Helpful", variant="secondary"
730
+ ),
731
+ thumb_down_btn: gr.update(
732
+ interactive=True, value="👎 Not helpful", variant="secondary"
733
+ ),
734
  feedback_toggle_btn: gr.update(interactive=True),
735
  feedback_text: gr.update(visible=False, value=""),
736
  feedback_submit_btn: gr.update(interactive=True, visible=False),
 
779
  user_input: gr.update(
780
  value="",
781
  interactive=False,
782
+ placeholder="Please log in on the right before asking Clare anything...",
 
 
783
  ),
784
  clear_btn: gr.update(interactive=False),
785
  export_btn: gr.update(interactive=False),
 
829
  )
830
 
831
  # ================== Main Logic ==================
 
832
  def update_course_and_rag(file, doc_type_val):
 
 
 
833
  local_topics = preloaded_topics or []
834
  local_chunks = preloaded_chunks or []
835
 
 
866
 
867
  def show_loaded_docs(doc_type_val):
868
  gr.Info(
869
+ f"For this experiment, Clare always includes the pre-loaded Module 10 reading.\n"
870
  f"Additional uploaded {doc_type_val} files will be used as supplementary context.",
871
  title="Loaded Documents",
872
  )
 
886
  doc_type_val,
887
  user_id_val,
888
  ):
889
+ # 没登录
890
+ default_up = gr.update(
891
+ value="👍 Helpful", variant="secondary", interactive=False
892
+ )
893
+ default_down = gr.update(
894
+ value="👎 Not helpful", variant="secondary", interactive=False
895
+ )
896
+
897
  if not user_id_val:
898
  out_msg = (
899
  "🔒 Please log in with your Student Name and Email/ID on the right "
 
905
  weaknesses or [],
906
  cognitive_state or {"confusion": 0, "mastery": 0},
907
  )
908
+ return (
909
+ "",
910
+ new_history,
911
+ weaknesses,
912
+ cognitive_state,
913
+ new_status,
914
+ "",
915
+ "",
916
+ default_up,
917
+ default_down,
918
+ )
919
 
920
  resolved_lang = detect_language(message or "", lang_pref)
921
 
 
925
  weaknesses or [],
926
  cognitive_state or {"confusion": 0, "mastery": 0},
927
  )
928
+ # 已登录时,允许评分,所以按钮可点
929
+ logged_up = gr.update(
930
+ value="👍 Helpful", variant="secondary", interactive=True
931
+ )
932
+ logged_down = gr.update(
933
+ value="👎 Not helpful", variant="secondary", interactive=True
934
+ )
935
+ return (
936
+ "",
937
+ chat_history,
938
+ weaknesses,
939
+ cognitive_state,
940
+ new_status,
941
+ "",
942
+ "",
943
+ logged_up,
944
+ logged_down,
945
+ )
946
 
947
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
948
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
949
 
 
950
  if is_academic_query(message):
951
  rag_context_text, rag_used_chunks = retrieve_relevant_chunks(
952
  message, rag_chunks or []
 
970
  end_ts = time.time()
971
  latency_ms = (end_ts - start_ts) * 1000.0
972
 
 
973
  if is_academic_query(message) and rag_used_chunks:
974
  ref_text = format_references(rag_used_chunks)
975
  else:
 
982
  new_history[-1] = [last_user, last_assistant]
983
  answer = last_assistant
984
 
 
985
  student_id = user_id_val or "ANON"
986
  experiment_id = "RESP_AI_W10"
987
  try:
 
1004
 
1005
  new_status = render_session_status(mode_val, weaknesses, cognitive_state)
1006
 
1007
+ # 新一轮回答出来 -> 重置按钮为默认可点状态
1008
+ logged_up = gr.update(
1009
+ value="👍 Helpful", variant="secondary", interactive=True
1010
+ )
1011
+ logged_down = gr.update(
1012
+ value="👎 Not helpful", variant="secondary", interactive=True
1013
+ )
1014
+
1015
+ return (
1016
+ "",
1017
+ new_history,
1018
+ weaknesses,
1019
+ cognitive_state,
1020
+ new_status,
1021
+ message,
1022
+ answer,
1023
+ logged_up,
1024
+ logged_down,
1025
+ )
1026
 
1027
  user_input.submit(
1028
  respond,
 
1047
  session_status,
1048
  last_question_state,
1049
  last_answer_state,
1050
+ thumb_up_btn,
1051
+ thumb_down_btn,
1052
  ],
1053
  )
1054
 
1055
+ # ===== Micro-Quiz =====
1056
  def start_micro_quiz(
1057
  chat_history,
1058
  course_outline,
 
1090
  "• Do NOT start a content question until I have answered 1 or 2.\n\n"
1091
  "Step 2 – After I choose the style:\n"
1092
  "• If I choose 1 (multiple-choice):\n"
1093
+ " - Ask ONE multiple-choice question at a time, based on Module 10 concepts "
1094
+ "(Responsible AI definition, risk types, mitigation layers, EU AI Act, etc.).\n"
1095
  " - Provide 3–4 options (A, B, C, D) and make only one option clearly correct.\n"
1096
  "• If I choose 2 (short-answer):\n"
1097
  " - Ask ONE short-answer question at a time, also based on Module 10 concepts.\n"
 
1184
  def send_thumb_up(last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref):
1185
  if not last_q and not last_a:
1186
  print("No last QA to log for thumbs_up.")
1187
+ # 仍然返回按钮状态,防止 Gradio 报错
1188
+ return (
1189
+ gr.update(value="👍 Helpful", variant="secondary"),
1190
+ gr.update(value="👎 Not helpful", variant="secondary"),
1191
+ )
1192
  try:
1193
  log_event(
1194
  {
 
1207
  except Exception as e:
1208
  print("thumb_up log error:", e)
1209
 
1210
+ # 实时视觉反馈:左边按钮高亮 + 标记 sent,右边保持可点普通状态
1211
+ return (
1212
+ gr.update(
1213
+ value="👍 Helpful (sent)", variant="primary", interactive=True
1214
+ ),
1215
+ gr.update(value="👎 Not helpful", variant="secondary", interactive=True),
1216
+ )
1217
+
1218
  def send_thumb_down(
1219
  last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref
1220
  ):
1221
  if not last_q and not last_a:
1222
  print("No last QA to log for thumbs_down.")
1223
+ return (
1224
+ gr.update(value="👍 Helpful", variant="secondary"),
1225
+ gr.update(value="👎 Not helpful", variant="secondary"),
1226
+ )
1227
  try:
1228
  log_event(
1229
  {
 
1242
  except Exception as e:
1243
  print("thumb_down log error:", e)
1244
 
1245
+ return (
1246
+ gr.update(value="👍 Helpful", variant="secondary", interactive=True),
1247
+ gr.update(
1248
+ value="👎 Not helpful (sent)",
1249
+ variant="primary",
1250
+ interactive=True,
1251
+ ),
1252
+ )
1253
+
1254
  thumb_up_btn.click(
1255
  send_thumb_up,
1256
  [
 
1261
  model_name,
1262
  language_preference,
1263
  ],
1264
+ [thumb_up_btn, thumb_down_btn],
1265
  )
1266
 
1267
  thumb_down_btn.click(
 
1274
  model_name,
1275
  language_preference,
1276
  ],
1277
+ [thumb_up_btn, thumb_down_btn],
1278
  )
1279
 
1280
  def submit_detailed_feedback(
 
1327
  # ===== Export / Summary =====
1328
  export_btn.click(
1329
  lambda h, c, m, w, cog: export_conversation(h, c, m, w, cog),
1330
+ [chatbot, course_outline_state, learning_mode, weakness_state, cognitive_state_state],
 
 
 
 
 
 
1331
  [result_display],
1332
  )
1333
 
 
1350
  def clear_all():
1351
  empty_state = {"confusion": 0, "mastery": 0}
1352
  default_status = render_session_status("Concept Explainer", [], empty_state)
1353
+ return (
1354
+ [],
1355
+ [],
1356
+ empty_state,
1357
+ [],
1358
+ "",
1359
+ default_status,
1360
+ "",
1361
+ "",
1362
+ gr.update(value="👍 Helpful", variant="secondary", interactive=False),
1363
+ gr.update(value="👎 Not helpful", variant="secondary", interactive=False),
1364
+ )
1365
 
1366
  clear_btn.click(
1367
  clear_all,
 
1375
  session_status,
1376
  last_question_state,
1377
  last_answer_state,
1378
+ thumb_up_btn,
1379
+ thumb_down_btn,
1380
  ],
1381
  queue=False,
1382
  )
1383
 
1384
  if __name__ == "__main__":
1385
  demo.launch(
1386
+ share=True,
1387
  server_name="0.0.0.0",
1388
  server_port=7860,
1389
  )