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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +582 -232
app.py CHANGED
@@ -1,7 +1,8 @@
1
- import gradio as gr
2
- import os
3
  from typing import List, Dict, Tuple, Optional
4
 
 
 
5
  from config import (
6
  DEFAULT_MODEL,
7
  DEFAULT_COURSE_TOPICS,
@@ -26,314 +27,663 @@ from rag_engine import (
26
  )
27
  from syllabus_utils import extract_course_topics_from_file
28
 
29
- # ================== Assets (图片路径配置) ==================
30
- # 确保文件名与你上传到 Hugging Face Space 的文件名完全一致
 
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 渲染带图片的进度条
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 必须放在 launch()
339
- demo.launch(allowed_paths=["."])
 
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,
8
  DEFAULT_COURSE_TOPICS,
 
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()