SarahXia0405 commited on
Commit
389c921
·
verified ·
1 Parent(s): 92b95bb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +195 -233
app.py CHANGED
@@ -184,51 +184,18 @@ Currently: English & 简体中文.
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; }
@@ -238,17 +205,20 @@ CUSTOM_CSS = """
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
-
249
- /* Memory Line Box */
250
  .memory-line-box { border: 1px solid #e5e7eb; padding: 12px; border-radius: 8px; background-color: #f9fafb; height: 100%; display: flex; flex-direction: column; justify-content: space-between; }
251
 
 
 
 
 
252
  @keyframes fadeIn { to { opacity: 1; } }
253
  """
254
 
@@ -262,224 +232,216 @@ with gr.Blocks(title="Clare – Hanbridge AI Teaching Assistant", css=CUSTOM_CSS
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)
 
184
 
185
  # ================== CSS 样式表 ==================
186
  CUSTOM_CSS = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  /* --- Main Header --- */
188
+ .header-container { padding: 10px 20px; background-color: #ffffff; border-bottom: 2px solid #f3f4f6; margin-bottom: 15px; display: flex; align-items: center; }
 
 
 
 
 
 
 
189
 
190
+ /* Log Out Link Style */
191
+ .link-btn { background: transparent !important; border: none !important; box-shadow: none !important; color: #000 !important; text-decoration: underline; font-weight: bold !important; font-size: 12px !important; padding: 0 5px !important; min-width: auto !important; }
192
+ .link-btn:hover { color: #cc0000 !important; }
193
+
194
+ /* Login Button in Header */
195
+ .header-login-btn { font-size: 13px !important; padding: 4px 12px !important; background-color: #f3f4f6 !important; color: #333 !important; border: 1px solid #ddd !important; border-radius: 15px !important; }
196
+ .header-login-btn:hover { background-color: #e5e7eb !important; border-color: #ccc !important; }
 
 
 
 
 
 
 
 
197
 
198
+ /* User Guide */
199
  .main-user-guide { border: none !important; background: transparent !important; box-shadow: none !important; }
200
  .main-user-guide > .label-wrap { border: none !important; background: transparent !important; padding: 10px 0 !important; }
201
  .main-user-guide > .label-wrap span { font-size: 1.3rem !important; font-weight: 800 !important; color: #111827 !important; }
 
205
  .clean-accordion > .label-wrap span { font-size: 0.9rem !important; font-weight: 500 !important; color: #374151 !important; }
206
  .clean-accordion > .label-wrap:hover { background-color: #f9fafb !important; }
207
 
208
+ /* Action Buttons */
209
  .action-btn { font-weight: bold !important; font-size: 0.9rem !important; position: relative; overflow: visible !important; }
210
  .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; }
211
  .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; }
212
 
213
+ /* Tooltips & Memory Line */
214
  .html-tooltip { border-bottom: 1px dashed #999; cursor: help; position: relative; }
215
  .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; }
 
 
216
  .memory-line-box { border: 1px solid #e5e7eb; padding: 12px; border-radius: 8px; background-color: #f9fafb; height: 100%; display: flex; flex-direction: column; justify-content: space-between; }
217
 
218
+ /* Results Box Style */
219
+ .result-box { border: 1px solid #e5e7eb; background: #ffffff; padding: 10px; border-radius: 8px; height: 100%; }
220
+ .result-box .prose { font-size: 0.9rem; }
221
+
222
  @keyframes fadeIn { to { opacity: 1; } }
223
  """
224
 
 
232
  cognitive_state_state = gr.State({"confusion": 0, "mastery": 0})
233
  rag_chunks_state = gr.State([])
234
 
235
+ # 用户状态
236
  user_name_state = gr.State("")
237
  user_id_state = gr.State("")
238
 
239
+ # --- Header (Always Visible) ---
240
+ with gr.Row(elem_classes="header-container"):
241
+ # Left: Logo & Title
242
+ with gr.Column(scale=2):
243
+ gr.HTML(
244
+ f"""
245
+ <div style="display:flex; align-items:center; gap: 20px;">
246
+ <img src="{image_to_base64(CLARE_LOGO_PATH)}" style="height: 75px; object-fit: contain;">
247
+ <div style="display:flex; flex-direction:column;">
248
+ <div style="font-size: 32px; font-weight: 800; line-height: 1.1; color: #000;">
249
+ Clare
250
+ <span style="font-size: 18px; font-weight: 600; margin-left: 10px;">Your Personalized AI Tutor</span>
251
+ </div>
252
+ <div style="font-size: 14px; font-style: italic; color: #333; margin-top: 4px;">
253
+ Personalized guidance, review, and intelligent reinforcement
254
+ </div>
255
+ </div>
256
+ </div>
257
+ """
258
+ )
259
+
260
+ # Right: Hanbridge Logo & Dynamic Login Area
261
+ with gr.Column(scale=1):
262
+ # Top: Logo
263
  gr.HTML(
264
  f"""
265
+ <div style="display:flex; justify-content:flex-end; margin-bottom: 8px;">
266
+ <img src="{image_to_base64(HANBRIDGE_LOGO_PATH)}" style="height: 55px; object-fit: contain;">
 
267
  </div>
 
 
268
  """
269
  )
270
+ # Bottom: Login UI Logic
271
+ with gr.Row():
272
+ with gr.Column(scale=1): pass # Spacer to push content right
273
+
274
+ with gr.Column(scale=0, min_width=300):
275
+
276
+ # 1. Default State: Login Button (Visible initially)
277
+ login_start_btn = gr.Button("👤 Student Login", size="sm", elem_classes="header-login-btn")
278
+
279
+ # 2. Input State: Inputs + Confirm (Hidden initially)
280
+ with gr.Group(visible=False) as login_form_group:
281
+ with gr.Row(elem_classes="no-gap"):
282
+ name_input = gr.Textbox(placeholder="Name", show_label=False, container=False, scale=2, min_width=80)
283
+ id_input = gr.Textbox(placeholder="ID", show_label=False, container=False, scale=2, min_width=80)
284
+ login_confirm_btn = gr.Button("✅", size="sm", scale=1, min_width=40)
285
+
286
+ # 3. Logged In State: Info + Logout (Hidden initially)
287
+ with gr.Group(visible=False) as logged_in_group:
288
+ with gr.Row(elem_classes="no-gap"):
289
+ student_info_disp = gr.Markdown(value="", elem_id="student-info")
290
+ logout_btn = gr.Button("Log Out", elem_classes="link-btn")
291
+
292
+ # --- Main Layout ---
293
+ with gr.Row():
294
+
295
+ # === Left Sidebar ===
296
+ with gr.Column(scale=1, min_width=200):
297
+ clear_btn = gr.Button("Reset Conversation", variant="stop")
298
+
299
+ gr.Markdown("### Model Settings")
300
+ model_name = gr.Textbox(label="Model", value="gpt-4.1-mini", interactive=False, lines=1)
301
+ language_preference = gr.Radio(choices=["Auto", "English", "简体中文"], value="Auto", label="Language")
302
 
303
+ learning_mode = gr.Radio(
304
+ choices=LEARNING_MODES,
305
+ value="Concept Explainer",
306
+ label="Learning Mode",
307
+ info="See User Guide for mode definitions details."
308
+ )
309
+
310
+ # User Guide
311
+ with gr.Accordion("User Guide", open=True, elem_classes="main-user-guide"):
312
+ with gr.Accordion("Getting Started", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["getting_started"])
313
+ with gr.Accordion("Mode Definition", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["mode_definition"])
314
+ with gr.Accordion("How Clare Works", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["how_clare_works"])
315
+ with gr.Accordion("What is Memory Line", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["memory_line"])
316
+ with gr.Accordion("Learning Progress Report", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["learning_progress"])
317
+ with gr.Accordion("How Clare Uses Your Files", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["how_files"])
318
+ with gr.Accordion("Micro-Quiz", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["micro_quiz"])
319
+ with gr.Accordion("Summarization", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["summarization"])
320
+ with gr.Accordion("Export Conversation", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["export_conversation"])
321
+ with gr.Accordion("FAQ", open=False, elem_classes="clean-accordion"): gr.Markdown(USER_GUIDE_SECTIONS["faq"])
322
 
323
+ gr.Markdown("---")
324
+ gr.Button("System Settings", size="sm", variant="secondary")
325
 
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
+ # --- Top Control Row ---
338
+ with gr.Row():
339
+ # Upload Box
340
+ with gr.Column(scale=2):
341
+ syllabus_file = gr.File(
342
+ file_types=[".docx", ".pdf", ".pptx"],
343
+ file_count="single",
344
+ height=160,
345
+ label="Upload file (.docx/.pdf/.pptx)"
346
+ )
347
+ # Controls
348
+ with gr.Column(scale=1):
349
+ doc_type = gr.Dropdown(choices=DOC_TYPES, value="Syllabus", label="File type", container=True)
350
+ gr.HTML("<div style='height:5px'></div>")
351
+ docs_btn = gr.Button("📂 Loaded Docs", size="sm", variant="secondary")
352
+ # Memory Line
353
+ with gr.Column(scale=2):
354
+ with gr.Group(elem_classes="memory-line-box"):
355
+ gr.HTML(
356
+ f"""
357
+ <div style="font-weight:bold; font-size:14px; margin-bottom:5px;">
358
+ <span class="html-tooltip" data-tooltip="See User Guide for explanation">Memory Line</span>
359
  </div>
360
+ <div style="position: relative; height: 35px; margin-top: 10px; margin-bottom: 5px;">
361
+ <div style="position: absolute; bottom: 5px; left: 0; width: 100%; height: 8px; background-color: #e5e7eb; border-radius: 4px;"></div>
362
+ <div style="position: absolute; bottom: 5px; left: 0; width: 40%; height: 8px; background-color: #8B1A1A; border-radius: 4px 0 0 4px;"></div>
363
+ <img src="{image_to_base64(CLARE_RUN_PATH)}" style="position: absolute; left: 36%; bottom: 8px; height: 35px; z-index: 10;">
364
  </div>
365
+ <div style="display:flex; justify-content:space-between; align-items:center;">
366
+ <div style="font-size: 12px; color: #666;">Next Review: T+7</div>
367
+ <div style="font-size: 12px; color: #004a99; text-decoration:underline; cursor:pointer;">Report ⬇️</div>
368
+ </div>
369
+ """
370
+ )
371
+ review_btn = gr.Button("Review Now", size="sm", variant="primary")
372
+ session_status = gr.Markdown(visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
 
374
+ # Chat
375
+ gr.Markdown(
376
+ """
377
+ <div style="background-color:#f9fafb; padding:10px; border-radius:5px; margin-top:10px; font-size:0.9em; color:#555;">
378
+ ✦ <b>Instruction:</b> Use different learning modes to change Clare's teaching style.<br>
379
+ ✦ <b>Example:</b> "Why does context length matter?" or "How does RAG reduce hallucination?"
380
+ </div>
381
+ """
382
+ )
383
+ chatbot = gr.Chatbot(label="", height=450, avatar_images=(None, CLARE_LOGO_PATH), show_label=False, bubble_full_width=False)
384
+ 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)
385
+
386
+ # === Right Sidebar ===
387
+ with gr.Column(scale=1, min_width=180):
388
+ gr.HTML("<div style='height: 210px; width: 100%;'></div>") # Spacer
389
 
390
+ gr.Markdown("### Actions")
391
+ export_btn = gr.Button("Export Conversation", size="sm", elem_classes="action-btn")
392
+ quiz_btn = gr.Button("Let's Try (Micro-Quiz)", size="sm", elem_classes="action-btn")
393
+ summary_btn = gr.Button("Summarization", size="sm", elem_classes="action-btn")
394
+
395
+ gr.Markdown("### Results")
396
+ with gr.Group(elem_classes="result-box"):
397
+ result_display = gr.Markdown(value="Results will appear here...", label="Generated Content")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
 
399
+ # ================== Logic: Login Flow ==================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
 
401
+ # 1. 点击 "Student Login" -> 显示输入框
402
+ def show_login_form():
403
+ return {
404
+ login_start_btn: gr.update(visible=False),
405
+ login_form_group: gr.update(visible=True)
406
+ }
407
+
408
+ login_start_btn.click(show_login_form, outputs=[login_start_btn, login_form_group])
409
 
410
+ # 2. 点击 "✅" -> 确认登录 -> 显示信息
411
+ def confirm_login(name, id_val):
412
+ if not name or not id_val:
413
+ # 简单校验,如果为空就不跳转 (实际可加提示)
414
+ return gr.update(), gr.update(), gr.update(), gr.update()
415
 
416
+ display_text = f"🎓 **{name}** ({id_val})"
 
417
  return {
418
+ login_form_group: gr.update(visible=False),
419
+ logged_in_group: gr.update(visible=True),
420
+ student_info_disp: gr.update(value=display_text),
 
421
  user_name_state: name,
422
  user_id_state: id_val
423
  }
424
 
425
+ login_confirm_btn.click(
426
+ confirm_login,
427
+ inputs=[name_input, id_input],
428
+ outputs=[login_form_group, logged_in_group, student_info_disp, user_name_state, user_id_state]
429
+ )
430
+
431
+ # 3. 点击 "Log Out" -> 重置回最初状态
432
+ def logout():
433
  return {
434
+ logged_in_group: gr.update(visible=False),
435
+ login_start_btn: gr.update(visible=True),
436
+ name_input: gr.update(value=""),
437
+ id_input: gr.update(value=""),
438
+ student_info_disp: gr.update(value="")
439
  }
440
 
441
+ logout_btn.click(logout, outputs=[logged_in_group, login_start_btn, name_input, id_input, student_info_disp])
 
 
 
 
442
 
443
+ # ================== Main App Logic ==================
 
 
 
444
 
 
445
  def update_course_and_rag(file, doc_type_val):
446
  topics = extract_course_topics_from_file(file, doc_type_val)
447
  rag_chunks = build_rag_chunks_from_file(file, doc_type_val)