SarahXia0405 commited on
Commit
fda6da3
·
verified ·
1 Parent(s): 7d4e75e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -179
app.py CHANGED
@@ -46,7 +46,7 @@ def image_to_base64(image_path):
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
  Welcome to **Clare — Your Personalized AI Tutor**.
@@ -184,62 +184,65 @@ Currently: English & 简体中文.
184
 
185
  # ================== CSS 样式表 ==================
186
  CUSTOM_CSS = """
187
- /* Header */
188
- .header-container {
189
- display: flex; align-items: center; justify-content: space-between;
190
- padding: 10px 20px; background-color: #ffffff;
191
- border-bottom: 2px solid #f3f4f6; margin-bottom: 15px;
192
- }
193
-
194
- /* --- User Guide 样式优化 --- */
195
-
196
- /* 1. 最外层 User Guide (大标题) */
197
- .main-user-guide {
198
- border: none !important;
199
- background: transparent !important;
200
- box-shadow: none !important;
201
  }
202
- .main-user-guide > .label-wrap {
203
- border: none !important;
204
- background: transparent !important;
205
- padding: 10px 0 !important;
 
206
  }
207
- .main-user-guide > .label-wrap span {
208
- font-size: 1.3rem !important; /* 加大 */
209
- font-weight: 800 !important; /* 加粗 */
210
- color: #111827 !important; /* 深黑色 */
 
 
 
 
 
211
  }
212
 
213
- /* 2. 内部子菜单 (小标题 - 列表风格) */
214
- .clean-accordion {
215
- border: none !important;
216
  background: transparent !important;
217
- box-shadow: none !important;
218
- margin-bottom: 0px !important;
219
- padding: 0 !important;
220
- border-radius: 0 !important;
221
- }
222
- .clean-accordion > .label-wrap {
223
- padding: 8px 5px !important;
224
  border: none !important;
225
- background: transparent !important;
226
- border-bottom: 1px solid #e5e7eb !important; /* 细分隔线 */
227
- }
228
- .clean-accordion > .label-wrap span {
229
- font-size: 0.9rem !important; /* 比主标题小 */
230
- font-weight: 500 !important; /* 中等粗细 */
231
- color: #374151 !important; /* 深灰色 */
232
  }
233
- .clean-accordion > .label-wrap:hover {
234
- background-color: #f9fafb !important;
235
  }
236
 
237
- /* Action 按钮加粗 & Tooltip */
 
 
 
 
 
 
 
 
 
 
238
  .action-btn { font-weight: bold !important; font-size: 0.9rem !important; position: relative; overflow: visible !important; }
239
  .action-btn:hover::before { content: "See User Guide for details"; position: absolute; bottom: 110%; left: 50%; transform: translateX(-50%); background-color: #333; color: #fff; padding: 5px 10px; border-radius: 5px; font-size: 12px; white-space: nowrap; z-index: 1000; pointer-events: none; opacity: 0; animation: fadeIn 0.2s forwards; }
240
  .action-btn:hover::after { content: ""; position: absolute; bottom: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: #333 transparent transparent transparent; opacity: 0; animation: fadeIn 0.2s forwards; }
241
 
242
- /* HTML Tooltip (Memory Line) */
243
  .html-tooltip { border-bottom: 1px dashed #999; cursor: help; position: relative; }
244
  .html-tooltip:hover::before { content: attr(data-tooltip); position: absolute; bottom: 120%; left: 0; background-color: #333; color: #fff; padding: 5px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap; z-index: 100; pointer-events: none; }
245
 
@@ -253,164 +256,237 @@ CUSTOM_CSS = """
253
 
254
  with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS) as demo:
255
 
256
- # State variables
257
  course_outline_state = gr.State(DEFAULT_COURSE_TOPICS)
258
  weakness_state = gr.State([])
259
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
260
- rag_chunks_state = gr.State([])
261
-
262
- # 1. Header (包含右上角 Log Out)
263
- gr.HTML(
264
- f"""
265
- <div class="header-container">
266
- <div style="display:flex; align-items:center; gap: 20px;">
267
- <img src="{image_to_base64(CLARE_LOGO_PATH)}" style="height: 75px; object-fit: contain;">
268
- <div style="display:flex; flex-direction:column;">
269
- <div style="font-size: 32px; font-weight: 800; line-height: 1.1; color: #000;">
270
- Clare
271
- <span style="font-size: 18px; font-weight: 600; margin-left: 10px;">Your Personalized AI Tutor</span>
272
- </div>
273
- <div style="font-size: 14px; font-style: italic; color: #333; margin-top: 4px;">
274
- Personalized guidance, review, and intelligent reinforcement - designed for how you learn
275
- </div>
276
- </div>
277
- </div>
278
- <div style="text-align: right;">
279
- <img src="{image_to_base64(HANBRIDGE_LOGO_PATH)}" style="height: 55px; object-fit: contain; margin-bottom: 5px;">
280
- <div style="font-size: 12px; color: #666; margin-top: 4px; display:flex; align-items:center; justify-content:flex-end; gap:12px;">
281
- <span>🎓 (Student Name) (Student Email/ID)</span>
282
- <a href="#" style="color: #000; font-weight: bold; text-decoration: underline; font-size: 12px;">Log Out</a>
283
  </div>
284
- </div>
285
- </div>
286
- """
287
- )
288
-
289
- # 2. Main Layout
290
- with gr.Row():
291
-
292
- # === [Left Sidebar] ===
293
- with gr.Column(scale=1, min_width=200):
294
- clear_btn = gr.Button("Reset Conversation", variant="stop")
295
 
296
- # Model Settings (Locked)
297
- gr.Markdown("### Model Settings")
298
- model_name = gr.Textbox(label="Model", value="gpt-4.1-mini", interactive=False, lines=1)
299
- language_preference = gr.Radio(choices=["Auto", "English", "简体中文"], value="Auto", label="Language")
300
 
301
- learning_mode = gr.Radio(
302
- choices=LEARNING_MODES,
303
- value="Concept Explainer",
304
- label="Learning Mode",
305
- info="See User Guide for mode definitions details."
306
- )
307
 
308
- # User Guide (美化后的手风琴)
309
- with gr.Accordion("User Guide", open=True, elem_classes="main-user-guide"):
310
- with gr.Accordion("Getting Started", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["getting_started"])
311
- with gr.Accordion("Mode Definition", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["mode_definition"])
312
- with gr.Accordion("How Clare Works", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["how_clare_works"])
313
- with gr.Accordion("What is Memory Line", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["memory_line"])
314
- with gr.Accordion("Learning Progress Report", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["learning_progress"])
315
- with gr.Accordion("How Clare Uses Your Files", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["how_files"])
316
- with gr.Accordion("Micro-Quiz", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["micro_quiz"])
317
- with gr.Accordion("Summarization", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["summarization"])
318
- with gr.Accordion("Export Conversation", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["export_conversation"])
319
- with gr.Accordion("FAQ", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["faq"])
 
 
 
 
 
 
 
 
 
 
 
320
 
321
- # 底部 Settings & Copyright
322
- gr.Markdown("---")
323
- gr.Button("System Settings", size="sm", variant="secondary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
 
325
- # 署名 (Footer)
326
- gr.HTML(
327
- """
328
- <div style="font-size: 11px; color: #9ca3af; margin-top: 15px; text-align: left;">
329
- © 2025 Made by <a href="https://www.linkedin.com/in/qinghua-xia-479199252/" target="_blank" style="color: #6b7280; text-decoration: underline;">Sarah Xia</a>
330
- </div>
331
- """
332
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
- # === [Center Main] ===
335
- with gr.Column(scale=3):
336
-
337
- # --- Upload & Memory Line Row (New Layout) ---
338
- with gr.Row():
339
 
340
- # [Left Group]: Upload Box + Controls (Dropdown/Button)
341
- with gr.Column(scale=2): # Give this side more space (approx 66%)
342
- with gr.Row():
343
- # Upload File Box (Larger)
344
- with gr.Column(scale=3):
345
- syllabus_file = gr.File(
346
- file_types=[".docx", ".pdf", ".pptx"],
347
- file_count="single",
348
- height=150,
349
- label="Upload file (.docx/.pdf/.pptx)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  )
351
- # Controls (Dropdown + Button) stacked vertically next to file box
352
- with gr.Column(scale=1):
353
- doc_type = gr.Dropdown(choices=DOC_TYPES, value="Syllabus", label="", container=False)
354
- # 使用 Markdown 模拟一点间距或者直接放按钮
355
- docs_btn = gr.Button("📂 Loaded Docs", size="sm", variant="secondary")
 
 
 
 
 
 
 
 
 
 
 
 
 
356
 
357
- # [Right Group]: Memory Line (Right Aligned)
358
- with gr.Column(scale=1): # Less space (approx 33%)
359
- with gr.Group(elem_classes="memory-line-box"):
360
- gr.HTML(
361
- f"""
362
- <div style="font-weight:bold; font-size:14px; margin-bottom:5px;">
363
- <span class="html-tooltip" data-tooltip="See User Guide for explanation">Memory Line</span>
364
- </div>
365
- <div style="position: relative; height: 35px; margin-top: 10px; margin-bottom: 5px;">
366
- <div style="position: absolute; bottom: 5px; left: 0; width: 100%; height: 8px; background-color: #e5e7eb; border-radius: 4px;"></div>
367
- <div style="position: absolute; bottom: 5px; left: 0; width: 40%; height: 8px; background-color: #8B1A1A; border-radius: 4px 0 0 4px;"></div>
368
- <img src="{image_to_base64(CLARE_RUN_PATH)}" style="position: absolute; left: 36%; bottom: 8px; height: 35px; z-index: 10;">
369
- </div>
370
- <div style="display:flex; justify-content:space-between; align-items:center;">
371
- <div style="font-size: 12px; color: #666;">Next Review: T+7</div>
372
- <div style="font-size: 12px; color: #004a99; text-decoration:underline; cursor:pointer;">Report ⬇️</div>
373
- </div>
374
- """
375
- )
376
- review_btn = gr.Button("Review Now", size="sm", variant="primary")
377
- session_status = gr.Markdown(visible=False)
378
 
379
- # Chat Interface
380
- gr.Markdown(
381
- """
382
- <div style="background-color:#f9fafb; padding:10px; border-radius:5px; margin-top:10px; font-size:0.9em; color:#555;">
383
- ✦ <b>Instruction:</b> Use different learning modes to change Clare's teaching style.<br>
384
- ✦ <b>Example:</b> "Why does context length matter?" or "How does RAG reduce hallucination?"
385
- </div>
386
- """
387
- )
388
- chatbot = gr.Chatbot(label="", height=450, avatar_images=(None, CLARE_LOGO_PATH), show_label=False, bubble_full_width=False)
389
- user_input = gr.Textbox(label="Your Input", placeholder="Ask about a concept, your assignment, or let Clare test you...", show_label=False, container=True, autofocus=True)
390
 
391
- # === [Right Sidebar] ===
392
- with gr.Column(scale=1, min_width=180):
393
-
394
- # Spacer (Push Actions down to align with Chatbot)
395
- gr.HTML("<div style='height: 200px; width: 100%;'></div>")
396
-
397
- gr.Markdown("### Actions")
398
-
399
- export_btn = gr.Button("Export Conversation", size="sm", elem_classes="action-btn")
400
- quiz_btn = gr.Button("Let's Try (Micro-Quiz)", size="sm", elem_classes="action-btn")
401
- summary_btn = gr.Button("Summarization", size="sm", elem_classes="action-btn")
402
-
403
- gr.Markdown("### Results")
404
- result_display = gr.Textbox(label="Generated Content", lines=15, placeholder="Results...", show_label=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
 
406
- # ================== Logic Bindings ==================
 
 
 
407
 
 
408
  def update_course_and_rag(file, doc_type_val):
409
  topics = extract_course_topics_from_file(file, doc_type_val)
410
  rag_chunks = build_rag_chunks_from_file(file, doc_type_val)
411
  status_md = f"✅ **File Loaded:** {doc_type_val}\n\nAnalyzing content for Memory Line..."
412
  return topics, rag_chunks, status_md
413
 
 
414
  docs_btn.click(lambda: gr.Info("Current Context: Syllabus.pdf (Uploaded), Course_Intro.docx (System)", title="Loaded Documents"))
415
 
416
  def respond(message, chat_history, course_outline, weaknesses, cognitive_state, rag_chunks, model, lang, mode, doc_type_val):
 
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
  Welcome to **Clare — Your Personalized AI Tutor**.
 
184
 
185
  # ================== CSS 样式表 ==================
186
  CUSTOM_CSS = """
187
+ /* --- Login Page --- */
188
+ .login-container {
189
+ max-width: 500px !important;
190
+ margin: 100px auto !important;
191
+ padding: 40px !important;
192
+ background: #ffffff;
193
+ border: 1px solid #e5e7eb;
194
+ border-radius: 12px;
195
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
196
+ text-align: center;
 
 
 
 
197
  }
198
+ .login-btn {
199
+ background-color: #8B1A1A !important;
200
+ color: white !important;
201
+ font-weight: bold !important;
202
+ margin-top: 20px !important;
203
  }
204
+
205
+ /* --- Main Header --- */
206
+ .header-container {
207
+ padding: 10px 20px;
208
+ background-color: #ffffff;
209
+ border-bottom: 2px solid #f3f4f6;
210
+ margin-bottom: 15px;
211
+ display: flex;
212
+ align-items: center;
213
  }
214
 
215
+ /* 伪装成 Link 的 Button (Log Out) */
216
+ .link-btn {
 
217
  background: transparent !important;
 
 
 
 
 
 
 
218
  border: none !important;
219
+ box-shadow: none !important;
220
+ color: #000 !important;
221
+ text-decoration: underline;
222
+ font-weight: bold !important;
223
+ font-size: 12px !important;
224
+ padding: 0 5px !important;
225
+ min-width: auto !important;
226
  }
227
+ .link-btn:hover {
228
+ color: #cc0000 !important;
229
  }
230
 
231
+ /* User Guide 样式 */
232
+ .main-user-guide { border: none !important; background: transparent !important; box-shadow: none !important; }
233
+ .main-user-guide > .label-wrap { border: none !important; background: transparent !important; padding: 10px 0 !important; }
234
+ .main-user-guide > .label-wrap span { font-size: 1.3rem !important; font-weight: 800 !important; color: #111827 !important; }
235
+
236
+ .clean-accordion { border: none !important; background: transparent !important; box-shadow: none !important; margin-bottom: 0px !important; padding: 0 !important; border-radius: 0 !important; }
237
+ .clean-accordion > .label-wrap { padding: 8px 5px !important; border: none !important; background: transparent !important; border-bottom: 1px solid #e5e7eb !important; }
238
+ .clean-accordion > .label-wrap span { font-size: 0.9rem !important; font-weight: 500 !important; color: #374151 !important; }
239
+ .clean-accordion > .label-wrap:hover { background-color: #f9fafb !important; }
240
+
241
+ /* Action 按钮 & Tooltip */
242
  .action-btn { font-weight: bold !important; font-size: 0.9rem !important; position: relative; overflow: visible !important; }
243
  .action-btn:hover::before { content: "See User Guide for details"; position: absolute; bottom: 110%; left: 50%; transform: translateX(-50%); background-color: #333; color: #fff; padding: 5px 10px; border-radius: 5px; font-size: 12px; white-space: nowrap; z-index: 1000; pointer-events: none; opacity: 0; animation: fadeIn 0.2s forwards; }
244
  .action-btn:hover::after { content: ""; position: absolute; bottom: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: #333 transparent transparent transparent; opacity: 0; animation: fadeIn 0.2s forwards; }
245
 
 
246
  .html-tooltip { border-bottom: 1px dashed #999; cursor: help; position: relative; }
247
  .html-tooltip:hover::before { content: attr(data-tooltip); position: absolute; bottom: 120%; left: 0; background-color: #333; color: #fff; padding: 5px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap; z-index: 100; pointer-events: none; }
248
 
 
256
 
257
  with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS) as demo:
258
 
259
+ # 全局状态
260
  course_outline_state = gr.State(DEFAULT_COURSE_TOPICS)
261
  weakness_state = gr.State([])
262
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
263
+ rag_chunks_state = gr.State([])
264
+
265
+ # 登录状态
266
+ user_name_state = gr.State("")
267
+ user_id_state = gr.State("")
268
+
269
+ # ================== Layer 1: Login View ==================
270
+ with gr.Group(visible=True) as login_view:
271
+ with gr.Column(elem_classes="login-container"):
272
+ # Logo Area
273
+ gr.HTML(
274
+ f"""
275
+ <div style="display:flex; justify-content:center; gap:20px; margin-bottom:20px;">
276
+ <img src="{image_to_base64(CLARE_LOGO_PATH)}" style="height: 80px;">
277
+ <img src="{image_to_base64(HANBRIDGE_LOGO_PATH)}" style="height: 60px; margin-top:10px;">
 
 
 
 
 
 
 
 
278
  </div>
279
+ <h1 style="font-size: 24px; font-weight: bold; margin-bottom: 5px;">Welcome to Clare</h1>
280
+ <p style="color: #666; margin-bottom: 30px;">Your Personalized AI Tutor</p>
281
+ """
282
+ )
 
 
 
 
 
 
 
283
 
284
+ login_name = gr.Textbox(label="Student Name", placeholder="Enter your full name", autofocus=True)
285
+ login_id = gr.Textbox(label="Student ID / Email", placeholder="Enter your student ID or email")
 
 
286
 
287
+ login_btn = gr.Button("Enter Class", elem_classes="login-btn", size="lg")
288
+
289
+ login_msg = gr.Markdown(visible=False) # 错误提示占位
 
 
 
290
 
291
+ # ================== Layer 2: Main App View ==================
292
+ with gr.Group(visible=False) as main_view:
293
+
294
+ # --- Custom Header (Using Row for Alignment) ---
295
+ with gr.Row(elem_classes="header-container"):
296
+ # Left: Mascot & Title
297
+ with gr.Column(scale=1):
298
+ gr.HTML(
299
+ f"""
300
+ <div style="display:flex; align-items:center; gap: 20px;">
301
+ <img src="{image_to_base64(CLARE_LOGO_PATH)}" style="height: 75px; object-fit: contain;">
302
+ <div style="display:flex; flex-direction:column;">
303
+ <div style="font-size: 32px; font-weight: 800; line-height: 1.1; color: #000;">
304
+ Clare
305
+ <span style="font-size: 18px; font-weight: 600; margin-left: 10px;">Your Personalized AI Tutor</span>
306
+ </div>
307
+ <div style="font-size: 14px; font-style: italic; color: #333; margin-top: 4px;">
308
+ Personalized guidance, review, and intelligent reinforcement
309
+ </div>
310
+ </div>
311
+ </div>
312
+ """
313
+ )
314
 
315
+ # Right: Hanbridge Logo + Student Info + Logout
316
+ with gr.Column(scale=1):
317
+ # 顶部放 Logo,居右
318
+ gr.HTML(
319
+ f"""
320
+ <div style="display:flex; justify-content:flex-end; margin-bottom: 5px;">
321
+ <img src="{image_to_base64(HANBRIDGE_LOGO_PATH)}" style="height: 55px; object-fit: contain;">
322
+ </div>
323
+ """
324
+ )
325
+ # 下面放 Info 和 Logout 按钮,使用 Row 紧凑排列
326
+ with gr.Row():
327
+ # 这里放一个空的 Column 把内容挤到右边
328
+ with gr.Column(scale=1): pass
329
+
330
+ # 真正的内容在右边
331
+ with gr.Column(scale=0, min_width=300):
332
+ with gr.Row(elem_classes="no-gap"):
333
+ student_info_disp = gr.Markdown(value="", elem_id="student-info")
334
+ logout_btn = gr.Button("Log Out", elem_classes="link-btn")
335
+
336
+ # --- Main Layout (Sidebar + Center + Right) ---
337
+ with gr.Row():
338
 
339
+ # === Left Sidebar ===
340
+ with gr.Column(scale=1, min_width=200):
341
+ clear_btn = gr.Button("Reset Conversation", variant="stop")
342
+
343
+ gr.Markdown("### Model Settings")
344
+ model_name = gr.Textbox(label="Model", value="gpt-4.1-mini", interactive=False, lines=1)
345
+ language_preference = gr.Radio(choices=["Auto", "English", "简体中文"], value="Auto", label="Language")
346
+
347
+ learning_mode = gr.Radio(
348
+ choices=LEARNING_MODES,
349
+ value="Concept Explainer",
350
+ label="Learning Mode",
351
+ info="See User Guide for mode definitions details."
352
+ )
353
+
354
+ # User Guide
355
+ with gr.Accordion("User Guide", open=True, elem_classes="main-user-guide"):
356
+ with gr.Accordion("Getting Started", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["getting_started"])
357
+ with gr.Accordion("Mode Definition", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["mode_definition"])
358
+ with gr.Accordion("How Clare Works", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["how_clare_works"])
359
+ with gr.Accordion("What is Memory Line", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["memory_line"])
360
+ with gr.Accordion("Learning Progress Report", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["learning_progress"])
361
+ with gr.Accordion("How Clare Uses Your Files", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["how_files"])
362
+ with gr.Accordion("Micro-Quiz", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["micro_quiz"])
363
+ with gr.Accordion("Summarization", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["summarization"])
364
+ with gr.Accordion("Export Conversation", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["export_conversation"])
365
+ with gr.Accordion("FAQ", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["faq"])
366
+
367
+ gr.Markdown("---")
368
+ gr.Button("System Settings", size="sm", variant="secondary")
369
+
370
+ gr.HTML(
371
+ """
372
+ <div style="font-size: 11px; color: #9ca3af; margin-top: 15px; text-align: left;">
373
+ © 2025 Made by <a href="https://www.linkedin.com/in/qinghua-xia-479199252/" target="_blank" style="color: #6b7280; text-decoration: underline;">Sarah Xia</a>
374
+ </div>
375
+ """
376
+ )
377
 
378
+ # === Center Main ===
379
+ with gr.Column(scale=3):
 
 
 
380
 
381
+ # Upload & Memory Line
382
+ with gr.Row():
383
+ # [Left] Upload
384
+ with gr.Column(scale=2):
385
+ with gr.Row():
386
+ with gr.Column(scale=3):
387
+ # 调大高度 height=160
388
+ syllabus_file = gr.File(
389
+ file_types=[".docx", ".pdf", ".pptx"],
390
+ file_count="single",
391
+ height=160,
392
+ label="Upload file (.docx/.pdf/.pptx)"
393
+ )
394
+ with gr.Column(scale=1):
395
+ doc_type = gr.Dropdown(choices=DOC_TYPES, value="Syllabus", label="", container=False)
396
+ docs_btn = gr.Button("📂 Loaded Docs", size="sm", variant="secondary")
397
+
398
+ # [Right] Memory Line
399
+ with gr.Column(scale=1):
400
+ with gr.Group(elem_classes="memory-line-box"):
401
+ gr.HTML(
402
+ f"""
403
+ <div style="font-weight:bold; font-size:14px; margin-bottom:5px;">
404
+ <span class="html-tooltip" data-tooltip="See User Guide for explanation">Memory Line</span>
405
+ </div>
406
+ <div style="position: relative; height: 35px; margin-top: 10px; margin-bottom: 5px;">
407
+ <div style="position: absolute; bottom: 5px; left: 0; width: 100%; height: 8px; background-color: #e5e7eb; border-radius: 4px;"></div>
408
+ <div style="position: absolute; bottom: 5px; left: 0; width: 40%; height: 8px; background-color: #8B1A1A; border-radius: 4px 0 0 4px;"></div>
409
+ <img src="{image_to_base64(CLARE_RUN_PATH)}" style="position: absolute; left: 36%; bottom: 8px; height: 35px; z-index: 10;">
410
+ </div>
411
+ <div style="display:flex; justify-content:space-between; align-items:center;">
412
+ <div style="font-size: 12px; color: #666;">Next Review: T+7</div>
413
+ <div style="font-size: 12px; color: #004a99; text-decoration:underline; cursor:pointer;">Report ⬇️</div>
414
+ </div>
415
+ """
416
  )
417
+ review_btn = gr.Button("Review Now", size="sm", variant="primary")
418
+ session_status = gr.Markdown(visible=False)
419
+
420
+ # Chat
421
+ gr.Markdown(
422
+ """
423
+ <div style="background-color:#f9fafb; padding:10px; border-radius:5px; margin-top:10px; font-size:0.9em; color:#555;">
424
+ ✦ <b>Instruction:</b> Use different learning modes to change Clare's teaching style.<br>
425
+ ✦ <b>Example:</b> "Why does context length matter?" or "How does RAG reduce hallucination?"
426
+ </div>
427
+ """
428
+ )
429
+ chatbot = gr.Chatbot(label="", height=450, avatar_images=(None, CLARE_LOGO_PATH), show_label=False, bubble_full_width=False)
430
+ user_input = gr.Textbox(label="Your Input", placeholder="Ask about a concept, your assignment, or let Clare test you...", show_label=False, container=True, autofocus=True)
431
+
432
+ # === Right Sidebar ===
433
+ with gr.Column(scale=1, min_width=180):
434
+ gr.HTML("<div style='height: 210px; width: 100%;'></div>") # Spacer
435
 
436
+ gr.Markdown("### Actions")
437
+ export_btn = gr.Button("Export Conversation", size="sm", elem_classes="action-btn")
438
+ quiz_btn = gr.Button("Let's Try (Micro-Quiz)", size="sm", elem_classes="action-btn")
439
+ summary_btn = gr.Button("Summarization", size="sm", elem_classes="action-btn")
440
+
441
+ gr.Markdown("### Results")
442
+ result_display = gr.Textbox(label="Generated Content", lines=15, placeholder="Results...", show_label=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
 
444
+ # ================== Logic & Events ==================
 
 
 
 
 
 
 
 
 
 
445
 
446
+ def perform_login(name, id_val):
447
+ if not name.strip() or not id_val.strip():
448
+ return {login_msg: gr.update(value="❌ Please enter both Name and ID", visible=True)}
449
+
450
+ # 登录成功:更新Header信息,隐藏登录页,显示主页
451
+ welcome_text = f"🎓 **{name}** ({id_val})"
452
+ return {
453
+ login_view: gr.update(visible=False),
454
+ main_view: gr.update(visible=True),
455
+ student_info_disp: gr.update(value=welcome_text),
456
+ login_msg: gr.update(visible=False),
457
+ user_name_state: name,
458
+ user_id_state: id_val
459
+ }
460
+
461
+ def perform_logout():
462
+ # 登出:清空输入,隐藏主页,显示登录页
463
+ return {
464
+ login_view: gr.update(visible=True),
465
+ main_view: gr.update(visible=False),
466
+ login_name: "",
467
+ login_id: "",
468
+ student_info_disp: ""
469
+ }
470
+
471
+ login_btn.click(
472
+ perform_login,
473
+ inputs=[login_name, login_id],
474
+ outputs=[login_view, main_view, student_info_disp, login_msg, user_name_state, user_id_state]
475
+ )
476
 
477
+ logout_btn.click(
478
+ perform_logout,
479
+ outputs=[login_view, main_view, login_name, login_id, student_info_disp]
480
+ )
481
 
482
+ # Existing Main App Logic
483
  def update_course_and_rag(file, doc_type_val):
484
  topics = extract_course_topics_from_file(file, doc_type_val)
485
  rag_chunks = build_rag_chunks_from_file(file, doc_type_val)
486
  status_md = f"✅ **File Loaded:** {doc_type_val}\n\nAnalyzing content for Memory Line..."
487
  return topics, rag_chunks, status_md
488
 
489
+ syllabus_file.change(update_course_and_rag, [syllabus_file, doc_type], [course_outline_state, rag_chunks_state, session_status])
490
  docs_btn.click(lambda: gr.Info("Current Context: Syllabus.pdf (Uploaded), Course_Intro.docx (System)", title="Loaded Documents"))
491
 
492
  def respond(message, chat_history, course_outline, weaknesses, cognitive_state, rag_chunks, model, lang, mode, doc_type_val):