SarahXia0405 commited on
Commit
73ad6fe
·
verified ·
1 Parent(s): 44f4ca4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +233 -583
app.py CHANGED
@@ -1,7 +1,6 @@
1
- # app.py
2
- from typing import List, Dict, Tuple, Optional
3
-
4
  import gradio as gr
 
 
5
 
6
  from config import (
7
  DEFAULT_MODEL,
@@ -27,663 +26,314 @@ from rag_engine import (
27
  )
28
  from syllabus_utils import extract_course_topics_from_file
29
 
30
-
31
- # ================== Assets ==================
32
-
33
  HANBRIDGE_LOGO_PATH = "hanbridge_logo.png"
34
- CLARE_LOGO_PATH = "clare_mascot.png"
35
- CLARE_RUN_PATH = "Clare_Run.png" # 预留:之后可用于 Memory Line 动画等
36
-
37
-
38
- # ================== CSS(只控制 User Guide 样式) ==================
39
-
40
- USER_GUIDE_CSS = """
41
- #user-guide-panel h2 {
42
- font-size: 1rem;
43
- margin-bottom: 0.35rem;
44
- }
45
- #user-guide-panel h3 {
46
- font-size: 0.9rem;
47
- margin-bottom: 0.25rem;
48
- }
49
- #user-guide-panel p,
50
- #user-guide-panel li {
51
- font-size: 0.8rem;
52
  }
53
 
54
- /* 左侧 User Guide 链接按钮 → 蓝色小链接风格 */
55
  #user-guide-links button {
56
- background: transparent !important;
57
- border: none !important;
58
- box-shadow: none !important;
59
- padding: 2px 0 !important;
60
- margin: 1px 0 !important;
61
- text-align: left !important;
62
- color: #004a99 !important;
63
- text-decoration: underline;
64
- font-size: 0.8rem;
65
  }
66
- #user-guide-links button:hover {
67
- background: transparent !important;
68
- color: #002b66 !important;
 
 
 
 
 
 
69
  }
70
  """
71
 
72
-
73
- # ========= Clare User Guide Content =========
74
-
75
  USER_GUIDE_SECTIONS = {
76
- "getting_started": """
77
- ## Getting started
78
-
79
- Welcome to **Clare Your Personalized AI Tutor**.
80
-
81
- Clare is designed to support your learning through:
82
- - Clear explanations of course concepts
83
- - Socratic-style reasoning guidance
84
- - Personalized reinforcement based on learning science
85
- - Course-aligned answers using your uploaded materials
86
- - Micro-quizzes & summaries to strengthen understanding
87
-
88
- **To begin:**
89
- 1. Select your **Learning Mode** on the left
90
- 2. (Optional) Upload your **syllabus / slides / notes** at the top
91
- 3. Ask Clare any question about your course, assignment, or study plan.
92
- """,
93
- "mode_definition": """
94
- ## Mode Definition
95
-
96
- Clare offers different teaching modes to match how you prefer to learn.
97
-
98
- ### Concept Explainer
99
- Clear, structured explanations with examples — ideal for learning new topics.
100
-
101
- ### Socratic Tutor
102
- Clare asks guiding questions instead of giving direct answers.
103
- Helps you build reasoning and problem-solving skills.
104
-
105
- ### Exam Prep / Quiz
106
- Generates short practice questions aligned with your course week.
107
- Useful for self-testing and preparing for exams.
108
-
109
- ### Assignment Helper
110
- Helps you interpret assignment prompts, plan structure, and understand requirements.
111
- ❗ Clare does **not** produce full assignment answers (academic integrity).
112
-
113
- ### Quick Summary
114
- Gives brief summaries of slides, reading materials, or long questions.
115
- """,
116
- "how_clare_works": """
117
- ## How Clare Works
118
-
119
- Clare combines **course context + learning science + AI reasoning** to generate answers.
120
-
121
- Clare uses:
122
- - **Your selected Learning Mode**
123
- Determines tone, depth, and interaction style.
124
-
125
- - **Your uploaded course files**
126
- Syllabus, slides, or papers help align answers with your specific course.
127
-
128
- - **Reinforcement learning cycle**
129
- Answers may prioritize key concepts you’re likely to forget (based on the forgetting curve).
130
-
131
- - **Adaptive explanation depth**
132
- Clare can adjust complexity depending on your previous interactions.
133
-
134
- - **Responsible AI principles**
135
- Avoids harmful output and preserves academic integrity.
136
- """,
137
- "memory_line": """
138
- ## What is Memory Line?
139
-
140
- **Memory Line** is a visualization of your *learning reinforcement cycle*.
141
-
142
- Based on the **forgetting-curve model**, Clare organizes your review topics into:
143
- - **T+0 (Current Week)** – new concepts
144
- - **T+7** – first spaced review
145
- - **T+14** – reinforcement review
146
- - **T+30** – long-term consolidation
147
-
148
- Clare uses these cycles to decide:
149
- - Which concepts to reinforce today
150
- - Which explanations to prioritize
151
- - When to introduce quick review questions
152
-
153
- **Review Now** will generate a small set of concepts that are due for spaced repetition.
154
- """,
155
- "learning_progress": """
156
- ## Learning Progress Report
157
-
158
- The Learning Progress Report highlights:
159
- - **Concepts mastered**
160
- - **Concepts in progress**
161
- - **Concepts due for review**
162
- - Your recent **micro-quiz results**
163
- - Suggested **next-step topics**
164
-
165
- You can download a **summary** at any time to use as study notes.
166
- """,
167
- "how_files": """
168
- ## How Clare Uses Your Files
169
-
170
- Your uploaded syllabus / slides / notes help Clare:
171
-
172
- - Align explanations with your exact course
173
- - Use terminology consistent with your professor
174
- - Improve factual accuracy
175
- - Generate personalized reinforcement content
176
-
177
- 🔒 **Privacy**
178
- - Files are used only within your session
179
- - They are not kept as permanent training data
180
-
181
- Accepted formats: **.docx / .pdf / .pptx**
182
- """,
183
- "micro_quiz": """
184
- ## Micro-Quiz
185
-
186
- The **Micro-Quiz** function provides a:
187
-
188
- - 1-minute self-check
189
- - 1–3 questions based on your recent topics
190
- - Instant feedback
191
-
192
- Micro-quizzes strengthen your memory and are part of Clare’s reinforcement system.
193
- """,
194
- "summarization": """
195
- ## Summarization
196
-
197
- Clare can summarize:
198
-
199
- - Lecture notes
200
- - Uploaded PDFs
201
- - Long conversation threads
202
- - Complex concepts
203
-
204
- Summary styles can include:
205
- - Bullet points
206
- - Key ideas
207
- - Study notes
208
- - Comparison-style highlights.
209
- """,
210
- "export_conversation": """
211
- ## Export Conversation
212
-
213
- You can export your chat session for:
214
-
215
- - Study review
216
- - Exam preparation
217
- - Documentation for tutoring help
218
- - Saving important explanations
219
-
220
- Export format: **Markdown / plain text** (depending on this Space configuration).
221
- """,
222
- "faq": """
223
- ## FAQ
224
-
225
- **Q: Does Clare give assignment answers?**
226
- No. Clare assists with understanding and planning but does **not** generate full solutions.
227
-
228
- **Q: Does Clare replace lectures or TA office hours?**
229
- No. Clare supplements your learning by providing on-demand guidance.
230
-
231
- **Q: Can Clare explain my professor’s slides?**
232
- Yes — upload your slides so Clare can align explanations with your course.
233
-
234
- **Q: What languages does Clare support?**
235
- Currently: English & 简体中文.
236
-
237
- **Q: Why does Clare sometimes ask me questions instead of answering directly?**
238
- You may be in **Socratic Tutor** mode — Clare is guiding your reasoning.
239
- """
240
  }
241
 
242
-
243
  def get_user_guide_section(section_key: str) -> str:
244
- """Return markdown text for a User Guide section."""
245
  return USER_GUIDE_SECTIONS.get(section_key, "Section not found.")
246
 
247
-
248
  # ================== Gradio App ==================
249
 
250
- with gr.Blocks(
251
- title="Clare – Hanbridge AI Teaching Assistant",
252
- css=USER_GUIDE_CSS,
253
- ) as demo:
254
- # ---------- Header using Markdown + <img> ----------
255
- gr.Markdown(
256
- f"""
257
- <div style="display:flex;align-items:center;gap:20px; padding:8px 0;">
258
-
259
- <!-- Clare Mascot -->
260
- <img src="file/{CLARE_LOGO_PATH}" style="height:80px;">
261
 
262
- <!-- Text Block -->
263
- <div style="display:flex;flex-direction:column;">
264
- <div style="font-size:40px;font-weight:700; line-height:1.1;">
265
- Clare
266
- </div>
267
- <div style="font-size:18px;font-weight:600; line-height:1.2;">
268
- Your Personalized AI Tutor
269
- </div>
270
- <div style="font-size:13px;color:#666;">
271
- Personalized guidance, review, and intelligent reinforcement designed for how you learn.
 
 
 
 
272
  </div>
273
  </div>
274
 
275
- <!-- Right-side Hanbridge Logo + text -->
276
- <div style="margin-left:auto; display:flex; flex-direction:column; align-items:flex-end;">
277
- <div style="display:flex;align-items:center;gap:8px;">
278
- <img src="file/{HANBRIDGE_LOGO_PATH}" style="height:40px;">
279
- <div style="font-size:20px;font-weight:700;">Hanbridge University</div>
280
- </div>
281
- <div style="font-size:11px;color:#555; margin-top:4px;">
282
- <span style="font-size:13px;">🎓</span> (Student Name) (Student Email/ID)
283
  </div>
284
  </div>
285
-
286
  </div>
287
  """
288
  )
289
 
290
- # 简短说明
291
- gr.Markdown(
292
- """
293
- - Ask in **English** → Clare answers in English.
294
- - Ask in **Chinese** → Clare can answer in Chinese.
295
- - Use different **learning modes** to change Clare's teaching style.
296
- - Optionally upload your **course syllabus / slides / literature (.docx / .pdf / .pptx)** so Clare stays aligned with your course.
297
- """
298
- )
299
-
300
- # 顶部:模型、语言偏好、学习模式
301
  with gr.Row():
302
- model_name = gr.Textbox(
303
- label="Model name",
304
- value=DEFAULT_MODEL,
305
- info="For example: gpt-4.1-mini, gpt-4.1, gpt-4o, etc.",
306
- )
307
- language_preference = gr.Radio(
308
- choices=["Auto", "English", "中文"],
309
- value="Auto",
310
- label="Preferred answer language",
311
- )
312
- learning_mode = gr.Radio(
313
- choices=LEARNING_MODES,
314
- value="Concept Explainer",
315
- label="Learning mode",
316
- )
 
 
 
 
317
 
318
- # 课程文件上传
319
- with gr.Row():
320
- syllabus_file = gr.File(
321
- label="Upload course file (.docx / .pdf / .pptx)",
322
- file_types=[".docx", ".pdf", ".pptx"],
323
- )
324
- doc_type = gr.Dropdown(
325
- choices=DOC_TYPES,
326
- value="Syllabus",
327
- label="File type",
328
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
- # 状态:课程大纲 + 学生弱项 + 认知状态 + RAG chunks
331
- course_outline_state = gr.State(DEFAULT_COURSE_TOPICS)
332
- weakness_state = gr.State([])
333
- cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
334
- rag_chunks_state = gr.State([]) # Session 级 RAG 向量库
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
- # 上传 syllabus 时更新课程大纲 + RAG chunks
337
  def update_course_and_rag(file, doc_type_val):
338
- """
339
- 上传文件时:
340
- - 如果 doc_type 是 Syllabus,就尝试从文件中解析课程大纲(支持 docx / pdf);
341
- - 否则使用默认大纲;
342
- - 同时无论 doc_type 为何,都用文件内容构建一次 RAG chunks。
343
- """
344
  topics = extract_course_topics_from_file(file, doc_type_val)
345
  rag_chunks = build_rag_chunks_from_file(file, doc_type_val)
346
- return topics, rag_chunks
 
347
 
348
  syllabus_file.change(
349
  fn=update_course_and_rag,
350
  inputs=[syllabus_file, doc_type],
351
- outputs=[course_outline_state, rag_chunks_state],
352
- )
353
-
354
- # 左侧聊天,右侧 Session 状态栏
355
- with gr.Row():
356
- chatbot = gr.Chatbot(
357
- label="Clare Chat",
358
- height=450,
359
- avatar_images=(None, CLARE_LOGO_PATH), # 用户默认头像,助教用小火苗
360
- )
361
- session_status = gr.Markdown(
362
- value=render_session_status(
363
- "Concept Explainer",
364
- [],
365
- {"confusion": 0, "mastery": 0},
366
- ),
367
- label="Session status",
368
- )
369
-
370
- user_input = gr.Textbox(
371
- label="Your question",
372
- placeholder="Ask about a concept, your assignment, or let Clare test you with a quick question...",
373
- )
374
-
375
- with gr.Row():
376
- clear_btn = gr.Button("Reset conversation")
377
- export_btn = gr.Button("Export conversation")
378
- quiz_btn = gr.Button("Generate 3 quiz questions")
379
- summary_btn = gr.Button("Summarize concepts")
380
-
381
- export_box = gr.Textbox(
382
- label="Conversation export (Markdown)",
383
- lines=8,
384
- )
385
- quiz_box = gr.Textbox(
386
- label="Generated quiz (with answer key)",
387
- lines=8,
388
- )
389
- summary_box = gr.Textbox(
390
- label="Concept summary (for study notes)",
391
- lines=8,
392
  )
393
 
394
- # ========== User Guide ==========
395
-
396
- with gr.Accordion("User Guide", open=False):
397
- with gr.Row():
398
- # 左侧“蓝色链接列表”
399
- with gr.Column(scale=1, min_width=180, elem_id="user-guide-links"):
400
- ug_getting_started = gr.Button("Getting started", variant="secondary")
401
- ug_mode_def = gr.Button("Mode Definition", variant="secondary")
402
- ug_how_works = gr.Button("How Clare Works", variant="secondary")
403
- ug_memory_line = gr.Button("What is Memory line", variant="secondary")
404
- ug_learning_progress = gr.Button("Learning Progress Report", variant="secondary")
405
- ug_how_files = gr.Button("How Clare Uses Your Files", variant="secondary")
406
- ug_micro_quiz = gr.Button("Micro-Quiz", variant="secondary")
407
- ug_summarization = gr.Button("Summarization", variant="secondary")
408
- ug_export_conv = gr.Button("Export Conversation", variant="secondary")
409
- ug_faq = gr.Button("FAQ", variant="secondary")
410
-
411
- # 右侧具体内容框
412
- with gr.Column(scale=3):
413
- user_guide_md = gr.Markdown(
414
- value=USER_GUIDE_SECTIONS["getting_started"],
415
- elem_id="user-guide-panel",
416
- )
417
-
418
- # ================== 主对话逻辑 ==================
419
-
420
- def respond(
421
- message,
422
- chat_history,
423
- course_outline,
424
- weaknesses,
425
- cognitive_state,
426
- rag_chunks,
427
- model_name_val,
428
- language_pref_val,
429
- learning_mode_val,
430
- doc_type_val,
431
- ):
432
- # 1) 决定本轮语言(Auto / English / 中文)
433
- resolved_lang = detect_language(message or "", language_pref_val)
434
-
435
- # 2) 空输入防护
436
  if not message or not message.strip():
437
- empty_msg = get_empty_input_prompt(resolved_lang)
438
- new_history = chat_history + [("", empty_msg)]
439
- status_text = render_session_status(
440
- learning_mode_val,
441
- weaknesses or [],
442
- cognitive_state,
443
- )
444
- return "", new_history, weaknesses, cognitive_state, status_text
445
 
446
- # 3) 更新弱项 & 认知状态
447
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
448
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
449
-
450
- # 4) Same Question Check(session 内复用答案)
451
- dup = find_similar_past_question(message, chat_history)
452
- if dup is not None:
453
- past_q, past_a, sim = dup
454
- prefix_en = (
455
- "I noticed this question is very similar to one you asked earlier, "
456
- "so I'm showing the previous explanation again. "
457
- "If there's a specific part that's still unclear, tell me and I can "
458
- "re-explain it in a different way.\n\n"
459
- "**Earlier answer:**\n"
460
- )
461
- prefix_zh = (
462
- "我注意到你现在的问题和之前问过的非常相似,"
463
- "所以我先把当时的回答再展示一次。"
464
- "���果还有具体不清楚的地方,可以告诉我,我会换一种方式解释。\n\n"
465
- "**之前的回答:**\n"
466
- )
467
- if resolved_lang == "中文":
468
- answer = prefix_zh + past_a
469
- else:
470
- answer = prefix_en + past_a
471
-
472
- new_history = chat_history + [(message, answer)]
473
- status_text = render_session_status(
474
- learning_mode_val,
475
- weaknesses,
476
- cognitive_state,
477
- )
478
- return "", new_history, weaknesses, cognitive_state, status_text
479
-
480
- # 5) RAG:基于上传文档做检索
481
  rag_context = retrieve_relevant_chunks(message, rag_chunks or [])
482
 
483
- # 6) 正常调用 Clare(带上 RAG context)
484
  answer, new_history = chat_with_clare(
485
  message=message,
486
  history=chat_history,
487
- model_name=model_name_val,
488
  language_preference=resolved_lang,
489
- learning_mode=learning_mode_val,
490
  doc_type=doc_type_val,
491
  course_outline=course_outline,
492
  weaknesses=weaknesses,
493
  cognitive_state=cognitive_state,
494
- rag_context=rag_context,
495
- )
496
-
497
- # RAG 标记
498
- if rag_context:
499
- # 根据语言给不同提示
500
- if resolved_lang == "中文":
501
- prefix = "(本次回答参考了你上传的课程文档内容。)\n\n"
502
- else:
503
- prefix = "(This answer used excerpts from your uploaded course document.)\n\n"
504
-
505
- tagged_answer = prefix + answer
506
- # 更新最后一条记录里的 assistant 内容
507
- if new_history:
508
- new_history[-1] = (message, tagged_answer)
509
- answer = tagged_answer
510
-
511
- status_text = render_session_status(
512
- learning_mode_val,
513
- weaknesses,
514
- cognitive_state,
515
  )
516
- return "", new_history, weaknesses, cognitive_state, status_text
 
 
517
 
518
  user_input.submit(
519
  respond,
520
- [
521
- user_input,
522
- chatbot,
523
- course_outline_state,
524
- weakness_state,
525
- cognitive_state_state,
526
- rag_chunks_state,
527
- model_name,
528
- language_preference,
529
- learning_mode,
530
- doc_type,
531
- ],
532
- [user_input, chatbot, weakness_state, cognitive_state_state, session_status],
533
- )
534
-
535
- # 清空对话 & 状态
536
- def clear_all():
537
- empty_state = {"confusion": 0, "mastery": 0}
538
- status_text = render_session_status("Concept Explainer", [], empty_state)
539
- return (
540
- [], # chatbot
541
- [], # weaknesses
542
- empty_state, # cognitive_state
543
- [], # rag_chunks
544
- "", # export_box
545
- "", # quiz_box
546
- "", # summary_box
547
- status_text, # session_status
548
- )
549
-
550
- clear_btn.click(
551
- clear_all,
552
- None,
553
- [
554
- chatbot,
555
- weakness_state,
556
- cognitive_state_state,
557
- rag_chunks_state,
558
- export_box,
559
- quiz_box,
560
- summary_box,
561
- session_status,
562
- ],
563
- queue=False,
564
  )
565
 
566
- # 导出对话
567
- def on_export(chat_history, course_outline, learning_mode_val, weaknesses, cognitive_state):
568
- return export_conversation(
569
- chat_history,
570
- course_outline,
571
- learning_mode_val,
572
- weaknesses or [],
573
- cognitive_state,
574
- )
575
 
576
  export_btn.click(
577
  on_export,
578
  [chatbot, course_outline_state, learning_mode, weakness_state, cognitive_state_state],
579
- [export_box],
580
  )
581
 
582
- # 生成 quiz
583
- def on_quiz(
584
- chat_history,
585
- course_outline,
586
- weaknesses,
587
- cognitive_state,
588
- model_name_val,
589
- language_pref_val,
590
- ):
591
- return generate_quiz_from_history(
592
- chat_history,
593
- course_outline,
594
- weaknesses or [],
595
- cognitive_state,
596
- model_name_val,
597
- language_pref_val,
598
- )
599
 
600
  quiz_btn.click(
601
  on_quiz,
602
- [
603
- chatbot,
604
- course_outline_state,
605
- weakness_state,
606
- cognitive_state_state,
607
- model_name,
608
- language_preference,
609
- ],
610
- [quiz_box],
611
  )
612
 
613
- # 概念总结
614
- def on_summary(
615
- chat_history,
616
- course_outline,
617
- weaknesses,
618
- cognitive_state,
619
- model_name_val,
620
- language_pref_val,
621
- ):
622
- return summarize_conversation(
623
- chat_history,
624
- course_outline,
625
- weaknesses or [],
626
- cognitive_state,
627
- model_name_val,
628
- language_pref_val,
629
- )
630
 
631
  summary_btn.click(
632
  on_summary,
633
- [
634
- chatbot,
635
- course_outline_state,
636
- weakness_state,
637
- cognitive_state_state,
638
- model_name,
639
- language_preference,
640
- ],
641
- [summary_box],
642
  )
643
 
644
- # ========== User Guide button bindings ==========
 
 
 
645
 
646
- ug_getting_started.click(
647
- lambda: get_user_guide_section("getting_started"),
648
- outputs=user_guide_md,
649
- )
650
- ug_mode_def.click(
651
- lambda: get_user_guide_section("mode_definition"),
652
- outputs=user_guide_md,
653
- )
654
- ug_how_works.click(
655
- lambda: get_user_guide_section("how_clare_works"),
656
- outputs=user_guide_md,
657
- )
658
- ug_memory_line.click(
659
- lambda: get_user_guide_section("memory_line"),
660
- outputs=user_guide_md,
661
- )
662
- ug_learning_progress.click(
663
- lambda: get_user_guide_section("learning_progress"),
664
- outputs=user_guide_md,
665
- )
666
- ug_how_files.click(
667
- lambda: get_user_guide_section("how_files"),
668
- outputs=user_guide_md,
669
- )
670
- ug_micro_quiz.click(
671
- lambda: get_user_guide_section("micro_quiz"),
672
- outputs=user_guide_md,
673
- )
674
- ug_summarization.click(
675
- lambda: get_user_guide_section("summarization"),
676
- outputs=user_guide_md,
677
- )
678
- ug_export_conv.click(
679
- lambda: get_user_guide_section("export_conversation"),
680
- outputs=user_guide_md,
681
- )
682
- ug_faq.click(
683
- lambda: get_user_guide_section("faq"),
684
- outputs=user_guide_md,
685
  )
 
 
 
686
 
 
 
 
 
687
 
688
  if __name__ == "__main__":
689
- demo.launch()
 
 
 
 
 
1
  import gradio as gr
2
+ import os
3
+ from typing import List, Dict, Tuple, Optional
4
 
5
  from config import (
6
  DEFAULT_MODEL,
 
26
  )
27
  from syllabus_utils import extract_course_topics_from_file
28
 
29
+ # ================== Assets (图片路径配置) ==================
30
+ # 根据你的截图,文件名大小写必须严格一致
 
31
  HANBRIDGE_LOGO_PATH = "hanbridge_logo.png"
32
+ CLARE_LOGO_PATH = "clare_mascot.png"
33
+ CLARE_RUN_PATH = "Clare_Run.png" # 注意:你的截图里这个文件首字母是大写的
34
+
35
+ # ================== CSS (样式调整) ==================
36
+ CUSTOM_CSS = """
37
+ /* 顶部 Header 容器样式 */
38
+ .header-container {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: space-between;
42
+ padding: 10px 20px;
43
+ background-color: #ffffff;
44
+ border-bottom: 2px solid #f3f4f6;
45
+ margin-bottom: 15px;
 
 
 
 
46
  }
47
 
48
+ /* User Guide 链接样式 */
49
  #user-guide-links button {
50
+ background: transparent !important; border: none !important; box-shadow: none !important;
51
+ padding: 2px 0 !important; margin: 1px 0 !important; text-align: left !important;
52
+ color: #004a99 !important; text-decoration: underline; font-size: 0.8rem;
 
 
 
 
 
 
53
  }
54
+ #user-guide-links button:hover { color: #002b66 !important; }
55
+
56
+ /* Memory Line 区域样式 */
57
+ .memory-line-box {
58
+ border: 1px solid #e5e7eb;
59
+ padding: 12px;
60
+ border-radius: 8px;
61
+ background-color: #f9fafb;
62
+ height: 100%;
63
  }
64
  """
65
 
66
+ # ========= Clare User Guide Content (保持原样) =========
 
 
67
  USER_GUIDE_SECTIONS = {
68
+ "getting_started": "## Getting started\n\nWelcome to **Clare**...",
69
+ "mode_definition": "## Mode Definition\n\nClare offers different teaching modes...",
70
+ "how_clare_works": "## How Clare Works\n\nClare combines course context...",
71
+ "memory_line": "## What is Memory Line?\n\nMemory Line is a visualization...",
72
+ "learning_progress": "## Learning Progress Report\n\nThe Learning Progress Report highlights...",
73
+ "how_files": "## How Clare Uses Your Files\n\nYour uploaded syllabus / slides...",
74
+ "micro_quiz": "## Micro-Quiz\n\nThe Micro-Quiz function provides...",
75
+ "summarization": "## Summarization\n\nClare can summarize...",
76
+ "export_conversation": "## Export Conversation\n\nYou can export your chat session...",
77
+ "faq": "## FAQ\n\n**Q: Does Clare give assignment answers?**..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
 
 
80
  def get_user_guide_section(section_key: str) -> str:
 
81
  return USER_GUIDE_SECTIONS.get(section_key, "Section not found.")
82
 
 
83
  # ================== Gradio App ==================
84
 
85
+ # 【重要修正】:这里不要放 allowed_paths,否则会报错
86
+ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS) as demo:
87
+
88
+ # 状态变量
89
+ course_outline_state = gr.State(DEFAULT_COURSE_TOPICS)
90
+ weakness_state = gr.State([])
91
+ cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
92
+ rag_chunks_state = gr.State([])
 
 
 
93
 
94
+ # ============ 1. 顶部 Header (全宽布局) ============
95
+ gr.HTML(
96
+ f"""
97
+ <div class="header-container">
98
+ <div style="display:flex; align-items:center; gap: 20px;">
99
+ <img src="/file/{CLARE_LOGO_PATH}" style="height: 75px; object-fit: contain;">
100
+ <div style="display:flex; flex-direction:column;">
101
+ <div style="font-size: 32px; font-weight: 800; line-height: 1.1; color: #000;">
102
+ Clare
103
+ <span style="font-size: 18px; font-weight: 600; margin-left: 10px;">Your Personalized AI Tutor</span>
104
+ </div>
105
+ <div style="font-size: 14px; font-style: italic; color: #333; margin-top: 4px;">
106
+ Personalized guidance, review, and intelligent reinforcement - designed for how you learn
107
+ </div>
108
  </div>
109
  </div>
110
 
111
+ <div style="text-align: right;">
112
+ <img src="/file/{HANBRIDGE_LOGO_PATH}" style="height: 55px; object-fit: contain; margin-bottom: 5px;">
113
+ <div style="font-size: 12px; color: #666;">
114
+ 🎓 (Student Name) (Student Email/ID)
 
 
 
 
115
  </div>
116
  </div>
 
117
  </div>
118
  """
119
  )
120
 
121
+ # ============ 2. 主体分栏布局:左(1) - 中(3) - 右(1) ============
 
 
 
 
 
 
 
 
 
 
122
  with gr.Row():
123
+
124
+ # ============ [左侧] 控制面板 Sidebar ============
125
+ with gr.Column(scale=1, min_width=200):
126
+ clear_btn = gr.Button("Reset Conversation", variant="stop")
127
+
128
+ gr.Markdown("### Model Settings")
129
+ model_name = gr.Textbox(label="Model", value=DEFAULT_MODEL, lines=1)
130
+ language_preference = gr.Radio(
131
+ choices=["Auto", "English", "简体中文"],
132
+ value="Auto",
133
+ label="Language"
134
+ )
135
+
136
+ learning_mode = gr.Radio(
137
+ choices=LEARNING_MODES,
138
+ value="Concept Explainer",
139
+ label="Learning Mode",
140
+ info="Select style"
141
+ )
142
 
143
+ with gr.Accordion("User Guide", open=False):
144
+ with gr.Column(elem_id="user-guide-links"):
145
+ ug_getting_started = gr.Button("Getting started")
146
+ ug_mode_def = gr.Button("Mode Definition")
147
+ ug_how_works = gr.Button("How Clare Works")
148
+ ug_memory_line = gr.Button("What is Memory Line")
149
+
150
+ with gr.Group():
151
+ gr.Button("System Settings", variant="secondary")
152
+ gr.Button("Log Out", variant="secondary")
153
+
154
+ # ============ [中间] 主内容区 Center Main ============
155
+ with gr.Column(scale=3):
156
+
157
+ # 上半部分:上传区(左) + Memory Line(右) 并排
158
+ with gr.Row():
159
+ # --- Upload File ---
160
+ with gr.Column(scale=1):
161
+ syllabus_file = gr.File(
162
+ label="Upload file (.docx/.pdf/.pptx)",
163
+ file_types=[".docx", ".pdf", ".pptx"],
164
+ file_count="single",
165
+ height=100
166
+ )
167
+ doc_type = gr.Dropdown(
168
+ choices=DOC_TYPES,
169
+ value="Syllabus",
170
+ label="File type",
171
+ container=False
172
+ )
173
+
174
+ # --- Memory Line (包含 CLARE_RUN_PATH) ---
175
+ with gr.Column(scale=1):
176
+ # 使用 HTML 渲染带图片的进度条,src="/file/..." 是关键
177
+ gr.HTML(
178
+ f"""
179
+ <div class="memory-line-box">
180
+ <div style="display:flex; justify-content:space-between; font-size:14px; margin-bottom:5px; font-weight:bold;">
181
+ <span>Memory Line <span style="font-weight:normal; color:#999; cursor:help;" title="Visualizes your spaced repetition progress">?</span></span>
182
+ </div>
183
+
184
+ <div style="position: relative; height: 35px; margin-top: 15px; margin-bottom: 5px;">
185
+ <div style="position: absolute; bottom: 5px; left: 0; width: 100%; height: 8px; background-color: #e5e7eb; border-radius: 4px;"></div>
186
+ <div style="position: absolute; bottom: 5px; left: 0; width: 40%; height: 8px; background-color: #8B1A1A; border-radius: 4px 0 0 4px;"></div>
187
+ <img src="/file/{CLARE_RUN_PATH}" style="position: absolute; left: 36%; bottom: 8px; height: 35px; z-index: 10;">
188
+ </div>
189
+
190
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-top:8px;">
191
+ <div style="font-size: 12px; color: #666;">Next Review: T+7</div>
192
+ <div style="font-size: 12px; color: #004a99; text-decoration:underline; cursor:pointer;">
193
+ Learning Progress Report ⬇️
194
+ </div>
195
+ </div>
196
+ </div>
197
+ """
198
+ )
199
+ # Review 按钮放在 HTML 下方
200
+ review_btn = gr.Button("Review Now", size="sm", variant="primary")
201
+ session_status = gr.Markdown(visible=False)
202
+
203
+ # --- Chat Interface ---
204
+ gr.Markdown(
205
+ """
206
+ <div style="background-color:#f9fafb; padding:10px; border-radius:5px; margin-top:10px; font-size:0.9em; color:#555;">
207
+ ✦ <b>Instruction:</b> Use different learning modes to change Clare's teaching style.<br>
208
+ ✦ <b>Example:</b> "Why does context length matter?" or "How does RAG reduce hallucination?"
209
+ </div>
210
+ """
211
+ )
212
 
213
+ chatbot = gr.Chatbot(
214
+ label="",
215
+ height=450,
216
+ avatar_images=(None, CLARE_LOGO_PATH),
217
+ show_label=False,
218
+ bubble_full_width=False
219
+ )
220
+
221
+ user_input = gr.Textbox(
222
+ label="Your Input",
223
+ placeholder="Ask about a concept, your assignment, or let Clare test you...",
224
+ show_label=False,
225
+ container=True,
226
+ autofocus=True
227
+ )
228
+
229
+ # ============ [右侧] 功能栏 Right Sidebar ============
230
+ with gr.Column(scale=1, min_width=180):
231
+ gr.Markdown("### Actions")
232
+ export_btn = gr.Button("Export Conversation", size="sm")
233
+ quiz_btn = gr.Button("Let's Try (Micro-Quiz)", size="sm")
234
+ summary_btn = gr.Button("Summarization", size="sm")
235
+
236
+ gr.Markdown("### Results")
237
+ result_display = gr.Textbox(
238
+ label="Generated Content",
239
+ lines=15,
240
+ placeholder="Results from Export, Quiz, or Summary will appear here...",
241
+ show_label=False
242
+ )
243
+
244
+ # ================== Logic Bindings ==================
245
 
 
246
  def update_course_and_rag(file, doc_type_val):
 
 
 
 
 
 
247
  topics = extract_course_topics_from_file(file, doc_type_val)
248
  rag_chunks = build_rag_chunks_from_file(file, doc_type_val)
249
+ status_md = f"✅ **File Loaded:** {doc_type_val}\n\nAnalyzing content for Memory Line..."
250
+ return topics, rag_chunks, status_md
251
 
252
  syllabus_file.change(
253
  fn=update_course_and_rag,
254
  inputs=[syllabus_file, doc_type],
255
+ outputs=[course_outline_state, rag_chunks_state, session_status],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  )
257
 
258
+ def respond(message, chat_history, course_outline, weaknesses, cognitive_state, rag_chunks, model, lang, mode, doc_type_val):
259
+ resolved_lang = detect_language(message or "", lang)
260
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  if not message or not message.strip():
262
+ return "", chat_history, weaknesses, cognitive_state, session_status.value
 
 
 
 
 
 
 
263
 
 
264
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
265
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  rag_context = retrieve_relevant_chunks(message, rag_chunks or [])
267
 
 
268
  answer, new_history = chat_with_clare(
269
  message=message,
270
  history=chat_history,
271
+ model_name=model,
272
  language_preference=resolved_lang,
273
+ learning_mode=mode,
274
  doc_type=doc_type_val,
275
  course_outline=course_outline,
276
  weaknesses=weaknesses,
277
  cognitive_state=cognitive_state,
278
+ rag_context=rag_context
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  )
280
+
281
+ new_status = render_session_status(mode, weaknesses, cognitive_state)
282
+ return "", new_history, weaknesses, cognitive_state, new_status
283
 
284
  user_input.submit(
285
  respond,
286
+ [user_input, chatbot, course_outline_state, weakness_state, cognitive_state_state, rag_chunks_state, model_name, language_preference, learning_mode, doc_type],
287
+ [user_input, chatbot, weakness_state, cognitive_state_state, session_status]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  )
289
 
290
+ def on_export(chat_history, course_outline, mode, weaknesses, cognitive_state):
291
+ return export_conversation(chat_history, course_outline, mode, weaknesses, cognitive_state)
 
 
 
 
 
 
 
292
 
293
  export_btn.click(
294
  on_export,
295
  [chatbot, course_outline_state, learning_mode, weakness_state, cognitive_state_state],
296
+ [result_display]
297
  )
298
 
299
+ def on_quiz(chat_history, course_outline, weaknesses, cognitive_state, model, lang):
300
+ return generate_quiz_from_history(chat_history, course_outline, weaknesses, cognitive_state, model, lang)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
  quiz_btn.click(
303
  on_quiz,
304
+ [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference],
305
+ [result_display]
 
 
 
 
 
 
 
306
  )
307
 
308
+ def on_summary(chat_history, course_outline, weaknesses, cognitive_state, model, lang):
309
+ return summarize_conversation(chat_history, course_outline, weaknesses, cognitive_state, model, lang)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
  summary_btn.click(
312
  on_summary,
313
+ [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference],
314
+ [result_display]
 
 
 
 
 
 
 
315
  )
316
 
317
+ def clear_all():
318
+ empty_state = {"confusion": 0, "mastery": 0}
319
+ default_status = render_session_status("Concept Explainer", [], empty_state)
320
+ return [], [], empty_state, [], "", default_status
321
 
322
+ clear_btn.click(
323
+ clear_all,
324
+ None,
325
+ [chatbot, weakness_state, cognitive_state_state, rag_chunks_state, result_display, session_status],
326
+ queue=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  )
328
+
329
+ def show_guide(section):
330
+ return get_user_guide_section(section)
331
 
332
+ ug_getting_started.click(lambda: show_guide("getting_started"), outputs=result_display)
333
+ ug_mode_def.click(lambda: show_guide("mode_definition"), outputs=result_display)
334
+ ug_how_works.click(lambda: show_guide("how_clare_works"), outputs=result_display)
335
+ ug_memory_line.click(lambda: show_guide("memory_line"), outputs=result_display)
336
 
337
  if __name__ == "__main__":
338
+ # 【修正完成】:allowed_paths 必须放在这里
339
+ demo.launch(allowed_paths=["."])