SarahXia0405 commited on
Commit
6a691fc
·
verified ·
1 Parent(s): f53558e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -24
app.py CHANGED
@@ -42,20 +42,24 @@ LEARNING_MODES = [
42
  LEARNING_MODE_INSTRUCTIONS = {
43
  "Concept Explainer": (
44
  "Explain concepts step by step. Use clear definitions, key formulas or structures, "
45
- "and one or two simple examples. Focus on clarity over depth."
 
46
  ),
47
  "Socratic Tutor": (
48
- "Use a Socratic style. Ask the student short questions first, guide them to reason "
49
- "step by step, and only give full explanations after they try."
 
50
  ),
51
  "Exam Prep / Quiz": (
52
  "Behave like an exam prep coach. Often propose short quiz-style questions "
53
- "(multiple choice or short answer), then explain the solutions."
 
54
  ),
55
  "Assignment Helper": (
56
- "Help with assignments without giving full final solutions. Clarify requirements, "
57
- "break tasks into smaller steps, and provide hints or partial examples instead of "
58
- "complete code or final answers."
 
59
  ),
60
  "Quick Summary": (
61
  "Provide concise, bullet-point style summaries and cheat-sheet style notes. "
@@ -131,6 +135,22 @@ WEAKNESS_KEYWORDS = [
131
  "很难",
132
  ]
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  def update_weaknesses_from_message(message: str, weaknesses: List[str]) -> List[str]:
135
  lower_msg = message.lower()
136
  if any(k in lower_msg for k in WEAKNESS_KEYWORDS):
@@ -139,6 +159,39 @@ def update_weaknesses_from_message(message: str, weaknesses: List[str]) -> List[
139
  return weaknesses
140
 
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  # ---------- 构建 messages ----------
143
  def build_messages(
144
  user_message: str,
@@ -148,6 +201,7 @@ def build_messages(
148
  doc_type: str,
149
  course_outline: Optional[List[str]],
150
  weaknesses: Optional[List[str]],
 
151
  ) -> List[Dict[str, str]]:
152
  messages: List[Dict[str, str]] = [
153
  {"role": "system", "content": CLARE_SYSTEM_PROMPT}
@@ -203,6 +257,45 @@ def build_messages(
203
  }
204
  )
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  # 语言偏好控制
207
  if language_preference == "English":
208
  messages.append(
@@ -233,6 +326,7 @@ def chat_with_clare(
233
  doc_type: str,
234
  course_outline: Optional[List[str]],
235
  weaknesses: Optional[List[str]],
 
236
  ):
237
  try:
238
  messages = build_messages(
@@ -243,6 +337,7 @@ def chat_with_clare(
243
  doc_type=doc_type,
244
  course_outline=course_outline,
245
  weaknesses=weaknesses,
 
246
  )
247
  response = client.chat.completions.create(
248
  model=model_name or DEFAULT_MODEL,
@@ -263,11 +358,13 @@ def export_conversation(
263
  course_outline: List[str],
264
  learning_mode_val: str,
265
  weaknesses: List[str],
 
266
  ) -> str:
267
  lines: List[str] = []
268
  lines.append("# Clare – Conversation Export\n")
269
  lines.append(f"- Learning mode: **{learning_mode_val}**\n")
270
  lines.append("- Course topics (short): " + "; ".join(course_outline[:5]) + "\n")
 
271
 
272
  if weaknesses:
273
  lines.append("- Observed student difficulties:\n")
@@ -288,6 +385,7 @@ def generate_quiz_from_history(
288
  history: List[Tuple[str, str]],
289
  course_outline: List[str],
290
  weaknesses: List[str],
 
291
  model_name: str,
292
  language_preference: str,
293
  ) -> str:
@@ -297,6 +395,7 @@ def generate_quiz_from_history(
297
 
298
  topics_text = "; ".join(course_outline[:8])
299
  weakness_text = "; ".join(weaknesses[-5:]) if weaknesses else "N/A"
 
300
 
301
  messages = [
302
  {"role": "system", "content": CLARE_SYSTEM_PROMPT},
@@ -307,7 +406,8 @@ def generate_quiz_from_history(
307
  "Based on the conversation and course topics, generate **3 questions** "
308
  "(a mix of multiple-choice and short-answer is fine). After listing the "
309
  "questions, provide an answer key at the end under a heading 'Answer Key'. "
310
- "Number the questions Q1, Q2, Q3."
 
311
  ),
312
  },
313
  {
@@ -318,6 +418,10 @@ def generate_quiz_from_history(
318
  "role": "system",
319
  "content": f"Student known difficulties: {weakness_text}",
320
  },
 
 
 
 
321
  {
322
  "role": "user",
323
  "content": (
@@ -354,6 +458,7 @@ def summarize_conversation(
354
  history: List[Tuple[str, str]],
355
  course_outline: List[str],
356
  weaknesses: List[str],
 
357
  model_name: str,
358
  language_preference: str,
359
  ) -> str:
@@ -363,6 +468,7 @@ def summarize_conversation(
363
 
364
  topics_text = "; ".join(course_outline[:8])
365
  weakness_text = "; ".join(weaknesses[-5:]) if weaknesses else "N/A"
 
366
 
367
  messages = [
368
  {"role": "system", "content": CLARE_SYSTEM_PROMPT},
@@ -373,7 +479,8 @@ def summarize_conversation(
373
  "session. Only include knowledge points, definitions, key formulas, "
374
  "examples, and main takeaways. Do **not** include any personal remarks, "
375
  "jokes, or off-topic chat. Write in clear bullet points. This summary "
376
- "should be suitable for the student to paste into their study notes."
 
377
  ),
378
  },
379
  {
@@ -384,6 +491,10 @@ def summarize_conversation(
384
  "role": "system",
385
  "content": f"Student known difficulties: {weakness_text}",
386
  },
 
 
 
 
387
  {
388
  "role": "user",
389
  "content": (
@@ -457,9 +568,10 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant") as demo:
457
  label="File type",
458
  )
459
 
460
- # 状态:课程大纲 + 学生弱项
461
  course_outline_state = gr.State(DEFAULT_COURSE_TOPICS)
462
  weakness_state = gr.State([])
 
463
 
464
  # syllabus 上传后更新课程大纲(仅当类型是 Syllabus 时解析)
465
  def update_outline(file, doc_type_val):
@@ -505,19 +617,21 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant") as demo:
505
  lines=8,
506
  )
507
 
508
- # 主对话逻辑:更新弱项 + 调用 Clare
509
  def respond(
510
  message,
511
  chat_history,
512
  course_outline,
513
  weaknesses,
 
514
  model_name_val,
515
  language_pref_val,
516
  learning_mode_val,
517
  doc_type_val,
518
  ):
519
- # 更新弱项记忆
520
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
 
 
521
  answer, new_history = chat_with_clare(
522
  message=message,
523
  history=chat_history,
@@ -527,8 +641,9 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant") as demo:
527
  doc_type=doc_type_val,
528
  course_outline=course_outline,
529
  weaknesses=weaknesses,
 
530
  )
531
- return "", new_history, weaknesses
532
 
533
  user_input.submit(
534
  respond,
@@ -537,64 +652,101 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant") as demo:
537
  chatbot,
538
  course_outline_state,
539
  weakness_state,
 
540
  model_name,
541
  language_preference,
542
  learning_mode,
543
  doc_type,
544
  ],
545
- [user_input, chatbot, weakness_state],
546
  )
547
 
548
- # 清空对话 & 弱项 & 导出/quiz/summary
549
  def clear_all():
550
- return [], [], "", "", ""
551
 
552
  clear_btn.click(
553
  clear_all,
554
  None,
555
- [chatbot, weakness_state, export_box, quiz_box, summary_box],
556
  queue=False,
557
  )
558
 
559
  # 导出对话按钮
560
- def on_export(chat_history, course_outline, learning_mode_val, weaknesses):
561
- return export_conversation(chat_history, course_outline, learning_mode_val, weaknesses or [])
 
 
 
 
 
 
562
 
563
  export_btn.click(
564
  on_export,
565
- [chatbot, course_outline_state, learning_mode, weakness_state],
566
  [export_box],
567
  )
568
 
569
  # 生成 quiz 按钮
570
- def on_quiz(chat_history, course_outline, weaknesses, model_name_val, language_pref_val):
 
 
 
 
 
 
 
571
  return generate_quiz_from_history(
572
  chat_history,
573
  course_outline,
574
  weaknesses or [],
 
575
  model_name_val,
576
  language_pref_val,
577
  )
578
 
579
  quiz_btn.click(
580
  on_quiz,
581
- [chatbot, course_outline_state, weakness_state, model_name, language_preference],
 
 
 
 
 
 
 
582
  [quiz_box],
583
  )
584
 
585
  # 概念总结按钮
586
- def on_summary(chat_history, course_outline, weaknesses, model_name_val, language_pref_val):
 
 
 
 
 
 
 
587
  return summarize_conversation(
588
  chat_history,
589
  course_outline,
590
  weaknesses or [],
 
591
  model_name_val,
592
  language_pref_val,
593
  )
594
 
595
  summary_btn.click(
596
  on_summary,
597
- [chatbot, course_outline_state, weakness_state, model_name, language_preference],
 
 
 
 
 
 
 
598
  [summary_box],
599
  )
600
 
 
42
  LEARNING_MODE_INSTRUCTIONS = {
43
  "Concept Explainer": (
44
  "Explain concepts step by step. Use clear definitions, key formulas or structures, "
45
+ "and one or two simple examples. Focus on clarity over depth. Regularly check if "
46
+ "the student is following."
47
  ),
48
  "Socratic Tutor": (
49
+ "Use a Socratic style. Ask the student ONE short question at a time, guide them to "
50
+ "reason step by step, and only give full explanations after they try. Prioritize "
51
+ "questions and hints over long lectures."
52
  ),
53
  "Exam Prep / Quiz": (
54
  "Behave like an exam prep coach. Often propose short quiz-style questions "
55
+ "(multiple choice or short answer), then explain the solutions clearly. Emphasize "
56
+ "common traps and how to avoid them."
57
  ),
58
  "Assignment Helper": (
59
+ "Help with assignments WITHOUT giving full final solutions. Clarify requirements, "
60
+ "break tasks into smaller steps, and provide hints, partial examples, or pseudo-code "
61
+ "instead of complete code or final answers. Encourage the student to attempt each "
62
+ "step before revealing more."
63
  ),
64
  "Quick Summary": (
65
  "Provide concise, bullet-point style summaries and cheat-sheet style notes. "
 
135
  "很难",
136
  ]
137
 
138
+ # ---------- 简单“掌握”检测 ----------
139
+ MASTERY_KEYWORDS = [
140
+ "got it",
141
+ "makes sense",
142
+ "now i see",
143
+ "i see",
144
+ "understand now",
145
+ "clear now",
146
+ "easy",
147
+ "no problem",
148
+ "没问题",
149
+ "懂了",
150
+ "明白了",
151
+ "清楚了",
152
+ ]
153
+
154
  def update_weaknesses_from_message(message: str, weaknesses: List[str]) -> List[str]:
155
  lower_msg = message.lower()
156
  if any(k in lower_msg for k in WEAKNESS_KEYWORDS):
 
159
  return weaknesses
160
 
161
 
162
+ def update_cognitive_state_from_message(
163
+ message: str,
164
+ state: Optional[Dict[str, int]],
165
+ ) -> Dict[str, int]:
166
+ """
167
+ 简单认知状态统计:
168
+ - 遇到困惑类关键词 → confusion +1
169
+ - 遇到掌握类关键词 → mastery +1
170
+ """
171
+ if state is None:
172
+ state = {"confusion": 0, "mastery": 0}
173
+
174
+ lower_msg = message.lower()
175
+ if any(k in lower_msg for k in WEAKNESS_KEYWORDS):
176
+ state["confusion"] = state.get("confusion", 0) + 1
177
+ if any(k in lower_msg for k in MASTERY_KEYWORDS):
178
+ state["mastery"] = state.get("mastery", 0) + 1
179
+ return state
180
+
181
+
182
+ def describe_cognitive_state(state: Optional[Dict[str, int]]) -> str:
183
+ if not state:
184
+ return "unknown"
185
+ confusion = state.get("confusion", 0)
186
+ mastery = state.get("mastery", 0)
187
+ if confusion >= 2 and confusion >= mastery + 1:
188
+ return "student shows signs of HIGH cognitive load (often confused)."
189
+ elif mastery >= 2 and mastery >= confusion + 1:
190
+ return "student seems COMFORTABLE; material may be slightly easy."
191
+ else:
192
+ return "mixed or uncertain cognitive state."
193
+
194
+
195
  # ---------- 构建 messages ----------
196
  def build_messages(
197
  user_message: str,
 
201
  doc_type: str,
202
  course_outline: Optional[List[str]],
203
  weaknesses: Optional[List[str]],
204
+ cognitive_state: Optional[Dict[str, int]],
205
  ) -> List[Dict[str, str]]:
206
  messages: List[Dict[str, str]] = [
207
  {"role": "system", "content": CLARE_SYSTEM_PROMPT}
 
257
  }
258
  )
259
 
260
+ # 认知状态提示(动态复杂度调整)
261
+ if cognitive_state:
262
+ confusion = cognitive_state.get("confusion", 0)
263
+ mastery = cognitive_state.get("mastery", 0)
264
+ if confusion >= 2 and confusion >= mastery + 1:
265
+ messages.append(
266
+ {
267
+ "role": "system",
268
+ "content": (
269
+ "The student is currently under HIGH cognitive load. "
270
+ "Use simpler language, shorter steps, and more concrete examples. "
271
+ "Avoid long derivations in a single answer, and check understanding "
272
+ "frequently."
273
+ ),
274
+ }
275
+ )
276
+ elif mastery >= 2 and mastery >= confusion + 1:
277
+ messages.append(
278
+ {
279
+ "role": "system",
280
+ "content": (
281
+ "The student seems comfortable with the material. "
282
+ "You may increase difficulty slightly, introduce deeper follow-up "
283
+ "questions, and connect concepts across topics."
284
+ ),
285
+ }
286
+ )
287
+ else:
288
+ messages.append(
289
+ {
290
+ "role": "system",
291
+ "content": (
292
+ "The student's cognitive state is mixed or uncertain. "
293
+ "Keep explanations clear and moderately paced, and probe for "
294
+ "understanding with short questions."
295
+ ),
296
+ }
297
+ )
298
+
299
  # 语言偏好控制
300
  if language_preference == "English":
301
  messages.append(
 
326
  doc_type: str,
327
  course_outline: Optional[List[str]],
328
  weaknesses: Optional[List[str]],
329
+ cognitive_state: Optional[Dict[str, int]],
330
  ):
331
  try:
332
  messages = build_messages(
 
337
  doc_type=doc_type,
338
  course_outline=course_outline,
339
  weaknesses=weaknesses,
340
+ cognitive_state=cognitive_state,
341
  )
342
  response = client.chat.completions.create(
343
  model=model_name or DEFAULT_MODEL,
 
358
  course_outline: List[str],
359
  learning_mode_val: str,
360
  weaknesses: List[str],
361
+ cognitive_state: Optional[Dict[str, int]],
362
  ) -> str:
363
  lines: List[str] = []
364
  lines.append("# Clare – Conversation Export\n")
365
  lines.append(f"- Learning mode: **{learning_mode_val}**\n")
366
  lines.append("- Course topics (short): " + "; ".join(course_outline[:5]) + "\n")
367
+ lines.append(f"- Cognitive state snapshot: {describe_cognitive_state(cognitive_state)}\n")
368
 
369
  if weaknesses:
370
  lines.append("- Observed student difficulties:\n")
 
385
  history: List[Tuple[str, str]],
386
  course_outline: List[str],
387
  weaknesses: List[str],
388
+ cognitive_state: Optional[Dict[str, int]],
389
  model_name: str,
390
  language_preference: str,
391
  ) -> str:
 
395
 
396
  topics_text = "; ".join(course_outline[:8])
397
  weakness_text = "; ".join(weaknesses[-5:]) if weaknesses else "N/A"
398
+ cog_text = describe_cognitive_state(cognitive_state)
399
 
400
  messages = [
401
  {"role": "system", "content": CLARE_SYSTEM_PROMPT},
 
406
  "Based on the conversation and course topics, generate **3 questions** "
407
  "(a mix of multiple-choice and short-answer is fine). After listing the "
408
  "questions, provide an answer key at the end under a heading 'Answer Key'. "
409
+ "Number the questions Q1, Q2, Q3. Adjust the difficulty according to the "
410
+ "student's cognitive state."
411
  ),
412
  },
413
  {
 
418
  "role": "system",
419
  "content": f"Student known difficulties: {weakness_text}",
420
  },
421
+ {
422
+ "role": "system",
423
+ "content": f"Student cognitive state: {cog_text}",
424
+ },
425
  {
426
  "role": "user",
427
  "content": (
 
458
  history: List[Tuple[str, str]],
459
  course_outline: List[str],
460
  weaknesses: List[str],
461
+ cognitive_state: Optional[Dict[str, int]],
462
  model_name: str,
463
  language_preference: str,
464
  ) -> str:
 
468
 
469
  topics_text = "; ".join(course_outline[:8])
470
  weakness_text = "; ".join(weaknesses[-5:]) if weaknesses else "N/A"
471
+ cog_text = describe_cognitive_state(cognitive_state)
472
 
473
  messages = [
474
  {"role": "system", "content": CLARE_SYSTEM_PROMPT},
 
479
  "session. Only include knowledge points, definitions, key formulas, "
480
  "examples, and main takeaways. Do **not** include any personal remarks, "
481
  "jokes, or off-topic chat. Write in clear bullet points. This summary "
482
+ "should be suitable for the student to paste into their study notes. "
483
+ "Take into account what the student struggled with and their cognitive state."
484
  ),
485
  },
486
  {
 
491
  "role": "system",
492
  "content": f"Student known difficulties: {weakness_text}",
493
  },
494
+ {
495
+ "role": "system",
496
+ "content": f"Student cognitive state: {cog_text}",
497
+ },
498
  {
499
  "role": "user",
500
  "content": (
 
568
  label="File type",
569
  )
570
 
571
+ # 状态:课程大纲 + 学生弱项 + 认知状态
572
  course_outline_state = gr.State(DEFAULT_COURSE_TOPICS)
573
  weakness_state = gr.State([])
574
+ cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
575
 
576
  # syllabus 上传后更新课程大纲(仅当类型是 Syllabus 时解析)
577
  def update_outline(file, doc_type_val):
 
617
  lines=8,
618
  )
619
 
620
+ # 主对话逻辑:更新弱项 + 认知状态 + 调用 Clare
621
  def respond(
622
  message,
623
  chat_history,
624
  course_outline,
625
  weaknesses,
626
+ cognitive_state,
627
  model_name_val,
628
  language_pref_val,
629
  learning_mode_val,
630
  doc_type_val,
631
  ):
 
632
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
633
+ cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
634
+
635
  answer, new_history = chat_with_clare(
636
  message=message,
637
  history=chat_history,
 
641
  doc_type=doc_type_val,
642
  course_outline=course_outline,
643
  weaknesses=weaknesses,
644
+ cognitive_state=cognitive_state,
645
  )
646
+ return "", new_history, weaknesses, cognitive_state
647
 
648
  user_input.submit(
649
  respond,
 
652
  chatbot,
653
  course_outline_state,
654
  weakness_state,
655
+ cognitive_state_state,
656
  model_name,
657
  language_preference,
658
  learning_mode,
659
  doc_type,
660
  ],
661
+ [user_input, chatbot, weakness_state, cognitive_state_state],
662
  )
663
 
664
+ # 清空对话 & 状态 & 导出/quiz/summary
665
  def clear_all():
666
+ return [], [], {"confusion": 0, "mastery": 0}, "", "", ""
667
 
668
  clear_btn.click(
669
  clear_all,
670
  None,
671
+ [chatbot, weakness_state, cognitive_state_state, export_box, quiz_box, summary_box],
672
  queue=False,
673
  )
674
 
675
  # 导出对话按钮
676
+ def on_export(chat_history, course_outline, learning_mode_val, weaknesses, cognitive_state):
677
+ return export_conversation(
678
+ chat_history,
679
+ course_outline,
680
+ learning_mode_val,
681
+ weaknesses or [],
682
+ cognitive_state,
683
+ )
684
 
685
  export_btn.click(
686
  on_export,
687
+ [chatbot, course_outline_state, learning_mode, weakness_state, cognitive_state_state],
688
  [export_box],
689
  )
690
 
691
  # 生成 quiz 按钮
692
+ def on_quiz(
693
+ chat_history,
694
+ course_outline,
695
+ weaknesses,
696
+ cognitive_state,
697
+ model_name_val,
698
+ language_pref_val,
699
+ ):
700
  return generate_quiz_from_history(
701
  chat_history,
702
  course_outline,
703
  weaknesses or [],
704
+ cognitive_state,
705
  model_name_val,
706
  language_pref_val,
707
  )
708
 
709
  quiz_btn.click(
710
  on_quiz,
711
+ [
712
+ chatbot,
713
+ course_outline_state,
714
+ weakness_state,
715
+ cognitive_state_state,
716
+ model_name,
717
+ language_preference,
718
+ ],
719
  [quiz_box],
720
  )
721
 
722
  # 概念总结按钮
723
+ def on_summary(
724
+ chat_history,
725
+ course_outline,
726
+ weaknesses,
727
+ cognitive_state,
728
+ model_name_val,
729
+ language_pref_val,
730
+ ):
731
  return summarize_conversation(
732
  chat_history,
733
  course_outline,
734
  weaknesses or [],
735
+ cognitive_state,
736
  model_name_val,
737
  language_pref_val,
738
  )
739
 
740
  summary_btn.click(
741
  on_summary,
742
+ [
743
+ chatbot,
744
+ course_outline_state,
745
+ weakness_state,
746
+ cognitive_state_state,
747
+ model_name,
748
+ language_preference,
749
+ ],
750
  [summary_box],
751
  )
752