SarahXia0405 commited on
Commit
6afb080
·
verified ·
1 Parent(s): 97381fc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +232 -49
app.py CHANGED
@@ -31,7 +31,7 @@ from syllabus_utils import extract_course_topics_from_file
31
  HANBRIDGE_LOGO_PATH = "hanbridge_logo.png"
32
  CLARE_LOGO_PATH = "clare_mascot.png"
33
  CLARE_RUN_PATH = "Clare_Run.png"
34
- CLARE_READING_PATH = "Clare_reading.png" # 请确保上传了这张图片
35
 
36
  # ================== Base64 Helper ==================
37
  def image_to_base64(image_path):
@@ -190,7 +190,7 @@ CUSTOM_CSS = """
190
 
191
  /* --- Sidebar Login Panel --- */
192
  .login-panel {
193
- background-color: #e5e7eb; /* 浅灰色背景 */
194
  padding: 15px;
195
  border-radius: 8px;
196
  text-align: center;
@@ -199,7 +199,7 @@ CUSTOM_CSS = """
199
  .login-panel img {
200
  display: block;
201
  margin: 0 auto 10px auto;
202
- height: 80px; /* 调整图片高度 */
203
  object-fit: contain;
204
  }
205
  .login-main-btn {
@@ -209,7 +209,7 @@ CUSTOM_CSS = """
209
  font-weight: bold !important;
210
  }
211
  .logout-btn {
212
- background-color: #6b2828 !important; /* 深红色背景 */
213
  color: #fff !important;
214
  border: none !important;
215
  font-weight: bold !important;
@@ -242,6 +242,31 @@ CUSTOM_CSS = """
242
  @keyframes fadeIn { to { opacity: 1; } }
243
  """
244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  # ================== Gradio App ==================
246
 
247
  with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS) as demo:
@@ -252,7 +277,7 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
252
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
253
  rag_chunks_state = gr.State([])
254
 
255
- # 用户状态
256
  user_name_state = gr.State("")
257
  user_id_state = gr.State("")
258
 
@@ -307,16 +332,26 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
307
 
308
  # User Guide
309
  with gr.Accordion("User Guide", open=True, elem_classes="main-user-guide"):
310
- with gr.Accordion("Getting Started", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["getting_started"])
311
- with gr.Accordion("Mode Definition", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["mode_definition"])
312
- with gr.Accordion("How Clare Works", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["how_clare_works"])
313
- with gr.Accordion("What is Memory Line", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["memory_line"])
314
- with gr.Accordion("Learning Progress Report", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["learning_progress"])
315
- with gr.Accordion("How Clare Uses Your Files", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["how_files"])
316
- with gr.Accordion("Micro-Quiz", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["micro_quiz"])
317
- with gr.Accordion("Summarization", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["summarization"])
318
- with gr.Accordion("Export Conversation", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["export_conversation"])
319
- with gr.Accordion("FAQ", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["faq"])
 
 
 
 
 
 
 
 
 
 
320
 
321
  gr.Markdown("---")
322
  gr.Button("System Settings", size="sm", variant="secondary")
@@ -336,7 +371,7 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
336
  with gr.Row():
337
  # Upload Box
338
  with gr.Column(scale=2):
339
- syllabus_file = gr.File(
340
  file_types=[".docx", ".pdf", ".pptx"],
341
  file_count="single",
342
  height=160,
@@ -344,9 +379,9 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
344
  )
345
  # Controls
346
  with gr.Column(scale=1):
347
- doc_type = gr.Dropdown(choices=DOC_TYPES, value="Syllabus", label="File type", container=True)
348
- gr.HTML("<div style='height:5px'></div>")
349
- docs_btn = gr.Button("📂 Loaded Docs", size="sm", variant="secondary")
350
  # Memory Line
351
  with gr.Column(scale=2):
352
  with gr.Group(elem_classes="memory-line-box"):
@@ -378,18 +413,29 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
378
  </div>
379
  """
380
  )
381
- chatbot = gr.Chatbot(label="", height=450, avatar_images=(None, CLARE_LOGO_PATH), show_label=False, bubble_full_width=False)
382
- user_input = gr.Textbox(label="Your Input", placeholder="Ask about a concept, your assignment, or let Clare test you...", show_label=False, container=True, autofocus=True)
 
 
 
 
 
 
 
 
 
 
 
 
383
 
384
  # === Right Sidebar ===
385
  with gr.Column(scale=1, min_width=180):
386
 
387
- # --- New Login Panel (占用原来的 Spacer 空间) ---
388
  with gr.Group(elem_classes="login-panel"):
389
- # 图片始终显示
390
  gr.HTML(f"<img src='{image_to_base64(CLARE_READING_PATH)}'>")
391
 
392
- # 状态 1: 未登录 (显示 Login 按钮)
393
  with gr.Group(visible=True) as login_state_1:
394
  login_start_btn = gr.Button("Student Login", elem_classes="login-main-btn")
395
 
@@ -399,9 +445,8 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
399
  id_input = gr.Textbox(label="Email/ID", placeholder="ID", container=True)
400
  login_confirm_btn = gr.Button("Enter", variant="primary", size="sm")
401
 
402
- # 状态 3: 已登录 (显示信息 + Logout)
403
  with gr.Group(visible=False) as login_state_3:
404
- # 使用 HTML 显示居中的加粗名字
405
  student_info_html = gr.HTML()
406
  logout_btn = gr.Button("Log out", elem_classes="logout-btn", size="sm")
407
 
@@ -414,7 +459,10 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
414
  # Results
415
  gr.Markdown("### Results")
416
  with gr.Group(elem_classes="result-box"):
417
- result_display = gr.Markdown(value="Results will appear here...", label="Generated Content")
 
 
 
418
 
419
  # ================== Logic: Sidebar Login Flow ==================
420
 
@@ -423,18 +471,27 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
423
  return {
424
  login_state_1: gr.update(visible=False),
425
  login_state_2: gr.update(visible=True),
426
- login_state_3: gr.update(visible=False)
427
  }
428
 
429
- login_start_btn.click(show_inputs, outputs=[login_state_1, login_state_2, login_state_3])
 
 
 
430
 
431
- # 2. Confirm Login -> Show Profile
432
  def confirm_login(name, id_val):
433
  if not name or not id_val:
434
  # 简单验证,若为空则不跳转
435
- return gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
 
 
 
 
 
 
 
436
 
437
- # 构建居中的 HTML 信息
438
  info_html = f"""
439
  <div style="margin-bottom:10px;">
440
  <div style="font-weight:bold; font-size:16px;">{name}</div>
@@ -448,13 +505,20 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
448
  login_state_3: gr.update(visible=True),
449
  student_info_html: gr.update(value=info_html),
450
  user_name_state: name,
451
- user_id_state: id_val
452
  }
453
 
454
  login_confirm_btn.click(
455
  confirm_login,
456
  inputs=[name_input, id_input],
457
- outputs=[login_state_1, login_state_2, login_state_3, student_info_html, user_name_state, user_id_state]
 
 
 
 
 
 
 
458
  )
459
 
460
  # 3. Logout -> Reset
@@ -466,10 +530,21 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
466
  name_input: gr.update(value=""),
467
  id_input: gr.update(value=""),
468
  user_name_state: "",
469
- user_id_state: ""
470
  }
471
 
472
- logout_btn.click(logout, outputs=[login_state_1, login_state_2, login_state_3, name_input, id_input, user_name_state, user_id_state])
 
 
 
 
 
 
 
 
 
 
 
473
 
474
  # ================== Main App Logic ==================
475
 
@@ -479,32 +554,140 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
479
  status_md = f"✅ **File Loaded:** {doc_type_val}\n\nAnalyzing content for Memory Line..."
480
  return topics, rag_chunks, status_md
481
 
482
- syllabus_file.change(update_course_and_rag, [syllabus_file, doc_type], [course_outline_state, rag_chunks_state, session_status])
483
- docs_btn.click(lambda: gr.Info("Current Context: Syllabus.pdf (Uploaded), Course_Intro.docx (System)", title="Loaded Documents"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
 
485
- def respond(message, chat_history, course_outline, weaknesses, cognitive_state, rag_chunks, model, lang, mode, doc_type_val):
486
- resolved_lang = detect_language(message or "", lang)
487
  if not message or not message.strip():
488
- return "", chat_history, weaknesses, cognitive_state, session_status.value
 
 
 
 
 
 
 
489
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
490
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
491
  rag_context = retrieve_relevant_chunks(message, rag_chunks or [])
492
- answer, new_history = chat_with_clare(message, chat_history, model, resolved_lang, mode, doc_type_val, course_outline, weaknesses, cognitive_state, rag_context)
493
- new_status = render_session_status(mode, weaknesses, cognitive_state)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  return "", new_history, weaknesses, cognitive_state, new_status
495
 
496
- user_input.submit(respond, [user_input, chatbot, course_outline_state, weakness_state, cognitive_state_state, rag_chunks_state, model_name, language_preference, learning_mode, doc_type], [user_input, chatbot, weakness_state, cognitive_state_state, session_status])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
 
498
- export_btn.click(lambda h, c, m, w, cog: export_conversation(h, c, m, w, cog), [chatbot, course_outline_state, learning_mode, weakness_state, cognitive_state_state], [result_display])
499
- quiz_btn.click(lambda h, c, w, cog, m, l: generate_quiz_from_history(h, c, w, cog, m, l), [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference], [result_display])
500
- summary_btn.click(lambda h, c, w, cog, m, l: summarize_conversation(h, c, w, cog, m, l), [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference], [result_display])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
 
502
  def clear_all():
503
  empty_state = {"confusion": 0, "mastery": 0}
504
  default_status = render_session_status("Concept Explainer", [], empty_state)
505
  return [], [], empty_state, [], "", default_status
506
- clear_btn.click(clear_all, None, [chatbot, weakness_state, cognitive_state_state, rag_chunks_state, result_display, session_status], queue=False)
 
 
 
 
 
 
507
 
508
  if __name__ == "__main__":
509
  demo.launch()
510
-
 
31
  HANBRIDGE_LOGO_PATH = "hanbridge_logo.png"
32
  CLARE_LOGO_PATH = "clare_mascot.png"
33
  CLARE_RUN_PATH = "Clare_Run.png"
34
+ CLARE_READING_PATH = "Clare_reading.png" # 请确保上传了这张图片
35
 
36
  # ================== Base64 Helper ==================
37
  def image_to_base64(image_path):
 
190
 
191
  /* --- Sidebar Login Panel --- */
192
  .login-panel {
193
+ background-color: #e5e7eb;
194
  padding: 15px;
195
  border-radius: 8px;
196
  text-align: center;
 
199
  .login-panel img {
200
  display: block;
201
  margin: 0 auto 10px auto;
202
+ height: 80px;
203
  object-fit: contain;
204
  }
205
  .login-main-btn {
 
209
  font-weight: bold !important;
210
  }
211
  .logout-btn {
212
+ background-color: #6b2828 !important;
213
  color: #fff !important;
214
  border: none !important;
215
  font-weight: bold !important;
 
242
  @keyframes fadeIn { to { opacity: 1; } }
243
  """
244
 
245
+ # ===== log ====
246
+ import time
247
+ import requests
248
+
249
+ GSHEET_WEBHOOK_URL = os.getenv("GSHEET_WEBHOOK_URL")
250
+ GSHEET_API_KEY = os.getenv("GSHEET_API_KEY")
251
+
252
+ def log_event(data: Dict):
253
+ """
254
+ 把日志发到 Google Sheet(通过 Apps Script Webhook)
255
+ """
256
+ if not GSHEET_WEBHOOK_URL:
257
+ print("Google Sheet webhook not configured, skip logging")
258
+ return
259
+
260
+ payload = data.copy()
261
+ payload["api_key"] = GSHEET_API_KEY or ""
262
+
263
+ try:
264
+ resp = requests.post(GSHEET_WEBHOOK_URL, json=payload, timeout=3)
265
+ if not resp.ok:
266
+ print("GSheet log failed:", resp.status_code, resp.text)
267
+ except Exception as e:
268
+ print("GSheet log exception:", e)
269
+
270
  # ================== Gradio App ==================
271
 
272
  with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS) as demo:
 
277
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
278
  rag_chunks_state = gr.State([])
279
 
280
+ # 用户状态(登录)
281
  user_name_state = gr.State("")
282
  user_id_state = gr.State("")
283
 
 
332
 
333
  # User Guide
334
  with gr.Accordion("User Guide", open=True, elem_classes="main-user-guide"):
335
+ with gr.Accordion("Getting Started", open=False, elem_classes="clean-accordion"):
336
+ gr.Markdown(USER_GUIDE_SECTIONS["getting_started"])
337
+ with gr.Accordion("Mode Definition", open=False, elem_classes="clean-accordion"):
338
+ gr.Markdown(USER_GUIDE_SECTIONS["mode_definition"])
339
+ with gr.Accordion("How Clare Works", open=False, elem_classes="clean-accordion"):
340
+ gr.Markdown(USER_GUIDE_SECTIONS["how_clare_works"])
341
+ with gr.Accordion("What is Memory Line", open=False, elem_classes="clean-accordion"):
342
+ gr.Markdown(USER_GUIDE_SECTIONS["memory_line"])
343
+ with gr.Accordion("Learning Progress Report", open=False, elem_classes="clean-accordion"):
344
+ gr.Markdown(USER_GUIDE_SECTIONS["learning_progress"])
345
+ with gr.Accordion("How Clare Uses Your Files", open=False, elem_classes="clean-accordion"):
346
+ gr.Markdown(USER_GUIDE_SECTIONS["how_files"])
347
+ with gr.Accordion("Micro-Quiz", open=False, elem_classes="clean-accordion"):
348
+ gr.Markdown(USER_GUIDE_SECTIONS["micro_quiz"])
349
+ with gr.Accordion("Summarization", open=False, elem_classes="clean-accordion"):
350
+ gr.Markdown(USER_GUIDE_SECTIONS["summarization"])
351
+ with gr.Accordion("Export Conversation", open=False, elem_classes="clean-accordion"):
352
+ gr.Markdown(USER_GUIDE_SECTIONS["export_conversation"])
353
+ with gr.Accordion("FAQ", open=False, elem_classes="clean-accordion"):
354
+ gr.Markdown(USER_GUIDE_SECTIONS["faq"])
355
 
356
  gr.Markdown("---")
357
  gr.Button("System Settings", size="sm", variant="secondary")
 
371
  with gr.Row():
372
  # Upload Box
373
  with gr.Column(scale=2):
374
+ syllabus_file = gr.File(
375
  file_types=[".docx", ".pdf", ".pptx"],
376
  file_count="single",
377
  height=160,
 
379
  )
380
  # Controls
381
  with gr.Column(scale=1):
382
+ doc_type = gr.Dropdown(choices=DOC_TYPES, value="Syllabus", label="File type", container=True)
383
+ gr.HTML("<div style='height:5px'></div>")
384
+ docs_btn = gr.Button("📂 Loaded Docs", size="sm", variant="secondary")
385
  # Memory Line
386
  with gr.Column(scale=2):
387
  with gr.Group(elem_classes="memory-line-box"):
 
413
  </div>
414
  """
415
  )
416
+ chatbot = gr.Chatbot(
417
+ label="",
418
+ height=450,
419
+ avatar_images=(None, CLARE_LOGO_PATH),
420
+ show_label=False,
421
+ bubble_full_width=False
422
+ )
423
+ user_input = gr.Textbox(
424
+ label="Your Input",
425
+ placeholder="Ask about a concept, your assignment, or let Clare test you...",
426
+ show_label=False,
427
+ container=True,
428
+ autofocus=True
429
+ )
430
 
431
  # === Right Sidebar ===
432
  with gr.Column(scale=1, min_width=180):
433
 
434
+ # --- New Login Panel ---
435
  with gr.Group(elem_classes="login-panel"):
 
436
  gr.HTML(f"<img src='{image_to_base64(CLARE_READING_PATH)}'>")
437
 
438
+ # 状态 1: 未登录
439
  with gr.Group(visible=True) as login_state_1:
440
  login_start_btn = gr.Button("Student Login", elem_classes="login-main-btn")
441
 
 
445
  id_input = gr.Textbox(label="Email/ID", placeholder="ID", container=True)
446
  login_confirm_btn = gr.Button("Enter", variant="primary", size="sm")
447
 
448
+ # 状态 3: 已登录
449
  with gr.Group(visible=False) as login_state_3:
 
450
  student_info_html = gr.HTML()
451
  logout_btn = gr.Button("Log out", elem_classes="logout-btn", size="sm")
452
 
 
459
  # Results
460
  gr.Markdown("### Results")
461
  with gr.Group(elem_classes="result-box"):
462
+ result_display = gr.Markdown(
463
+ value="Results will appear here...",
464
+ label="Generated Content"
465
+ )
466
 
467
  # ================== Logic: Sidebar Login Flow ==================
468
 
 
471
  return {
472
  login_state_1: gr.update(visible=False),
473
  login_state_2: gr.update(visible=True),
474
+ login_state_3: gr.update(visible=False),
475
  }
476
 
477
+ login_start_btn.click(
478
+ show_inputs,
479
+ outputs=[login_state_1, login_state_2, login_state_3]
480
+ )
481
 
482
+ # 2. Confirm Login -> Show Profile & 更新 user_*_state
483
  def confirm_login(name, id_val):
484
  if not name or not id_val:
485
  # 简单验证,若为空则不跳转
486
+ return {
487
+ login_state_1: gr.update(),
488
+ login_state_2: gr.update(),
489
+ login_state_3: gr.update(),
490
+ student_info_html: gr.update(),
491
+ user_name_state: gr.update(),
492
+ user_id_state: gr.update(),
493
+ }
494
 
 
495
  info_html = f"""
496
  <div style="margin-bottom:10px;">
497
  <div style="font-weight:bold; font-size:16px;">{name}</div>
 
505
  login_state_3: gr.update(visible=True),
506
  student_info_html: gr.update(value=info_html),
507
  user_name_state: name,
508
+ user_id_state: id_val,
509
  }
510
 
511
  login_confirm_btn.click(
512
  confirm_login,
513
  inputs=[name_input, id_input],
514
+ outputs=[
515
+ login_state_1,
516
+ login_state_2,
517
+ login_state_3,
518
+ student_info_html,
519
+ user_name_state,
520
+ user_id_state,
521
+ ]
522
  )
523
 
524
  # 3. Logout -> Reset
 
530
  name_input: gr.update(value=""),
531
  id_input: gr.update(value=""),
532
  user_name_state: "",
533
+ user_id_state: "",
534
  }
535
 
536
+ logout_btn.click(
537
+ logout,
538
+ outputs=[
539
+ login_state_1,
540
+ login_state_2,
541
+ login_state_3,
542
+ name_input,
543
+ id_input,
544
+ user_name_state,
545
+ user_id_state,
546
+ ]
547
+ )
548
 
549
  # ================== Main App Logic ==================
550
 
 
554
  status_md = f"✅ **File Loaded:** {doc_type_val}\n\nAnalyzing content for Memory Line..."
555
  return topics, rag_chunks, status_md
556
 
557
+ syllabus_file.change(
558
+ update_course_and_rag,
559
+ [syllabus_file, doc_type],
560
+ [course_outline_state, rag_chunks_state, session_status],
561
+ )
562
+ docs_btn.click(
563
+ lambda: gr.Info(
564
+ "Current Context: Syllabus.pdf (Uploaded), Course_Intro.docx (System)",
565
+ title="Loaded Documents",
566
+ )
567
+ )
568
+
569
+ def respond(
570
+ message,
571
+ chat_history,
572
+ course_outline,
573
+ weaknesses,
574
+ cognitive_state,
575
+ rag_chunks,
576
+ model_name_val,
577
+ lang_pref,
578
+ mode_val,
579
+ doc_type_val,
580
+ user_id_val,
581
+ ):
582
+ # 语言决策
583
+ resolved_lang = detect_language(message or "", lang_pref)
584
 
 
 
585
  if not message or not message.strip():
586
+ # 空输入,直接返回,不更新状态
587
+ new_status = render_session_status(
588
+ mode_val or "Concept Explainer",
589
+ weaknesses or [],
590
+ cognitive_state or {"confusion": 0, "mastery": 0},
591
+ )
592
+ return "", chat_history, weaknesses, cognitive_state, new_status
593
+
594
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
595
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
596
  rag_context = retrieve_relevant_chunks(message, rag_chunks or [])
597
+
598
+ # 调用 Clare 并记录时间
599
+ start_ts = time.time()
600
+ answer, new_history = chat_with_clare(
601
+ message=message,
602
+ history=chat_history,
603
+ model_name=model_name_val,
604
+ language_preference=resolved_lang,
605
+ learning_mode=mode_val,
606
+ doc_type=doc_type_val,
607
+ course_outline=course_outline,
608
+ weaknesses=weaknesses,
609
+ cognitive_state=cognitive_state,
610
+ rag_context=rag_context,
611
+ )
612
+ end_ts = time.time()
613
+ latency_ms = (end_ts - start_ts) * 1000.0
614
+
615
+ # 日志所用的 student_id(来自登录;未登录则为 ANON)
616
+ student_id = user_id_val or "ANON"
617
+ experiment_id = "RESP_AI_W10" # 你定义一个本次实验的ID
618
+
619
+ # 发送日志到 Google Sheet
620
+ try:
621
+ log_event(
622
+ {
623
+ "experiment_id": experiment_id,
624
+ "student_id": student_id,
625
+ "event_type": "chat_turn",
626
+ "timestamp": end_ts,
627
+ "latency_ms": latency_ms,
628
+ "question": message,
629
+ "answer": answer,
630
+ "model_name": model_name_val,
631
+ "language": resolved_lang,
632
+ "learning_mode": mode_val,
633
+ }
634
+ )
635
+ except Exception as e:
636
+ print("log_event error:", e)
637
+
638
+ # 更新 session_status
639
+ new_status = render_session_status(mode_val, weaknesses, cognitive_state)
640
  return "", new_history, weaknesses, cognitive_state, new_status
641
 
642
+ user_input.submit(
643
+ respond,
644
+ [
645
+ user_input,
646
+ chatbot,
647
+ course_outline_state,
648
+ weakness_state,
649
+ cognitive_state_state,
650
+ rag_chunks_state,
651
+ model_name,
652
+ language_preference,
653
+ learning_mode,
654
+ doc_type,
655
+ user_id_state, # ✅ 把登录的 ID 作为 respond 的输入
656
+ ],
657
+ [user_input, chatbot, weakness_state, cognitive_state_state, session_status],
658
+ )
659
 
660
+ export_btn.click(
661
+ lambda h, c, m, w, cog: export_conversation(h, c, m, w, cog),
662
+ [chatbot, course_outline_state, learning_mode, weakness_state, cognitive_state_state],
663
+ [result_display],
664
+ )
665
+ quiz_btn.click(
666
+ lambda h, c, w, cog, m, l: generate_quiz_from_history(
667
+ h, c, w, cog, m, l
668
+ ),
669
+ [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference],
670
+ [result_display],
671
+ )
672
+ summary_btn.click(
673
+ lambda h, c, w, cog, m, l: summarize_conversation(
674
+ h, c, w, cog, m, l
675
+ ),
676
+ [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference],
677
+ [result_display],
678
+ )
679
 
680
  def clear_all():
681
  empty_state = {"confusion": 0, "mastery": 0}
682
  default_status = render_session_status("Concept Explainer", [], empty_state)
683
  return [], [], empty_state, [], "", default_status
684
+
685
+ clear_btn.click(
686
+ clear_all,
687
+ None,
688
+ [chatbot, weakness_state, cognitive_state_state, rag_chunks_state, result_display, session_status],
689
+ queue=False,
690
+ )
691
 
692
  if __name__ == "__main__":
693
  demo.launch()