SarahXia0405 commited on
Commit
3a40f53
·
verified ·
1 Parent(s): 8ca5d39

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +268 -161
app.py CHANGED
@@ -32,14 +32,12 @@ HANBRIDGE_LOGO_PATH = "hanbridge_logo.png"
32
  CLARE_LOGO_PATH = "clare_mascot.png"
33
  CLARE_RUN_PATH = "Clare_Run.png"
34
 
35
- # ================== 【核心修改】Base64 图片转换函数 ==================
36
  def image_to_base64(image_path):
37
- """将本地图片转换为 Base64 字符串,直接嵌入 HTML,避免路径错误"""
38
  if not os.path.exists(image_path):
39
- return "" # 如果文件不存在,返回空
40
  with open(image_path, "rb") as img_file:
41
  encoded_string = base64.b64encode(img_file.read()).decode('utf-8')
42
- # 根据文件扩展名决定 MIME type
43
  if image_path.lower().endswith(".png"):
44
  mime = "image/png"
45
  elif image_path.lower().endswith(".jpg") or image_path.lower().endswith(".jpeg"):
@@ -48,65 +46,192 @@ def image_to_base64(image_path):
48
  mime = "image/png"
49
  return f"data:{mime};base64,{encoded_string}"
50
 
51
- # ================== CSS (样式调整) ==================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  CUSTOM_CSS = """
53
- /* 顶部 Header 容器样式 */
54
  .header-container {
55
- display: flex;
56
- align-items: center;
57
- justify-content: space-between;
58
- padding: 10px 20px;
59
- background-color: #ffffff;
60
- border-bottom: 2px solid #f3f4f6;
61
- margin-bottom: 15px;
62
  }
63
 
64
- /* User Guide 链接样式 */
65
- #user-guide-links button {
66
- background: transparent !important; border: none !important; box-shadow: none !important;
67
- padding: 2px 0 !important; margin: 1px 0 !important; text-align: left !important;
68
- color: #004a99 !important; text-decoration: underline; font-size: 0.8rem;
 
 
 
69
  }
70
- #user-guide-links button:hover { color: #002b66 !important; }
71
 
72
- /* Memory Line 区域样式 */
73
  .memory-line-box {
74
- border: 1px solid #e5e7eb;
75
- padding: 12px;
76
- border-radius: 8px;
77
- background-color: #f9fafb;
78
- height: 100%;
79
- }
80
- """
81
-
82
- # ========= Clare User Guide Content (保持原样) =========
83
- USER_GUIDE_SECTIONS = {
84
- "getting_started": "## Getting started\n\nWelcome to **Clare**...",
85
- "mode_definition": "## Mode Definition\n\nClare offers different teaching modes...",
86
- "how_clare_works": "## How Clare Works\n\nClare combines course context...",
87
- "memory_line": "## What is Memory Line?\n\nMemory Line is a visualization...",
88
- "learning_progress": "## Learning Progress Report\n\nThe Learning Progress Report highlights...",
89
- "how_files": "## How Clare Uses Your Files\n\nYour uploaded syllabus / slides...",
90
- "micro_quiz": "## Micro-Quiz\n\nThe Micro-Quiz function provides...",
91
- "summarization": "## Summarization\n\nClare can summarize...",
92
- "export_conversation": "## Export Conversation\n\nYou can export your chat session...",
93
- "faq": "## FAQ\n\n**Q: Does Clare give assignment answers?**..."
94
  }
95
 
96
- def get_user_guide_section(section_key: str) -> str:
97
- return USER_GUIDE_SECTIONS.get(section_key, "Section not found.")
 
98
 
99
  # ================== Gradio App ==================
100
 
101
  with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS) as demo:
102
 
103
- # 状态变量
104
  course_outline_state = gr.State(DEFAULT_COURSE_TOPICS)
105
  weakness_state = gr.State([])
106
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
107
  rag_chunks_state = gr.State([])
108
 
109
- # ============ 1. 顶部 Header (使用 Base64 嵌入图片) ============
110
  gr.HTML(
111
  f"""
112
  <div class="header-container">
@@ -122,7 +247,6 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
122
  </div>
123
  </div>
124
  </div>
125
-
126
  <div style="text-align: right;">
127
  <img src="{image_to_base64(HANBRIDGE_LOGO_PATH)}" style="height: 55px; object-fit: contain; margin-bottom: 5px;">
128
  <div style="font-size: 12px; color: #666;">
@@ -133,13 +257,14 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
133
  """
134
  )
135
 
136
- # ============ 2. 主体分栏布局 ============
137
  with gr.Row():
138
 
139
- # ============ [左侧] 控制面板 Sidebar ============
140
  with gr.Column(scale=1, min_width=200):
141
  clear_btn = gr.Button("Reset Conversation", variant="stop")
142
 
 
143
  gr.Markdown("### Model Settings")
144
  model_name = gr.Textbox(label="Model", value=DEFAULT_MODEL, lines=1)
145
  language_preference = gr.Radio(
@@ -148,36 +273,72 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
148
  label="Language"
149
  )
150
 
 
 
 
 
 
151
  learning_mode = gr.Radio(
152
  choices=LEARNING_MODES,
153
  value="Concept Explainer",
154
- label="Learning Mode",
155
- info="Select style"
 
156
  )
157
 
158
- with gr.Accordion("User Guide", open=False):
159
- with gr.Column(elem_id="user-guide-links"):
160
- ug_getting_started = gr.Button("Getting started")
161
- ug_mode_def = gr.Button("Mode Definition")
162
- ug_how_works = gr.Button("How Clare Works")
163
- ug_memory_line = gr.Button("What is Memory Line")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  with gr.Group():
166
  gr.Button("System Settings", variant="secondary")
167
  gr.Button("Log Out", variant="secondary")
168
 
169
- # ============ [中间] 主内容区 Center Main ============
170
  with gr.Column(scale=3):
171
 
172
- # 上半部分:上传区 + Memory Line
173
  with gr.Row():
174
- # --- Upload File ---
175
  with gr.Column(scale=1):
 
 
 
 
176
  syllabus_file = gr.File(
177
- label="Upload file (.docx/.pdf/.pptx)",
178
  file_types=[".docx", ".pdf", ".pptx"],
179
  file_count="single",
180
- height=100
 
181
  )
182
  doc_type = gr.Dropdown(
183
  choices=DOC_TYPES,
@@ -186,34 +347,33 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
186
  container=False
187
  )
188
 
189
- # --- Memory Line (使用 Base64 嵌入奔跑图片) ---
190
  with gr.Column(scale=1):
191
  gr.HTML(
192
  f"""
193
  <div class="memory-line-box">
194
- <div style="display:flex; justify-content:space-between; font-size:14px; margin-bottom:5px; font-weight:bold;">
195
- <span>Memory Line <span style="font-weight:normal; color:#999; cursor:help;" title="Visualizes your spaced repetition progress">?</span></span>
196
- </div>
197
-
198
  <div style="position: relative; height: 35px; margin-top: 15px; margin-bottom: 5px;">
199
  <div style="position: absolute; bottom: 5px; left: 0; width: 100%; height: 8px; background-color: #e5e7eb; border-radius: 4px;"></div>
200
  <div style="position: absolute; bottom: 5px; left: 0; width: 40%; height: 8px; background-color: #8B1A1A; border-radius: 4px 0 0 4px;"></div>
201
  <img src="{image_to_base64(CLARE_RUN_PATH)}" style="position: absolute; left: 36%; bottom: 8px; height: 35px; z-index: 10;">
202
  </div>
203
-
204
  <div style="display:flex; justify-content:space-between; align-items:center; margin-top:8px;">
205
  <div style="font-size: 12px; color: #666;">Next Review: T+7</div>
206
- <div style="font-size: 12px; color: #004a99; text-decoration:underline; cursor:pointer;">
207
- Learning Progress Report ⬇️
208
- </div>
209
  </div>
210
  </div>
211
  """
212
  )
213
- review_btn = gr.Button("Review Now", size="sm", variant="primary")
 
 
 
214
  session_status = gr.Markdown(visible=False)
215
 
216
- # --- Chat Interface ---
217
  gr.Markdown(
218
  """
219
  <div style="background-color:#f9fafb; padding:10px; border-radius:5px; margin-top:10px; font-size:0.9em; color:#555;">
@@ -222,134 +382,81 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
222
  </div>
223
  """
224
  )
225
-
226
  chatbot = gr.Chatbot(
227
- label="",
228
- height=450,
229
- # 聊天框内的头像也可以使用 Base64,但 Gradio 的 avatar_images
230
- # 通常直接接受路径即可(因为它内部会处理)。
231
- # 如果这里头像也不显示,请告诉我,我再改这里。目前保持路径。
232
  avatar_images=(None, CLARE_LOGO_PATH),
233
- show_label=False,
234
- bubble_full_width=False
235
  )
236
-
237
  user_input = gr.Textbox(
238
- label="Your Input",
239
- placeholder="Ask about a concept, your assignment, or let Clare test you...",
240
- show_label=False,
241
- container=True,
242
- autofocus=True
243
  )
244
 
245
- # ============ [右侧] 功能栏 Right Sidebar ============
246
  with gr.Column(scale=1, min_width=180):
247
  gr.Markdown("### Actions")
248
- export_btn = gr.Button("Export Conversation", size="sm")
249
- quiz_btn = gr.Button("Let's Try (Micro-Quiz)", size="sm")
250
- summary_btn = gr.Button("Summarization", size="sm")
 
 
 
 
 
 
 
 
 
251
 
252
  gr.Markdown("### Results")
253
  result_display = gr.Textbox(
254
- label="Generated Content",
255
- lines=15,
256
  placeholder="Results from Export, Quiz, or Summary will appear here...",
257
  show_label=False
258
  )
259
 
260
- # ================== Logic Bindings (逻辑部分保持不变) ==================
 
 
 
 
 
 
 
261
 
 
262
  def update_course_and_rag(file, doc_type_val):
263
  topics = extract_course_topics_from_file(file, doc_type_val)
264
  rag_chunks = build_rag_chunks_from_file(file, doc_type_val)
265
  status_md = f"✅ **File Loaded:** {doc_type_val}\n\nAnalyzing content for Memory Line..."
266
  return topics, rag_chunks, status_md
267
 
268
- syllabus_file.change(
269
- fn=update_course_and_rag,
270
- inputs=[syllabus_file, doc_type],
271
- outputs=[course_outline_state, rag_chunks_state, session_status],
272
- )
273
 
274
  def respond(message, chat_history, course_outline, weaknesses, cognitive_state, rag_chunks, model, lang, mode, doc_type_val):
275
  resolved_lang = detect_language(message or "", lang)
276
-
277
  if not message or not message.strip():
278
  return "", chat_history, weaknesses, cognitive_state, session_status.value
279
-
280
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
281
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
282
  rag_context = retrieve_relevant_chunks(message, rag_chunks or [])
283
-
284
- answer, new_history = chat_with_clare(
285
- message=message,
286
- history=chat_history,
287
- model_name=model,
288
- language_preference=resolved_lang,
289
- learning_mode=mode,
290
- doc_type=doc_type_val,
291
- course_outline=course_outline,
292
- weaknesses=weaknesses,
293
- cognitive_state=cognitive_state,
294
- rag_context=rag_context
295
- )
296
-
297
  new_status = render_session_status(mode, weaknesses, cognitive_state)
298
  return "", new_history, weaknesses, cognitive_state, new_status
299
 
300
- user_input.submit(
301
- respond,
302
- [user_input, chatbot, course_outline_state, weakness_state, cognitive_state_state, rag_chunks_state, model_name, language_preference, learning_mode, doc_type],
303
- [user_input, chatbot, weakness_state, cognitive_state_state, session_status]
304
- )
305
-
306
- def on_export(chat_history, course_outline, mode, weaknesses, cognitive_state):
307
- return export_conversation(chat_history, course_outline, mode, weaknesses, cognitive_state)
308
-
309
- export_btn.click(
310
- on_export,
311
- [chatbot, course_outline_state, learning_mode, weakness_state, cognitive_state_state],
312
- [result_display]
313
- )
314
-
315
- def on_quiz(chat_history, course_outline, weaknesses, cognitive_state, model, lang):
316
- return generate_quiz_from_history(chat_history, course_outline, weaknesses, cognitive_state, model, lang)
317
-
318
- quiz_btn.click(
319
- on_quiz,
320
- [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference],
321
- [result_display]
322
- )
323
-
324
- def on_summary(chat_history, course_outline, weaknesses, cognitive_state, model, lang):
325
- return summarize_conversation(chat_history, course_outline, weaknesses, cognitive_state, model, lang)
326
-
327
- summary_btn.click(
328
- on_summary,
329
- [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference],
330
- [result_display]
331
- )
332
 
 
 
 
 
 
 
333
  def clear_all():
334
  empty_state = {"confusion": 0, "mastery": 0}
335
  default_status = render_session_status("Concept Explainer", [], empty_state)
336
  return [], [], empty_state, [], "", default_status
337
-
338
- clear_btn.click(
339
- clear_all,
340
- None,
341
- [chatbot, weakness_state, cognitive_state_state, rag_chunks_state, result_display, session_status],
342
- queue=False
343
- )
344
-
345
- def show_guide(section):
346
- return get_user_guide_section(section)
347
-
348
- ug_getting_started.click(lambda: show_guide("getting_started"), outputs=result_display)
349
- ug_mode_def.click(lambda: show_guide("mode_definition"), outputs=result_display)
350
- ug_how_works.click(lambda: show_guide("how_clare_works"), outputs=result_display)
351
- ug_memory_line.click(lambda: show_guide("memory_line"), outputs=result_display)
352
 
353
  if __name__ == "__main__":
354
- # 移除 allowed_paths,因为 Base64 不需要它
355
  demo.launch()
 
32
  CLARE_LOGO_PATH = "clare_mascot.png"
33
  CLARE_RUN_PATH = "Clare_Run.png"
34
 
35
+ # ================== Base64 图片转换 ==================
36
  def image_to_base64(image_path):
 
37
  if not os.path.exists(image_path):
38
+ return ""
39
  with open(image_path, "rb") as img_file:
40
  encoded_string = base64.b64encode(img_file.read()).decode('utf-8')
 
41
  if image_path.lower().endswith(".png"):
42
  mime = "image/png"
43
  elif image_path.lower().endswith(".jpg") or image_path.lower().endswith(".jpeg"):
 
46
  mime = "image/png"
47
  return f"data:{mime};base64,{encoded_string}"
48
 
49
+ # ================== User Guide Content ==================
50
+ USER_GUIDE_SECTIONS = {
51
+ "getting_started": """
52
+ ## Getting started
53
+
54
+ Welcome to **Clare — Your Personalized AI Tutor**.
55
+
56
+ Clare is designed to support your learning through:
57
+ - Clear explanations of course concepts
58
+ - Socratic-style reasoning guidance
59
+ - Personalized reinforcement based on learning science
60
+ - Course-aligned answers using your uploaded materials
61
+ - Micro-quizzes & summaries to strengthen understanding
62
+
63
+ **To begin:**
64
+ 1. Select your **Learning Mode** on the left
65
+ 2. (Optional) Upload your **syllabus / slides / notes** at the top
66
+ 3. Ask Clare any question about your course, assignment, or study plan.
67
+ """,
68
+ "mode_definition": """
69
+ ## Mode Definition
70
+
71
+ Clare offers different teaching modes to match how you prefer to learn.
72
+
73
+ ### Concept Explainer
74
+ Clear, structured explanations with examples — ideal for learning new topics.
75
+
76
+ ### Socratic Tutor
77
+ Clare asks guiding questions instead of giving direct answers.
78
+ Helps you build reasoning and problem-solving skills.
79
+
80
+ ### Exam Prep / Quiz
81
+ Generates short practice questions aligned with your course week.
82
+ Useful for self-testing and preparing for exams.
83
+
84
+ ### Assignment Helper
85
+ Helps you interpret assignment prompts, plan structure, and understand requirements.
86
+ ❗ Clare does **not** produce full assignment answers (academic integrity).
87
+
88
+ ### Quick Summary
89
+ Gives brief summaries of slides, reading materials, or long questions.
90
+ """,
91
+ "how_clare_works": """
92
+ ## How Clare Works
93
+
94
+ Clare combines **course context + learning science + AI reasoning** to generate answers.
95
+
96
+ Clare uses:
97
+ - **Your selected Learning Mode** Determines tone, depth, and interaction style.
98
+
99
+ - **Your uploaded course files** Syllabus, slides, or papers help align answers with your specific course.
100
+
101
+ - **Reinforcement learning cycle** Answers may prioritize key concepts you’re likely to forget (based on the forgetting curve).
102
+
103
+ - **Adaptive explanation depth** Clare can adjust complexity depending on your previous interactions.
104
+
105
+ - **Responsible AI principles** Avoids harmful output and preserves academic integrity.
106
+ """,
107
+ "memory_line": """
108
+ ## What is Memory Line?
109
+
110
+ **Memory Line** is a visualization of your *learning reinforcement cycle*.
111
+
112
+ Based on the **forgetting-curve model**, Clare organizes your review topics into:
113
+ - **T+0 (Current Week)** – new concepts
114
+ - **T+7** – first spaced review
115
+ - **T+14** – reinforcement review
116
+ - **T+30** – long-term consolidation
117
+
118
+ Clare uses these cycles to decide:
119
+ - Which concepts to reinforce today
120
+ - Which explanations to prioritize
121
+ - When to introduce quick review questions
122
+
123
+ **Review Now** will generate a small set of concepts that are due for spaced repetition.
124
+ """,
125
+ "learning_progress": """
126
+ ## Learning Progress Report
127
+
128
+ The Learning Progress Report highlights:
129
+ - **Concepts mastered** - **Concepts in progress** - **Concepts due for review** - Your recent **micro-quiz results** - Suggested **next-step topics**
130
+
131
+ You can download a **summary** at any time to use as study notes.
132
+ """,
133
+ "how_files": """
134
+ ## How Clare Uses Your Files
135
+
136
+ Your uploaded syllabus / slides / notes help Clare:
137
+
138
+ - Align explanations with your exact course
139
+ - Use terminology consistent with your professor
140
+ - Improve factual accuracy
141
+ - Generate personalized reinforcement content
142
+ - 🔒 **Privacy**: Files are used only within your session.
143
+
144
+ Accepted formats: **.docx / .pdf / .pptx**
145
+ """,
146
+ "micro_quiz": """
147
+ ## Micro-Quiz
148
+
149
+ The **Micro-Quiz** function provides a:
150
+
151
+ - 1-minute self-check
152
+ - 1–3 questions based on your recent topics
153
+ - Instant feedback
154
+
155
+ Micro-quizzes strengthen your memory and are part of Clare’s reinforcement system.
156
+ """,
157
+ "summarization": """
158
+ ## Summarization
159
+
160
+ Clare can summarize:
161
+
162
+ - Lecture notes
163
+ - Uploaded PDFs
164
+ - Long conversation threads
165
+ - Complex concepts
166
+
167
+ Summary styles can include: Bullet points, Key ideas, Study notes, Comparison-style highlights.
168
+ """,
169
+ "export_conversation": """
170
+ ## Export Conversation
171
+
172
+ You can export your chat session for:
173
+
174
+ - Study review
175
+ - Exam preparation
176
+ - Documentation for tutoring help
177
+ - Saving important explanations
178
+
179
+ Export format: **Markdown / plain text**.
180
+ """,
181
+ "faq": """
182
+ ## FAQ
183
+
184
+ **Q: Does Clare give assignment answers?** No. Clare assists with understanding and planning but does **not** generate full solutions.
185
+
186
+ **Q: Does Clare replace lectures or TA office hours?** No. Clare supplements your learning by providing on-demand guidance.
187
+
188
+ **Q: Can Clare explain my professor’s slides?** Yes — upload your slides so Clare can align explanations with your course.
189
+
190
+ **Q: What languages does Clare support?** Currently: English & 简体中文.
191
+ """
192
+ }
193
+
194
+ # ================== CSS ==================
195
  CUSTOM_CSS = """
196
+ /* Header */
197
  .header-container {
198
+ display: flex; align-items: center; justify-content: space-between;
199
+ padding: 10px 20px; background-color: #ffffff;
200
+ border-bottom: 2px solid #f3f4f6; margin-bottom: 15px;
 
 
 
 
201
  }
202
 
203
+ /* 问号按钮样式 (? Buttons) */
204
+ .help-btn {
205
+ min-width: 25px !important; width: 25px !important; height: 25px !important;
206
+ padding: 0 !important; border-radius: 50% !important;
207
+ font-size: 12px !important; font-weight: bold !important;
208
+ background: transparent !important; color: #666 !important; border: 1px solid #ccc !important;
209
+ box-shadow: none !important; margin-left: 5px !important;
210
+ display: inline-flex !important; align-items: center !important; justify-content: center !important;
211
  }
212
+ .help-btn:hover { background: #eee !important; color: #333 !important; border-color: #999 !important; }
213
 
214
+ /* Memory Line */
215
  .memory-line-box {
216
+ border: 1px solid #e5e7eb; padding: 12px; border-radius: 8px;
217
+ background-color: #f9fafb; height: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  }
219
 
220
+ /* User Guide Accordion specific tweaking */
221
+ .user-guide-item { margin-bottom: 5px; }
222
+ """
223
 
224
  # ================== Gradio App ==================
225
 
226
  with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS) as demo:
227
 
228
+ # State variables
229
  course_outline_state = gr.State(DEFAULT_COURSE_TOPICS)
230
  weakness_state = gr.State([])
231
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
232
  rag_chunks_state = gr.State([])
233
 
234
+ # 1. Header
235
  gr.HTML(
236
  f"""
237
  <div class="header-container">
 
247
  </div>
248
  </div>
249
  </div>
 
250
  <div style="text-align: right;">
251
  <img src="{image_to_base64(HANBRIDGE_LOGO_PATH)}" style="height: 55px; object-fit: contain; margin-bottom: 5px;">
252
  <div style="font-size: 12px; color: #666;">
 
257
  """
258
  )
259
 
260
+ # 2. Main Layout
261
  with gr.Row():
262
 
263
+ # === [Left Sidebar] ===
264
  with gr.Column(scale=1, min_width=200):
265
  clear_btn = gr.Button("Reset Conversation", variant="stop")
266
 
267
+ # Model Settings
268
  gr.Markdown("### Model Settings")
269
  model_name = gr.Textbox(label="Model", value=DEFAULT_MODEL, lines=1)
270
  language_preference = gr.Radio(
 
273
  label="Language"
274
  )
275
 
276
+ # Learning Mode with Help Button
277
+ with gr.Row(variant="compact", elem_classes="no-gap"):
278
+ gr.Markdown("**Learning Mode**")
279
+ mode_help_btn = gr.Button("?", elem_classes="help-btn") # ? Button
280
+
281
  learning_mode = gr.Radio(
282
  choices=LEARNING_MODES,
283
  value="Concept Explainer",
284
+ label=None, # Hide default label to use custom header above
285
+ info="Select style",
286
+ show_label=False
287
  )
288
 
289
+ # --- User Guide (New Design: Nested Accordions) ---
290
+ with gr.Accordion("User Guide", open=True):
291
+ # 这里实现了“点击一个展开一个”的效果
292
+ with gr.Accordion("Getting Started", open=False, elem_classes="user-guide-item"):
293
+ gr.Markdown(USER_GUIDE_SECTIONS["getting_started"])
294
+
295
+ with gr.Accordion("Mode Definition", open=False, elem_classes="user-guide-item"):
296
+ gr.Markdown(USER_GUIDE_SECTIONS["mode_definition"])
297
+
298
+ with gr.Accordion("How Clare Works", open=False, elem_classes="user-guide-item"):
299
+ gr.Markdown(USER_GUIDE_SECTIONS["how_clare_works"])
300
+
301
+ with gr.Accordion("What is Memory Line", open=False, elem_classes="user-guide-item"):
302
+ gr.Markdown(USER_GUIDE_SECTIONS["memory_line"])
303
+
304
+ with gr.Accordion("Learning Progress Report", open=False, elem_classes="user-guide-item"):
305
+ gr.Markdown(USER_GUIDE_SECTIONS["learning_progress"])
306
+
307
+ with gr.Accordion("How Clare Uses Your Files", open=False, elem_classes="user-guide-item"):
308
+ gr.Markdown(USER_GUIDE_SECTIONS["how_files"])
309
+
310
+ with gr.Accordion("Micro-Quiz", open=False, elem_classes="user-guide-item"):
311
+ gr.Markdown(USER_GUIDE_SECTIONS["micro_quiz"])
312
+
313
+ with gr.Accordion("Summarization", open=False, elem_classes="user-guide-item"):
314
+ gr.Markdown(USER_GUIDE_SECTIONS["summarization"])
315
+
316
+ with gr.Accordion("Export Conversation", open=False, elem_classes="user-guide-item"):
317
+ gr.Markdown(USER_GUIDE_SECTIONS["export_conversation"])
318
+
319
+ with gr.Accordion("FAQ", open=False, elem_classes="user-guide-item"):
320
+ gr.Markdown(USER_GUIDE_SECTIONS["faq"])
321
 
322
  with gr.Group():
323
  gr.Button("System Settings", variant="secondary")
324
  gr.Button("Log Out", variant="secondary")
325
 
326
+ # === [Center Main] ===
327
  with gr.Column(scale=3):
328
 
329
+ # Upload & Memory Line
330
  with gr.Row():
331
+ # Upload
332
  with gr.Column(scale=1):
333
+ with gr.Row():
334
+ gr.Markdown("**Upload Course File**")
335
+ file_help_btn = gr.Button("?", elem_classes="help-btn") # ? Button
336
+
337
  syllabus_file = gr.File(
 
338
  file_types=[".docx", ".pdf", ".pptx"],
339
  file_count="single",
340
+ height=100,
341
+ show_label=False
342
  )
343
  doc_type = gr.Dropdown(
344
  choices=DOC_TYPES,
 
347
  container=False
348
  )
349
 
350
+ # Memory Line
351
  with gr.Column(scale=1):
352
  gr.HTML(
353
  f"""
354
  <div class="memory-line-box">
355
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:5px;">
356
+ <div style="font-weight:bold; font-size:14px;">Memory Line</div>
357
+ </div>
 
358
  <div style="position: relative; height: 35px; margin-top: 15px; margin-bottom: 5px;">
359
  <div style="position: absolute; bottom: 5px; left: 0; width: 100%; height: 8px; background-color: #e5e7eb; border-radius: 4px;"></div>
360
  <div style="position: absolute; bottom: 5px; left: 0; width: 40%; height: 8px; background-color: #8B1A1A; border-radius: 4px 0 0 4px;"></div>
361
  <img src="{image_to_base64(CLARE_RUN_PATH)}" style="position: absolute; left: 36%; bottom: 8px; height: 35px; z-index: 10;">
362
  </div>
 
363
  <div style="display:flex; justify-content:space-between; align-items:center; margin-top:8px;">
364
  <div style="font-size: 12px; color: #666;">Next Review: T+7</div>
365
+ <div style="font-size: 12px; color: #004a99; text-decoration:underline;">Learning Progress Report ⬇️</div>
 
 
366
  </div>
367
  </div>
368
  """
369
  )
370
+ # Memory Line 的问号按钮 (放在 HTML 下方紧凑的一行里)
371
+ with gr.Row(elem_classes="no-gap"):
372
+ ml_help_btn = gr.Button("What is Memory Line?", size="sm", variant="secondary")
373
+ review_btn = gr.Button("Review Now", size="sm", variant="primary")
374
  session_status = gr.Markdown(visible=False)
375
 
376
+ # Chat
377
  gr.Markdown(
378
  """
379
  <div style="background-color:#f9fafb; padding:10px; border-radius:5px; margin-top:10px; font-size:0.9em; color:#555;">
 
382
  </div>
383
  """
384
  )
 
385
  chatbot = gr.Chatbot(
386
+ label="", height=450,
 
 
 
 
387
  avatar_images=(None, CLARE_LOGO_PATH),
388
+ show_label=False, bubble_full_width=False
 
389
  )
 
390
  user_input = gr.Textbox(
391
+ label="Your Input", placeholder="Ask about a concept, your assignment, or let Clare test you...",
392
+ show_label=False, container=True, autofocus=True
 
 
 
393
  )
394
 
395
+ # === [Right Sidebar] ===
396
  with gr.Column(scale=1, min_width=180):
397
  gr.Markdown("### Actions")
398
+
399
+ with gr.Row():
400
+ export_btn = gr.Button("Export Conversation", size="sm", scale=4)
401
+ export_help_btn = gr.Button("?", elem_classes="help-btn", scale=1)
402
+
403
+ with gr.Row():
404
+ quiz_btn = gr.Button("Let's Try (Micro-Quiz)", size="sm", scale=4)
405
+ quiz_help_btn = gr.Button("?", elem_classes="help-btn", scale=1)
406
+
407
+ with gr.Row():
408
+ summary_btn = gr.Button("Summarization", size="sm", scale=4)
409
+ summary_help_btn = gr.Button("?", elem_classes="help-btn", scale=1)
410
 
411
  gr.Markdown("### Results")
412
  result_display = gr.Textbox(
413
+ label="Generated Content", lines=15,
 
414
  placeholder="Results from Export, Quiz, or Summary will appear here...",
415
  show_label=False
416
  )
417
 
418
+ # ================== 问号按钮逻辑 (Pop-up Info) ==================
419
+ # 当点击问号时,弹出对应的信息
420
+ mode_help_btn.click(lambda: gr.Info(USER_GUIDE_SECTIONS["mode_definition"], title="Mode Definition"))
421
+ file_help_btn.click(lambda: gr.Info(USER_GUIDE_SECTIONS["how_files"], title="How Files Work"))
422
+ ml_help_btn.click(lambda: gr.Info(USER_GUIDE_SECTIONS["memory_line"], title="Memory Line"))
423
+ export_help_btn.click(lambda: gr.Info(USER_GUIDE_SECTIONS["export_conversation"], title="Export"))
424
+ quiz_help_btn.click(lambda: gr.Info(USER_GUIDE_SECTIONS["micro_quiz"], title="Micro-Quiz"))
425
+ summary_help_btn.click(lambda: gr.Info(USER_GUIDE_SECTIONS["summarization"], title="Summarization"))
426
 
427
+ # ================== Main Logic (保持原有逻辑) ==================
428
  def update_course_and_rag(file, doc_type_val):
429
  topics = extract_course_topics_from_file(file, doc_type_val)
430
  rag_chunks = build_rag_chunks_from_file(file, doc_type_val)
431
  status_md = f"✅ **File Loaded:** {doc_type_val}\n\nAnalyzing content for Memory Line..."
432
  return topics, rag_chunks, status_md
433
 
434
+ syllabus_file.change(update_course_and_rag, [syllabus_file, doc_type], [course_outline_state, rag_chunks_state, session_status])
 
 
 
 
435
 
436
  def respond(message, chat_history, course_outline, weaknesses, cognitive_state, rag_chunks, model, lang, mode, doc_type_val):
437
  resolved_lang = detect_language(message or "", lang)
 
438
  if not message or not message.strip():
439
  return "", chat_history, weaknesses, cognitive_state, session_status.value
 
440
  weaknesses = update_weaknesses_from_message(message, weaknesses or [])
441
  cognitive_state = update_cognitive_state_from_message(message, cognitive_state)
442
  rag_context = retrieve_relevant_chunks(message, rag_chunks or [])
443
+ answer, new_history = chat_with_clare(message, chat_history, model, resolved_lang, mode, doc_type_val, course_outline, weaknesses, cognitive_state, rag_context)
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  new_status = render_session_status(mode, weaknesses, cognitive_state)
445
  return "", new_history, weaknesses, cognitive_state, new_status
446
 
447
+ user_input.submit(respond, [user_input, chatbot, course_outline_state, weakness_state, cognitive_state_state, rag_chunks_state, model_name, language_preference, learning_mode, doc_type], [user_input, chatbot, weakness_state, cognitive_state_state, session_status])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
 
449
+ # Button Actions
450
+ export_btn.click(lambda h, c, m, w, cog: export_conversation(h, c, m, w, cog), [chatbot, course_outline_state, learning_mode, weakness_state, cognitive_state_state], [result_display])
451
+ quiz_btn.click(lambda h, c, w, cog, m, l: generate_quiz_from_history(h, c, w, cog, m, l), [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference], [result_display])
452
+ summary_btn.click(lambda h, c, w, cog, m, l: summarize_conversation(h, c, w, cog, m, l), [chatbot, course_outline_state, weakness_state, cognitive_state_state, model_name, language_preference], [result_display])
453
+
454
+ # Reset
455
  def clear_all():
456
  empty_state = {"confusion": 0, "mastery": 0}
457
  default_status = render_session_status("Concept Explainer", [], empty_state)
458
  return [], [], empty_state, [], "", default_status
459
+ clear_btn.click(clear_all, None, [chatbot, weakness_state, cognitive_state_state, rag_chunks_state, result_display, session_status], queue=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
 
461
  if __name__ == "__main__":
 
462
  demo.launch()