Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -294,11 +294,6 @@ def format_references(
|
|
| 294 |
根据当前使用的 rag_chunks,生成一个简短的 reference 列表:
|
| 295 |
- 按文件名分组
|
| 296 |
- 每个文件列出若干个 section
|
| 297 |
-
|
| 298 |
-
期望 rag_engine 在每个 chunk 中填入:
|
| 299 |
-
chunk["source_file"] -> 文件名
|
| 300 |
-
chunk["section"] -> 段落 / section 描述(如 "Section 3.2 – Risk taxonomy")
|
| 301 |
-
若未提供,将回退为 "Unknown file" / "Related section"。
|
| 302 |
"""
|
| 303 |
if not rag_chunks:
|
| 304 |
return ""
|
|
@@ -306,7 +301,7 @@ def format_references(
|
|
| 306 |
refs_by_file: Dict[str, List[str]] = defaultdict(list)
|
| 307 |
|
| 308 |
for chunk in rag_chunks:
|
| 309 |
-
file_name = chunk.get("source_file") or "
|
| 310 |
section = chunk.get("section") or "Related section"
|
| 311 |
if section not in refs_by_file[file_name]:
|
| 312 |
refs_by_file[file_name].append(section)
|
|
@@ -330,7 +325,6 @@ def format_references(
|
|
| 330 |
return "\n".join(lines)
|
| 331 |
|
| 332 |
|
| 333 |
-
|
| 334 |
def is_academic_query(message: str) -> bool:
|
| 335 |
"""
|
| 336 |
判断当前学生输入是否是“学术/课程相关”问题。
|
|
@@ -343,7 +337,6 @@ def is_academic_query(message: str) -> bool:
|
|
| 343 |
if not m:
|
| 344 |
return False
|
| 345 |
|
| 346 |
-
# 归一化一下空格
|
| 347 |
m = " ".join(m.split())
|
| 348 |
|
| 349 |
# 1) 典型闲聊词
|
|
@@ -356,11 +349,10 @@ def is_academic_query(message: str) -> bool:
|
|
| 356 |
}
|
| 357 |
tokens = m.split()
|
| 358 |
|
| 359 |
-
# 如果全是闲聊词而且没有问号 → 视为非学术问题
|
| 360 |
if "?" not in m and all(t in smalltalk_tokens for t in tokens):
|
| 361 |
return False
|
| 362 |
|
| 363 |
-
# 2)
|
| 364 |
meta_phrases = [
|
| 365 |
"who are you",
|
| 366 |
"what are you",
|
|
@@ -375,22 +367,18 @@ def is_academic_query(message: str) -> bool:
|
|
| 375 |
"what is this app",
|
| 376 |
"what is this tool",
|
| 377 |
"what is clare",
|
| 378 |
-
"who is clare"
|
| 379 |
]
|
| 380 |
if any(p in m for p in meta_phrases):
|
| 381 |
return False
|
| 382 |
|
| 383 |
-
# 3) 很短
|
| 384 |
if len(tokens) <= 2 and "?" not in m:
|
| 385 |
return False
|
| 386 |
|
| 387 |
-
# 其他情况默认当作学术/课程相关问题
|
| 388 |
return True
|
| 389 |
|
| 390 |
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
# ================== Gradio App ==================
|
| 395 |
with gr.Blocks(
|
| 396 |
title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
|
|
@@ -402,6 +390,10 @@ with gr.Blocks(
|
|
| 402 |
cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
|
| 403 |
rag_chunks_state = gr.State(preloaded_chunks or [])
|
| 404 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
# 用户状态(登录)
|
| 406 |
user_name_state = gr.State("")
|
| 407 |
user_id_state = gr.State("")
|
|
@@ -532,7 +524,7 @@ with gr.Blocks(
|
|
| 532 |
# === Center Main ===
|
| 533 |
with gr.Column(scale=3):
|
| 534 |
|
| 535 |
-
#
|
| 536 |
gr.Markdown(
|
| 537 |
"""
|
| 538 |
<div style="background-color:#f9fafb; padding:10px; border-radius:5px; margin-top:10px; font-size:0.9em; color:#555;">
|
|
@@ -550,6 +542,31 @@ with gr.Blocks(
|
|
| 550 |
show_label=False,
|
| 551 |
bubble_full_width=False,
|
| 552 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 553 |
user_input = gr.Textbox(
|
| 554 |
label="Your Input",
|
| 555 |
placeholder="Please log in on the right before asking Clare anything...",
|
|
@@ -559,7 +576,7 @@ with gr.Blocks(
|
|
| 559 |
interactive=False,
|
| 560 |
)
|
| 561 |
|
| 562 |
-
#
|
| 563 |
with gr.Row():
|
| 564 |
with gr.Column(scale=2):
|
| 565 |
syllabus_file = gr.File(
|
|
@@ -691,6 +708,11 @@ with gr.Blocks(
|
|
| 691 |
learning_mode: gr.update(interactive=False),
|
| 692 |
model_name: gr.update(interactive=False),
|
| 693 |
docs_btn: gr.update(interactive=False),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 694 |
}
|
| 695 |
|
| 696 |
info_html = f"""
|
|
@@ -721,6 +743,11 @@ with gr.Blocks(
|
|
| 721 |
learning_mode: gr.update(interactive=True),
|
| 722 |
model_name: gr.update(interactive=False),
|
| 723 |
docs_btn: gr.update(interactive=True),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
}
|
| 725 |
|
| 726 |
login_confirm_btn.click(
|
|
@@ -745,6 +772,11 @@ with gr.Blocks(
|
|
| 745 |
learning_mode,
|
| 746 |
model_name,
|
| 747 |
docs_btn,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
],
|
| 749 |
)
|
| 750 |
|
|
@@ -773,6 +805,11 @@ with gr.Blocks(
|
|
| 773 |
language_preference: gr.update(interactive=False),
|
| 774 |
learning_mode: gr.update(interactive=False),
|
| 775 |
docs_btn: gr.update(interactive=False),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 776 |
}
|
| 777 |
|
| 778 |
logout_btn.click(
|
|
@@ -797,6 +834,11 @@ with gr.Blocks(
|
|
| 797 |
language_preference,
|
| 798 |
learning_mode,
|
| 799 |
docs_btn,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 800 |
],
|
| 801 |
)
|
| 802 |
|
|
@@ -874,7 +916,7 @@ with gr.Blocks(
|
|
| 874 |
weaknesses or [],
|
| 875 |
cognitive_state or {"confusion": 0, "mastery": 0},
|
| 876 |
)
|
| 877 |
-
return "", new_history, weaknesses, cognitive_state, new_status
|
| 878 |
|
| 879 |
resolved_lang = detect_language(message or "", lang_pref)
|
| 880 |
|
|
@@ -884,14 +926,13 @@ with gr.Blocks(
|
|
| 884 |
weaknesses or [],
|
| 885 |
cognitive_state or {"confusion": 0, "mastery": 0},
|
| 886 |
)
|
| 887 |
-
return "", chat_history, weaknesses, cognitive_state, new_status
|
| 888 |
|
| 889 |
# 更新学生状态
|
| 890 |
weaknesses = update_weaknesses_from_message(message, weaknesses or [])
|
| 891 |
cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
|
| 892 |
|
| 893 |
-
# RAG
|
| 894 |
-
# 只对“学术/课程相关”问题调用 RAG;闲聊不查文档,也不打 reference
|
| 895 |
if is_academic_query(message):
|
| 896 |
rag_context_text, rag_used_chunks = retrieve_relevant_chunks(
|
| 897 |
message, rag_chunks or []
|
|
@@ -899,8 +940,6 @@ with gr.Blocks(
|
|
| 899 |
else:
|
| 900 |
rag_context_text, rag_used_chunks = "", []
|
| 901 |
|
| 902 |
-
|
| 903 |
-
# 计时
|
| 904 |
start_ts = time.time()
|
| 905 |
answer, new_history = chat_with_clare(
|
| 906 |
message=message,
|
|
@@ -912,28 +951,27 @@ with gr.Blocks(
|
|
| 912 |
course_outline=course_outline,
|
| 913 |
weaknesses=weaknesses,
|
| 914 |
cognitive_state=cognitive_state,
|
| 915 |
-
rag_context=rag_context_text,
|
| 916 |
)
|
| 917 |
end_ts = time.time()
|
| 918 |
latency_ms = (end_ts - start_ts) * 1000.0
|
| 919 |
|
| 920 |
-
#
|
| 921 |
-
ref_text = ""
|
| 922 |
if is_academic_query(message) and rag_used_chunks:
|
| 923 |
ref_text = format_references(rag_used_chunks)
|
|
|
|
|
|
|
| 924 |
|
| 925 |
if ref_text and new_history:
|
| 926 |
last_user, last_assistant = new_history[-1]
|
| 927 |
if "References (RAG context used):" not in (last_assistant or ""):
|
| 928 |
last_assistant = f"{last_assistant}\n\n{ref_text}"
|
| 929 |
new_history[-1] = [last_user, last_assistant]
|
| 930 |
-
answer = last_assistant
|
| 931 |
-
# ============================================
|
| 932 |
|
| 933 |
# 日志
|
| 934 |
student_id = user_id_val or "ANON"
|
| 935 |
experiment_id = "RESP_AI_W10"
|
| 936 |
-
|
| 937 |
try:
|
| 938 |
log_event(
|
| 939 |
{
|
|
@@ -953,9 +991,9 @@ with gr.Blocks(
|
|
| 953 |
print("log_event error:", e)
|
| 954 |
|
| 955 |
new_status = render_session_status(mode_val, weaknesses, cognitive_state)
|
| 956 |
-
return "", new_history, weaknesses, cognitive_state, new_status
|
| 957 |
-
|
| 958 |
|
|
|
|
|
|
|
| 959 |
|
| 960 |
user_input.submit(
|
| 961 |
respond,
|
|
@@ -972,7 +1010,15 @@ with gr.Blocks(
|
|
| 972 |
doc_type,
|
| 973 |
user_id_state,
|
| 974 |
],
|
| 975 |
-
[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 976 |
)
|
| 977 |
|
| 978 |
# ===== Micro-Quiz: 直接在主 Chatbot 里进行 =====
|
|
@@ -1031,13 +1077,10 @@ with gr.Blocks(
|
|
| 1031 |
resolved_lang = lang_pref
|
| 1032 |
|
| 1033 |
start_ts = time.time()
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
quiz_ctx_text, _quiz_ctx_chunks = retrieve_relevant_chunks(
|
| 1037 |
"Module 10 quiz", rag_chunks or []
|
| 1038 |
)
|
| 1039 |
|
| 1040 |
-
|
| 1041 |
answer, new_history = chat_with_clare(
|
| 1042 |
message=quiz_instruction,
|
| 1043 |
history=chat_history,
|
|
@@ -1094,6 +1137,141 @@ with gr.Blocks(
|
|
| 1094 |
[chatbot, weakness_state, cognitive_state_state, session_status],
|
| 1095 |
)
|
| 1096 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1097 |
# ===== Export / Summary =====
|
| 1098 |
export_btn.click(
|
| 1099 |
lambda h, c, m, w, cog: export_conversation(h, c, m, w, cog),
|
|
@@ -1120,7 +1298,7 @@ with gr.Blocks(
|
|
| 1120 |
def clear_all():
|
| 1121 |
empty_state = {"confusion": 0, "mastery": 0}
|
| 1122 |
default_status = render_session_status("Concept Explainer", [], empty_state)
|
| 1123 |
-
return [], [], empty_state, [], "", default_status
|
| 1124 |
|
| 1125 |
clear_btn.click(
|
| 1126 |
clear_all,
|
|
@@ -1132,6 +1310,8 @@ with gr.Blocks(
|
|
| 1132 |
rag_chunks_state,
|
| 1133 |
result_display,
|
| 1134 |
session_status,
|
|
|
|
|
|
|
| 1135 |
],
|
| 1136 |
queue=False,
|
| 1137 |
)
|
|
|
|
| 294 |
根据当前使用的 rag_chunks,生成一个简短的 reference 列表:
|
| 295 |
- 按文件名分组
|
| 296 |
- 每个文件列出若干个 section
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
"""
|
| 298 |
if not rag_chunks:
|
| 299 |
return ""
|
|
|
|
| 301 |
refs_by_file: Dict[str, List[str]] = defaultdict(list)
|
| 302 |
|
| 303 |
for chunk in rag_chunks:
|
| 304 |
+
file_name = chunk.get("source_file") or "module10_responsible_ai.pdf"
|
| 305 |
section = chunk.get("section") or "Related section"
|
| 306 |
if section not in refs_by_file[file_name]:
|
| 307 |
refs_by_file[file_name].append(section)
|
|
|
|
| 325 |
return "\n".join(lines)
|
| 326 |
|
| 327 |
|
|
|
|
| 328 |
def is_academic_query(message: str) -> bool:
|
| 329 |
"""
|
| 330 |
判断当前学生输入是否是“学术/课程相关”问题。
|
|
|
|
| 337 |
if not m:
|
| 338 |
return False
|
| 339 |
|
|
|
|
| 340 |
m = " ".join(m.split())
|
| 341 |
|
| 342 |
# 1) 典型闲聊词
|
|
|
|
| 349 |
}
|
| 350 |
tokens = m.split()
|
| 351 |
|
|
|
|
| 352 |
if "?" not in m and all(t in smalltalk_tokens for t in tokens):
|
| 353 |
return False
|
| 354 |
|
| 355 |
+
# 2) 自我介绍 / 工具说明
|
| 356 |
meta_phrases = [
|
| 357 |
"who are you",
|
| 358 |
"what are you",
|
|
|
|
| 367 |
"what is this app",
|
| 368 |
"what is this tool",
|
| 369 |
"what is clare",
|
| 370 |
+
"who is clare",
|
| 371 |
]
|
| 372 |
if any(p in m for p in meta_phrases):
|
| 373 |
return False
|
| 374 |
|
| 375 |
+
# 3) 很短又没有问号,大概率不是学术问题
|
| 376 |
if len(tokens) <= 2 and "?" not in m:
|
| 377 |
return False
|
| 378 |
|
|
|
|
| 379 |
return True
|
| 380 |
|
| 381 |
|
|
|
|
|
|
|
|
|
|
| 382 |
# ================== Gradio App ==================
|
| 383 |
with gr.Blocks(
|
| 384 |
title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
|
|
|
|
| 390 |
cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
|
| 391 |
rag_chunks_state = gr.State(preloaded_chunks or [])
|
| 392 |
|
| 393 |
+
# 最近一次回答(用于每条回答的 thumbs / 详细反馈)
|
| 394 |
+
last_question_state = gr.State("")
|
| 395 |
+
last_answer_state = gr.State("")
|
| 396 |
+
|
| 397 |
# 用户状态(登录)
|
| 398 |
user_name_state = gr.State("")
|
| 399 |
user_id_state = gr.State("")
|
|
|
|
| 524 |
# === Center Main ===
|
| 525 |
with gr.Column(scale=3):
|
| 526 |
|
| 527 |
+
# Instruction + Chat
|
| 528 |
gr.Markdown(
|
| 529 |
"""
|
| 530 |
<div style="background-color:#f9fafb; padding:10px; border-radius:5px; margin-top:10px; font-size:0.9em; color:#555;">
|
|
|
|
| 542 |
show_label=False,
|
| 543 |
bubble_full_width=False,
|
| 544 |
)
|
| 545 |
+
|
| 546 |
+
# === Feedback on last answer ===
|
| 547 |
+
gr.Markdown("#### Rate Clare’s last answer")
|
| 548 |
+
with gr.Row():
|
| 549 |
+
thumb_up_btn = gr.Button(
|
| 550 |
+
"👍 Helpful", size="sm", interactive=False
|
| 551 |
+
)
|
| 552 |
+
thumb_down_btn = gr.Button(
|
| 553 |
+
"👎 Not helpful", size="sm", interactive=False
|
| 554 |
+
)
|
| 555 |
+
|
| 556 |
+
feedback_toggle_btn = gr.Button(
|
| 557 |
+
"Give detailed feedback", size="sm", variant="secondary", interactive=False
|
| 558 |
+
)
|
| 559 |
+
feedback_text = gr.Textbox(
|
| 560 |
+
label="What worked well or what was wrong?",
|
| 561 |
+
placeholder="Optional: describe what you liked / what was confusing or incorrect.",
|
| 562 |
+
lines=3,
|
| 563 |
+
visible=False,
|
| 564 |
+
)
|
| 565 |
+
feedback_submit_btn = gr.Button(
|
| 566 |
+
"Submit Feedback", size="sm", variant="primary", visible=False, interactive=False
|
| 567 |
+
)
|
| 568 |
+
|
| 569 |
+
# 用户输入
|
| 570 |
user_input = gr.Textbox(
|
| 571 |
label="Your Input",
|
| 572 |
placeholder="Please log in on the right before asking Clare anything...",
|
|
|
|
| 576 |
interactive=False,
|
| 577 |
)
|
| 578 |
|
| 579 |
+
# Upload / File type / Memory Line
|
| 580 |
with gr.Row():
|
| 581 |
with gr.Column(scale=2):
|
| 582 |
syllabus_file = gr.File(
|
|
|
|
| 708 |
learning_mode: gr.update(interactive=False),
|
| 709 |
model_name: gr.update(interactive=False),
|
| 710 |
docs_btn: gr.update(interactive=False),
|
| 711 |
+
thumb_up_btn: gr.update(interactive=False),
|
| 712 |
+
thumb_down_btn: gr.update(interactive=False),
|
| 713 |
+
feedback_toggle_btn: gr.update(interactive=False),
|
| 714 |
+
feedback_text: gr.update(visible=False, value=""),
|
| 715 |
+
feedback_submit_btn: gr.update(interactive=False, visible=False),
|
| 716 |
}
|
| 717 |
|
| 718 |
info_html = f"""
|
|
|
|
| 743 |
learning_mode: gr.update(interactive=True),
|
| 744 |
model_name: gr.update(interactive=False),
|
| 745 |
docs_btn: gr.update(interactive=True),
|
| 746 |
+
thumb_up_btn: gr.update(interactive=True),
|
| 747 |
+
thumb_down_btn: gr.update(interactive=True),
|
| 748 |
+
feedback_toggle_btn: gr.update(interactive=True),
|
| 749 |
+
feedback_text: gr.update(visible=False, value=""),
|
| 750 |
+
feedback_submit_btn: gr.update(interactive=True, visible=False),
|
| 751 |
}
|
| 752 |
|
| 753 |
login_confirm_btn.click(
|
|
|
|
| 772 |
learning_mode,
|
| 773 |
model_name,
|
| 774 |
docs_btn,
|
| 775 |
+
thumb_up_btn,
|
| 776 |
+
thumb_down_btn,
|
| 777 |
+
feedback_toggle_btn,
|
| 778 |
+
feedback_text,
|
| 779 |
+
feedback_submit_btn,
|
| 780 |
],
|
| 781 |
)
|
| 782 |
|
|
|
|
| 805 |
language_preference: gr.update(interactive=False),
|
| 806 |
learning_mode: gr.update(interactive=False),
|
| 807 |
docs_btn: gr.update(interactive=False),
|
| 808 |
+
thumb_up_btn: gr.update(interactive=False),
|
| 809 |
+
thumb_down_btn: gr.update(interactive=False),
|
| 810 |
+
feedback_toggle_btn: gr.update(interactive=False),
|
| 811 |
+
feedback_text: gr.update(visible=False, value=""),
|
| 812 |
+
feedback_submit_btn: gr.update(interactive=False, visible=False),
|
| 813 |
}
|
| 814 |
|
| 815 |
logout_btn.click(
|
|
|
|
| 834 |
language_preference,
|
| 835 |
learning_mode,
|
| 836 |
docs_btn,
|
| 837 |
+
thumb_up_btn,
|
| 838 |
+
thumb_down_btn,
|
| 839 |
+
feedback_toggle_btn,
|
| 840 |
+
feedback_text,
|
| 841 |
+
feedback_submit_btn,
|
| 842 |
],
|
| 843 |
)
|
| 844 |
|
|
|
|
| 916 |
weaknesses or [],
|
| 917 |
cognitive_state or {"confusion": 0, "mastery": 0},
|
| 918 |
)
|
| 919 |
+
return "", new_history, weaknesses, cognitive_state, new_status, "", ""
|
| 920 |
|
| 921 |
resolved_lang = detect_language(message or "", lang_pref)
|
| 922 |
|
|
|
|
| 926 |
weaknesses or [],
|
| 927 |
cognitive_state or {"confusion": 0, "mastery": 0},
|
| 928 |
)
|
| 929 |
+
return "", chat_history, weaknesses, cognitive_state, new_status, "", ""
|
| 930 |
|
| 931 |
# 更新学生状态
|
| 932 |
weaknesses = update_weaknesses_from_message(message, weaknesses or [])
|
| 933 |
cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
|
| 934 |
|
| 935 |
+
# RAG:只对学术问题启用
|
|
|
|
| 936 |
if is_academic_query(message):
|
| 937 |
rag_context_text, rag_used_chunks = retrieve_relevant_chunks(
|
| 938 |
message, rag_chunks or []
|
|
|
|
| 940 |
else:
|
| 941 |
rag_context_text, rag_used_chunks = "", []
|
| 942 |
|
|
|
|
|
|
|
| 943 |
start_ts = time.time()
|
| 944 |
answer, new_history = chat_with_clare(
|
| 945 |
message=message,
|
|
|
|
| 951 |
course_outline=course_outline,
|
| 952 |
weaknesses=weaknesses,
|
| 953 |
cognitive_state=cognitive_state,
|
| 954 |
+
rag_context=rag_context_text,
|
| 955 |
)
|
| 956 |
end_ts = time.time()
|
| 957 |
latency_ms = (end_ts - start_ts) * 1000.0
|
| 958 |
|
| 959 |
+
# 只在学术型问题上附 References
|
|
|
|
| 960 |
if is_academic_query(message) and rag_used_chunks:
|
| 961 |
ref_text = format_references(rag_used_chunks)
|
| 962 |
+
else:
|
| 963 |
+
ref_text = ""
|
| 964 |
|
| 965 |
if ref_text and new_history:
|
| 966 |
last_user, last_assistant = new_history[-1]
|
| 967 |
if "References (RAG context used):" not in (last_assistant or ""):
|
| 968 |
last_assistant = f"{last_assistant}\n\n{ref_text}"
|
| 969 |
new_history[-1] = [last_user, last_assistant]
|
| 970 |
+
answer = last_assistant
|
|
|
|
| 971 |
|
| 972 |
# 日志
|
| 973 |
student_id = user_id_val or "ANON"
|
| 974 |
experiment_id = "RESP_AI_W10"
|
|
|
|
| 975 |
try:
|
| 976 |
log_event(
|
| 977 |
{
|
|
|
|
| 991 |
print("log_event error:", e)
|
| 992 |
|
| 993 |
new_status = render_session_status(mode_val, weaknesses, cognitive_state)
|
|
|
|
|
|
|
| 994 |
|
| 995 |
+
# 将当前这一轮的 Q/A 存入 state,后面 thumbs / 详细反馈用
|
| 996 |
+
return "", new_history, weaknesses, cognitive_state, new_status, message, answer
|
| 997 |
|
| 998 |
user_input.submit(
|
| 999 |
respond,
|
|
|
|
| 1010 |
doc_type,
|
| 1011 |
user_id_state,
|
| 1012 |
],
|
| 1013 |
+
[
|
| 1014 |
+
user_input,
|
| 1015 |
+
chatbot,
|
| 1016 |
+
weakness_state,
|
| 1017 |
+
cognitive_state_state,
|
| 1018 |
+
session_status,
|
| 1019 |
+
last_question_state,
|
| 1020 |
+
last_answer_state,
|
| 1021 |
+
],
|
| 1022 |
)
|
| 1023 |
|
| 1024 |
# ===== Micro-Quiz: 直接在主 Chatbot 里进行 =====
|
|
|
|
| 1077 |
resolved_lang = lang_pref
|
| 1078 |
|
| 1079 |
start_ts = time.time()
|
|
|
|
|
|
|
| 1080 |
quiz_ctx_text, _quiz_ctx_chunks = retrieve_relevant_chunks(
|
| 1081 |
"Module 10 quiz", rag_chunks or []
|
| 1082 |
)
|
| 1083 |
|
|
|
|
| 1084 |
answer, new_history = chat_with_clare(
|
| 1085 |
message=quiz_instruction,
|
| 1086 |
history=chat_history,
|
|
|
|
| 1137 |
[chatbot, weakness_state, cognitive_state_state, session_status],
|
| 1138 |
)
|
| 1139 |
|
| 1140 |
+
# ===== Feedback Handlers =====
|
| 1141 |
+
def show_feedback_box():
|
| 1142 |
+
"""
|
| 1143 |
+
点击 'Give detailed feedback' 时显示文本框 + 提交按钮
|
| 1144 |
+
"""
|
| 1145 |
+
return {
|
| 1146 |
+
feedback_text: gr.update(visible=True),
|
| 1147 |
+
feedback_submit_btn: gr.update(visible=True),
|
| 1148 |
+
}
|
| 1149 |
+
|
| 1150 |
+
feedback_toggle_btn.click(
|
| 1151 |
+
show_feedback_box,
|
| 1152 |
+
None,
|
| 1153 |
+
[feedback_text, feedback_submit_btn],
|
| 1154 |
+
)
|
| 1155 |
+
|
| 1156 |
+
def send_thumb_up(last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref):
|
| 1157 |
+
if not last_q and not last_a:
|
| 1158 |
+
print("No last QA to log for thumbs_up.")
|
| 1159 |
+
return
|
| 1160 |
+
try:
|
| 1161 |
+
log_event(
|
| 1162 |
+
{
|
| 1163 |
+
"experiment_id": "RESP_AI_W10",
|
| 1164 |
+
"student_id": user_id_val or "ANON",
|
| 1165 |
+
"event_type": "thumbs_up",
|
| 1166 |
+
"timestamp": time.time(),
|
| 1167 |
+
"question": last_q,
|
| 1168 |
+
"answer": last_a,
|
| 1169 |
+
"model_name": model_name_val,
|
| 1170 |
+
"language": lang_pref,
|
| 1171 |
+
"learning_mode": mode_val,
|
| 1172 |
+
}
|
| 1173 |
+
)
|
| 1174 |
+
print("[Feedback] thumbs_up logged.")
|
| 1175 |
+
except Exception as e:
|
| 1176 |
+
print("thumb_up log error:", e)
|
| 1177 |
+
|
| 1178 |
+
def send_thumb_down(last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref):
|
| 1179 |
+
if not last_q and not last_a:
|
| 1180 |
+
print("No last QA to log for thumbs_down.")
|
| 1181 |
+
return
|
| 1182 |
+
try:
|
| 1183 |
+
log_event(
|
| 1184 |
+
{
|
| 1185 |
+
"experiment_id": "RESP_AI_W10",
|
| 1186 |
+
"student_id": user_id_val or "ANON",
|
| 1187 |
+
"event_type": "thumbs_down",
|
| 1188 |
+
"timestamp": time.time(),
|
| 1189 |
+
"question": last_q,
|
| 1190 |
+
"answer": last_a,
|
| 1191 |
+
"model_name": model_name_val,
|
| 1192 |
+
"language": lang_pref,
|
| 1193 |
+
"learning_mode": mode_val,
|
| 1194 |
+
}
|
| 1195 |
+
)
|
| 1196 |
+
print("[Feedback] thumbs_down logged.")
|
| 1197 |
+
except Exception as e:
|
| 1198 |
+
print("thumb_down log error:", e)
|
| 1199 |
+
|
| 1200 |
+
thumb_up_btn.click(
|
| 1201 |
+
send_thumb_up,
|
| 1202 |
+
[
|
| 1203 |
+
last_question_state,
|
| 1204 |
+
last_answer_state,
|
| 1205 |
+
user_id_state,
|
| 1206 |
+
learning_mode,
|
| 1207 |
+
model_name,
|
| 1208 |
+
language_preference,
|
| 1209 |
+
],
|
| 1210 |
+
[],
|
| 1211 |
+
)
|
| 1212 |
+
|
| 1213 |
+
thumb_down_btn.click(
|
| 1214 |
+
send_thumb_down,
|
| 1215 |
+
[
|
| 1216 |
+
last_question_state,
|
| 1217 |
+
last_answer_state,
|
| 1218 |
+
user_id_state,
|
| 1219 |
+
learning_mode,
|
| 1220 |
+
model_name,
|
| 1221 |
+
language_preference,
|
| 1222 |
+
],
|
| 1223 |
+
[],
|
| 1224 |
+
)
|
| 1225 |
+
|
| 1226 |
+
def submit_detailed_feedback(
|
| 1227 |
+
text, last_q, last_a, user_id_val, mode_val, model_name_val, lang_pref
|
| 1228 |
+
):
|
| 1229 |
+
if not text or not text.strip():
|
| 1230 |
+
# 提示:请先填写再提交(用 placeholder 提醒)
|
| 1231 |
+
return gr.update(
|
| 1232 |
+
value="",
|
| 1233 |
+
placeholder="Please enter some feedback before submitting.",
|
| 1234 |
+
)
|
| 1235 |
+
|
| 1236 |
+
try:
|
| 1237 |
+
log_event(
|
| 1238 |
+
{
|
| 1239 |
+
"experiment_id": "RESP_AI_W10",
|
| 1240 |
+
"student_id": user_id_val or "ANON",
|
| 1241 |
+
"event_type": "detailed_feedback",
|
| 1242 |
+
"timestamp": time.time(),
|
| 1243 |
+
"question": last_q,
|
| 1244 |
+
"answer": last_a,
|
| 1245 |
+
"feedback_text": text.strip(),
|
| 1246 |
+
"model_name": model_name_val,
|
| 1247 |
+
"language": lang_pref,
|
| 1248 |
+
"learning_mode": mode_val,
|
| 1249 |
+
}
|
| 1250 |
+
)
|
| 1251 |
+
print("[Feedback] detailed_feedback logged.")
|
| 1252 |
+
except Exception as e:
|
| 1253 |
+
print("detailed_feedback log error:", e)
|
| 1254 |
+
|
| 1255 |
+
# 清空文本框,并显示感谢(用 placeholder)
|
| 1256 |
+
return gr.update(
|
| 1257 |
+
value="",
|
| 1258 |
+
placeholder="Thanks! Your feedback has been recorded.",
|
| 1259 |
+
)
|
| 1260 |
+
|
| 1261 |
+
feedback_submit_btn.click(
|
| 1262 |
+
submit_detailed_feedback,
|
| 1263 |
+
[
|
| 1264 |
+
feedback_text,
|
| 1265 |
+
last_question_state,
|
| 1266 |
+
last_answer_state,
|
| 1267 |
+
user_id_state,
|
| 1268 |
+
learning_mode,
|
| 1269 |
+
model_name,
|
| 1270 |
+
language_preference,
|
| 1271 |
+
],
|
| 1272 |
+
[feedback_text],
|
| 1273 |
+
)
|
| 1274 |
+
|
| 1275 |
# ===== Export / Summary =====
|
| 1276 |
export_btn.click(
|
| 1277 |
lambda h, c, m, w, cog: export_conversation(h, c, m, w, cog),
|
|
|
|
| 1298 |
def clear_all():
|
| 1299 |
empty_state = {"confusion": 0, "mastery": 0}
|
| 1300 |
default_status = render_session_status("Concept Explainer", [], empty_state)
|
| 1301 |
+
return [], [], empty_state, [], "", default_status, "", ""
|
| 1302 |
|
| 1303 |
clear_btn.click(
|
| 1304 |
clear_all,
|
|
|
|
| 1310 |
rag_chunks_state,
|
| 1311 |
result_display,
|
| 1312 |
session_status,
|
| 1313 |
+
last_question_state,
|
| 1314 |
+
last_answer_state,
|
| 1315 |
],
|
| 1316 |
queue=False,
|
| 1317 |
)
|