Deevyankar commited on
Commit
faedfab
·
verified ·
1 Parent(s): f4b03d8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -88
app.py CHANGED
@@ -11,7 +11,7 @@ from sentence_transformers import SentenceTransformer
11
  from openai import OpenAI
12
 
13
  # =====================================================
14
- # PATHS
15
  # =====================================================
16
  BUILD_DIR = "brainchat_build"
17
  CHUNKS_PATH = os.path.join(BUILD_DIR, "chunks.pkl")
@@ -19,19 +19,9 @@ TOKENS_PATH = os.path.join(BUILD_DIR, "tokenized_chunks.pkl")
19
  EMBED_PATH = os.path.join(BUILD_DIR, "embeddings.npy")
20
  CONFIG_PATH = os.path.join(BUILD_DIR, "config.json")
21
 
22
- LOGO_CANDIDATES = [
23
- "Brain chat-09.png",
24
- "brainchat_logo.png.png",
25
- "Brain Chat Imagen.svg",
26
- "logo.png",
27
- "logo.svg",
28
- ]
29
-
30
  OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
31
 
32
- # =====================================================
33
- # GLOBALS
34
- # =====================================================
35
  BM25 = None
36
  CHUNKS = None
37
  EMBEDDINGS = None
@@ -120,7 +110,12 @@ def make_sources(records):
120
  seen = set()
121
  lines = []
122
  for r in records:
123
- key = (r.get("book"), r.get("section_title"), r.get("page_start"), r.get("page_end"))
 
 
 
 
 
124
  if key in seen:
125
  continue
126
  seen.add(key)
@@ -140,7 +135,7 @@ def language_instruction(language_mode: str) -> str:
140
  return "Answer only in Spanish."
141
  if language_mode == "Bilingual":
142
  return "Answer first in English, then provide a Spanish version under the heading 'Español:'."
143
- return "If the user's message is in Spanish, answer in Spanish. Otherwise answer in English."
144
 
145
 
146
  def choose_quiz_count(user_text: str, selector: str) -> int:
@@ -156,12 +151,12 @@ def choose_quiz_count(user_text: str, selector: str) -> int:
156
 
157
 
158
  def build_tutor_prompt(mode: str, language_mode: str, question: str, context: str) -> str:
159
- mode_map = {
160
- "Explain": "Explain clearly like a friendly tutor using simple language and short headings if useful.",
161
- "Detailed": "Give a fuller and more detailed explanation with key points and clinical relevance if supported by the context.",
162
  "Short Notes": "Write concise revision notes using short bullet points.",
163
  "Flashcards": "Create 6 flashcards in Q/A format using only the context.",
164
- "Case-Based": "Create a short clinical scenario and then explain the concept using the context."
165
  }
166
 
167
  return f"""
@@ -175,7 +170,7 @@ Rules:
175
  - {language_instruction(language_mode)}
176
 
177
  Teaching style:
178
- {mode_map.get(mode, "Explain clearly like a tutor.")}
179
 
180
  Context:
181
  {context}
@@ -192,9 +187,9 @@ You are BrainChat, an interactive tutor.
192
  Rules:
193
  - Use ONLY the provided context.
194
  - Create exactly {n_questions} quiz questions.
195
- - Keep each question short and clear.
196
- - Include a short answer key.
197
- - Return valid JSON only.
198
  - {language_instruction(language_mode)}
199
 
200
  Return JSON in this format:
@@ -222,7 +217,9 @@ You are BrainChat, an interactive tutor.
222
  Evaluate the student's answers fairly using the answer keys.
223
  Accept semantically correct answers even if wording differs.
224
 
225
- Return valid JSON only in this format:
 
 
226
  {{
227
  "score_obtained": 0,
228
  "score_total": 0,
@@ -251,7 +248,7 @@ Language:
251
 
252
 
253
  # =====================================================
254
- # OPENAI HELPERS
255
  # =====================================================
256
  def oai_text(prompt: str) -> str:
257
  ensure_loaded()
@@ -283,18 +280,10 @@ def oai_json(prompt: str) -> dict:
283
  # =====================================================
284
  # LOGO
285
  # =====================================================
286
- def find_logo_filename():
287
- for name in LOGO_CANDIDATES:
288
- if os.path.exists(name):
289
- return name
290
- return None
291
-
292
-
293
  def logo_url():
294
- f = find_logo_filename()
295
- if not f:
296
- return None
297
- return f"/gradio_api/file={quote(f)}"
298
 
299
 
300
  def render_logo():
@@ -305,9 +294,9 @@ def render_logo():
305
 
306
 
307
  # =====================================================
308
- # CHAT HTML RENDER
309
  # =====================================================
310
- def escape_and_format(text: str) -> str:
311
  safe = (
312
  text.replace("&", "&")
313
  .replace("<", "&lt;")
@@ -323,8 +312,7 @@ def render_chat(history):
323
  return """
324
  <div class="bc-empty">
325
  <div class="bc-empty-text">
326
- Choose a tutor mode above and ask a question.<br>
327
- For quiz mode, type a topic such as <strong>cranial nerves</strong>.
328
  </div>
329
  </div>
330
  """
@@ -332,7 +320,7 @@ def render_chat(history):
332
  rows = []
333
  for item in history:
334
  role = item["role"]
335
- content = escape_and_format(item["content"])
336
 
337
  if role == "user":
338
  rows.append(
@@ -348,9 +336,9 @@ def render_chat(history):
348
  {''.join(rows)}
349
  </div>
350
  <script>
351
- const el = document.getElementById("bc-chat-wrap");
352
- if (el) {{
353
- el.scrollTop = el.scrollHeight;
354
  }}
355
  </script>
356
  """
@@ -370,7 +358,6 @@ def respond(user_msg, history, mode, language_mode, quiz_count_mode, show_source
370
  try:
371
  history = history + [{"role": "user", "content": text}]
372
 
373
- # quiz evaluation
374
  if quiz_state.get("active", False):
375
  evaluation = oai_json(
376
  build_quiz_eval_prompt(
@@ -402,11 +389,9 @@ def respond(user_msg, history, mode, language_mode, quiz_count_mode, show_source
402
  quiz_state = {"active": False, "quiz_data": None, "language_mode": language_mode}
403
  return "", history, render_chat(history), quiz_state
404
 
405
- # normal retrieval
406
  records = search_hybrid(text, shortlist_k=20, final_k=4)
407
  context = build_context(records)
408
 
409
- # quiz generation
410
  if mode == "Quiz Me":
411
  n_questions = choose_quiz_count(text, quiz_count_mode)
412
  quiz_data = oai_json(build_quiz_generation_prompt(language_mode, text, context, n_questions))
@@ -414,9 +399,8 @@ def respond(user_msg, history, mode, language_mode, quiz_count_mode, show_source
414
  lines = []
415
  lines.append(f"**{quiz_data.get('title', 'Quiz')}**")
416
  lines.append(f"\n**Total questions:** {len(quiz_data.get('questions', []))}\n")
417
- lines.append("Reply in ONE message with numbered answers, for example:")
418
- lines.append("1. ...")
419
- lines.append("2. ...\n")
420
 
421
  for i, q in enumerate(quiz_data.get("questions", []), start=1):
422
  lines.append(f"**Q{i}.** {q.get('q','')}")
@@ -429,7 +413,6 @@ def respond(user_msg, history, mode, language_mode, quiz_count_mode, show_source
429
  quiz_state = {"active": True, "quiz_data": quiz_data, "language_mode": language_mode}
430
  return "", history, render_chat(history), quiz_state
431
 
432
- # tutor modes
433
  answer = oai_text(build_tutor_prompt(mode, language_mode, text, context))
434
  if show_sources:
435
  answer = answer.strip() + "\n\n**Sources:**\n" + make_sources(records)
@@ -451,24 +434,22 @@ def clear_all():
451
 
452
  # =====================================================
453
  # CSS
454
- # dark but light-friendly, no black background
455
  # =====================================================
456
  CSS = """
457
  :root{
458
- --page-bg: #d9d9dc;
459
- --panel-bg: #4a4c59;
460
- --chat-bg: #363846;
461
  --grad-top: #e8c7d4;
462
  --grad-mid: #a55ca2;
463
- --grad-bot: #53246d;
464
  --accent: #f4eb4b;
465
- --accent-soft: #f7f1a7;
466
- --user-bubble: #f8f8fb;
467
- --bot-bubble: #f7f3b0;
468
- --text-dark: #1b1630;
469
  --text-light: #ffffff;
470
- --input-bg: #f3efc7;
471
- --shadow: rgba(25, 20, 40, 0.18);
472
  }
473
 
474
  html, body, .gradio-container{
@@ -477,25 +458,36 @@ html, body, .gradio-container{
477
  }
478
  footer{display:none !important;}
479
 
480
- #bc-app{
481
- max-width: 980px;
482
  margin: 18px auto;
483
  }
484
 
485
- .bc-top-panel{
486
- background: var(--panel-bg);
487
- border-radius: 18px;
488
- padding: 16px;
489
  box-shadow: 0 10px 24px var(--shadow);
490
  margin-bottom: 14px;
491
  }
492
 
 
 
 
 
 
 
 
 
 
 
493
  .bc-phone{
494
  position: relative;
495
- background: linear-gradient(180deg, var(--grad-top) 0%, var(--grad-mid) 52%, var(--grad-bot) 100%);
496
- border-radius: 26px;
497
- padding: 90px 14px 14px 14px;
498
  box-shadow: 0 16px 34px var(--shadow);
 
499
  }
500
 
501
  .bc-logo-holder{
@@ -536,27 +528,27 @@ footer{display:none !important;}
536
  }
537
 
538
  .bc-chat-shell{
539
- background: var(--chat-bg);
540
- border-radius: 18px;
541
  padding: 16px;
542
- min-height: 480px;
543
- box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
544
  }
545
 
546
  .bc-chat-wrap{
547
  display: flex;
548
  flex-direction: column;
549
  gap: 14px;
550
- max-height: 480px;
551
  overflow-y: auto;
552
  padding-right: 4px;
553
  }
554
 
555
  .bc-chat-wrap::-webkit-scrollbar{
556
- width: 7px;
557
  }
558
  .bc-chat-wrap::-webkit-scrollbar-thumb{
559
- background: rgba(255,255,255,0.25);
560
  border-radius: 999px;
561
  }
562
 
@@ -579,17 +571,18 @@ footer{display:none !important;}
579
  line-height: 1.5;
580
  font-size: 15px;
581
  box-shadow: 0 10px 18px rgba(0,0,0,0.10);
582
- color: var(--text-dark);
583
  word-wrap: break-word;
584
  }
585
 
586
  .bc-user-bubble{
587
  background: var(--user-bubble);
 
588
  border-bottom-left-radius: 8px;
589
  }
590
 
591
  .bc-bot-bubble{
592
  background: var(--bot-bubble);
 
593
  border-bottom-right-radius: 8px;
594
  }
595
 
@@ -597,10 +590,10 @@ footer{display:none !important;}
597
  display:flex;
598
  justify-content:center;
599
  align-items:center;
600
- min-height: 420px;
601
  }
602
  .bc-empty-text{
603
- color: #fdfdfd;
604
  text-align:center;
605
  opacity: 0.96;
606
  font-size: 16px;
@@ -621,7 +614,7 @@ footer{display:none !important;}
621
  width: 38px;
622
  height: 38px;
623
  border-radius: 999px;
624
- background: rgba(255,255,255,0.30);
625
  display:flex;
626
  align-items:center;
627
  justify-content:center;
@@ -632,7 +625,7 @@ footer{display:none !important;}
632
  }
633
 
634
  #bc_msg textarea{
635
- background: rgba(255,255,255,0.38) !important;
636
  border: none !important;
637
  box-shadow: none !important;
638
  border-radius: 999px !important;
@@ -641,7 +634,7 @@ footer{display:none !important;}
641
  min-height: 42px !important;
642
  }
643
  #bc_msg textarea::placeholder{
644
- color: rgba(27,22,48,0.80) !important;
645
  }
646
 
647
  #bc_send button{
@@ -649,14 +642,14 @@ footer{display:none !important;}
649
  height: 42px !important;
650
  border-radius: 999px !important;
651
  border: none !important;
652
- background: rgba(255,255,255,0.32) !important;
653
  color: var(--text-dark) !important;
654
  font-size: 20px !important;
655
  font-weight: 900 !important;
656
  box-shadow: none !important;
657
  }
658
  #bc_send button:hover{
659
- background: rgba(255,255,255,0.50) !important;
660
  }
661
 
662
  #bc_clear button{
@@ -664,7 +657,7 @@ footer{display:none !important;}
664
  }
665
 
666
  @media (max-width: 768px){
667
- #bc-app{
668
  max-width: 96vw;
669
  }
670
  .bc-bubble{
@@ -681,8 +674,8 @@ with gr.Blocks() as demo:
681
  history_state = gr.State([])
682
  quiz_state = gr.State({"active": False, "quiz_data": None, "language_mode": "Auto"})
683
 
684
- with gr.Column(elem_id="bc-app"):
685
- with gr.Group(elem_classes="bc-top-panel"):
686
  with gr.Row():
687
  mode = gr.Dropdown(
688
  choices=["Explain", "Detailed", "Short Notes", "Flashcards", "Case-Based", "Quiz Me"],
@@ -703,6 +696,16 @@ with gr.Blocks() as demo:
703
  )
704
  show_sources = gr.Checkbox(value=True, label="Show Sources")
705
 
 
 
 
 
 
 
 
 
 
 
706
  with gr.Group(elem_classes="bc-phone"):
707
  gr.HTML(f'<div class="bc-logo-holder">{render_logo()}</div>')
708
 
 
11
  from openai import OpenAI
12
 
13
  # =====================================================
14
+ # CONFIG
15
  # =====================================================
16
  BUILD_DIR = "brainchat_build"
17
  CHUNKS_PATH = os.path.join(BUILD_DIR, "chunks.pkl")
 
19
  EMBED_PATH = os.path.join(BUILD_DIR, "embeddings.npy")
20
  CONFIG_PATH = os.path.join(BUILD_DIR, "config.json")
21
 
22
+ LOGO_FILE = "logo.png"
 
 
 
 
 
 
 
23
  OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
24
 
 
 
 
25
  BM25 = None
26
  CHUNKS = None
27
  EMBEDDINGS = None
 
110
  seen = set()
111
  lines = []
112
  for r in records:
113
+ key = (
114
+ r.get("book"),
115
+ r.get("section_title"),
116
+ r.get("page_start"),
117
+ r.get("page_end"),
118
+ )
119
  if key in seen:
120
  continue
121
  seen.add(key)
 
135
  return "Answer only in Spanish."
136
  if language_mode == "Bilingual":
137
  return "Answer first in English, then provide a Spanish version under the heading 'Español:'."
138
+ return "If the user's message is in Spanish, answer in Spanish; otherwise answer in English."
139
 
140
 
141
  def choose_quiz_count(user_text: str, selector: str) -> int:
 
151
 
152
 
153
  def build_tutor_prompt(mode: str, language_mode: str, question: str, context: str) -> str:
154
+ styles = {
155
+ "Explain": "Explain clearly like a friendly tutor using simple language.",
156
+ "Detailed": "Give a more detailed explanation with key points and clinical relevance only when supported by context.",
157
  "Short Notes": "Write concise revision notes using short bullet points.",
158
  "Flashcards": "Create 6 flashcards in Q/A format using only the context.",
159
+ "Case-Based": "Create a short clinical case scenario and then explain the concept using the context.",
160
  }
161
 
162
  return f"""
 
170
  - {language_instruction(language_mode)}
171
 
172
  Teaching style:
173
+ {styles.get(mode, "Explain clearly like a friendly tutor.")}
174
 
175
  Context:
176
  {context}
 
187
  Rules:
188
  - Use ONLY the provided context.
189
  - Create exactly {n_questions} quiz questions.
190
+ - Keep questions short and clear.
191
+ - Include a short answer key for each.
192
+ - Return VALID JSON only.
193
  - {language_instruction(language_mode)}
194
 
195
  Return JSON in this format:
 
217
  Evaluate the student's answers fairly using the answer keys.
218
  Accept semantically correct answers even if wording differs.
219
 
220
+ Return VALID JSON only.
221
+
222
+ Return JSON in this format:
223
  {{
224
  "score_obtained": 0,
225
  "score_total": 0,
 
248
 
249
 
250
  # =====================================================
251
+ # OPENAI
252
  # =====================================================
253
  def oai_text(prompt: str) -> str:
254
  ensure_loaded()
 
280
  # =====================================================
281
  # LOGO
282
  # =====================================================
 
 
 
 
 
 
 
283
  def logo_url():
284
+ if os.path.exists(LOGO_FILE):
285
+ return f"/gradio_api/file={quote(LOGO_FILE)}"
286
+ return None
 
287
 
288
 
289
  def render_logo():
 
294
 
295
 
296
  # =====================================================
297
+ # CHAT HTML
298
  # =====================================================
299
+ def format_text(text: str) -> str:
300
  safe = (
301
  text.replace("&", "&amp;")
302
  .replace("<", "&lt;")
 
312
  return """
313
  <div class="bc-empty">
314
  <div class="bc-empty-text">
315
+ Ask a question and choose a tutor mode above.
 
316
  </div>
317
  </div>
318
  """
 
320
  rows = []
321
  for item in history:
322
  role = item["role"]
323
+ content = format_text(item["content"])
324
 
325
  if role == "user":
326
  rows.append(
 
336
  {''.join(rows)}
337
  </div>
338
  <script>
339
+ const chatWrap = document.getElementById("bc-chat-wrap");
340
+ if (chatWrap) {{
341
+ chatWrap.scrollTop = chatWrap.scrollHeight;
342
  }}
343
  </script>
344
  """
 
358
  try:
359
  history = history + [{"role": "user", "content": text}]
360
 
 
361
  if quiz_state.get("active", False):
362
  evaluation = oai_json(
363
  build_quiz_eval_prompt(
 
389
  quiz_state = {"active": False, "quiz_data": None, "language_mode": language_mode}
390
  return "", history, render_chat(history), quiz_state
391
 
 
392
  records = search_hybrid(text, shortlist_k=20, final_k=4)
393
  context = build_context(records)
394
 
 
395
  if mode == "Quiz Me":
396
  n_questions = choose_quiz_count(text, quiz_count_mode)
397
  quiz_data = oai_json(build_quiz_generation_prompt(language_mode, text, context, n_questions))
 
399
  lines = []
400
  lines.append(f"**{quiz_data.get('title', 'Quiz')}**")
401
  lines.append(f"\n**Total questions:** {len(quiz_data.get('questions', []))}\n")
402
+ lines.append("Reply in one message using numbered answers.")
403
+ lines.append("Example: 1. ... 2. ...\n")
 
404
 
405
  for i, q in enumerate(quiz_data.get("questions", []), start=1):
406
  lines.append(f"**Q{i}.** {q.get('q','')}")
 
413
  quiz_state = {"active": True, "quiz_data": quiz_data, "language_mode": language_mode}
414
  return "", history, render_chat(history), quiz_state
415
 
 
416
  answer = oai_text(build_tutor_prompt(mode, language_mode, text, context))
417
  if show_sources:
418
  answer = answer.strip() + "\n\n**Sources:**\n" + make_sources(records)
 
434
 
435
  # =====================================================
436
  # CSS
 
437
  # =====================================================
438
  CSS = """
439
  :root{
440
+ --page-bg: #d9d9dd;
441
+ --panel-bg: #555765;
442
+ --chat-bg: #4a4c59;
443
  --grad-top: #e8c7d4;
444
  --grad-mid: #a55ca2;
445
+ --grad-bot: #5a2d77;
446
  --accent: #f4eb4b;
447
+ --accent-soft: #f5ef9a;
448
+ --user-bubble: #ffffff;
449
+ --bot-bubble: #f5efad;
450
+ --text-dark: #221735;
451
  --text-light: #ffffff;
452
+ --shadow: rgba(30,20,50,0.18);
 
453
  }
454
 
455
  html, body, .gradio-container{
 
458
  }
459
  footer{display:none !important;}
460
 
461
+ #bc_app{
462
+ max-width: 1000px;
463
  margin: 18px auto;
464
  }
465
 
466
+ .bc-settings{
467
+ background: linear-gradient(180deg, #484b59 0%, #3f424f 100%);
468
+ border-radius: 20px;
469
+ padding: 14px;
470
  box-shadow: 0 10px 24px var(--shadow);
471
  margin-bottom: 14px;
472
  }
473
 
474
+ .bc-howto{
475
+ margin-top: 8px;
476
+ padding: 12px 14px;
477
+ border-radius: 14px;
478
+ background: rgba(255,255,255,0.10);
479
+ color: white;
480
+ font-size: 14px;
481
+ line-height: 1.5;
482
+ }
483
+
484
  .bc-phone{
485
  position: relative;
486
+ background: linear-gradient(180deg, var(--grad-top) 0%, var(--grad-mid) 48%, var(--grad-bot) 100%);
487
+ border-radius: 30px;
488
+ padding: 92px 14px 14px 14px;
489
  box-shadow: 0 16px 34px var(--shadow);
490
+ min-height: 620px;
491
  }
492
 
493
  .bc-logo-holder{
 
528
  }
529
 
530
  .bc-chat-shell{
531
+ background: rgba(74,76,89,0.92);
532
+ border-radius: 20px;
533
  padding: 16px;
534
+ min-height: 460px;
535
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
536
  }
537
 
538
  .bc-chat-wrap{
539
  display: flex;
540
  flex-direction: column;
541
  gap: 14px;
542
+ max-height: 460px;
543
  overflow-y: auto;
544
  padding-right: 4px;
545
  }
546
 
547
  .bc-chat-wrap::-webkit-scrollbar{
548
+ width: 8px;
549
  }
550
  .bc-chat-wrap::-webkit-scrollbar-thumb{
551
+ background: rgba(255,255,255,0.28);
552
  border-radius: 999px;
553
  }
554
 
 
571
  line-height: 1.5;
572
  font-size: 15px;
573
  box-shadow: 0 10px 18px rgba(0,0,0,0.10);
 
574
  word-wrap: break-word;
575
  }
576
 
577
  .bc-user-bubble{
578
  background: var(--user-bubble);
579
+ color: var(--text-dark);
580
  border-bottom-left-radius: 8px;
581
  }
582
 
583
  .bc-bot-bubble{
584
  background: var(--bot-bubble);
585
+ color: var(--text-dark);
586
  border-bottom-right-radius: 8px;
587
  }
588
 
 
590
  display:flex;
591
  justify-content:center;
592
  align-items:center;
593
+ min-height: 400px;
594
  }
595
  .bc-empty-text{
596
+ color: white;
597
  text-align:center;
598
  opacity: 0.96;
599
  font-size: 16px;
 
614
  width: 38px;
615
  height: 38px;
616
  border-radius: 999px;
617
+ background: rgba(255,255,255,0.34);
618
  display:flex;
619
  align-items:center;
620
  justify-content:center;
 
625
  }
626
 
627
  #bc_msg textarea{
628
+ background: rgba(255,255,255,0.42) !important;
629
  border: none !important;
630
  box-shadow: none !important;
631
  border-radius: 999px !important;
 
634
  min-height: 42px !important;
635
  }
636
  #bc_msg textarea::placeholder{
637
+ color: rgba(34,23,53,0.72) !important;
638
  }
639
 
640
  #bc_send button{
 
642
  height: 42px !important;
643
  border-radius: 999px !important;
644
  border: none !important;
645
+ background: rgba(255,255,255,0.34) !important;
646
  color: var(--text-dark) !important;
647
  font-size: 20px !important;
648
  font-weight: 900 !important;
649
  box-shadow: none !important;
650
  }
651
  #bc_send button:hover{
652
+ background: rgba(255,255,255,0.52) !important;
653
  }
654
 
655
  #bc_clear button{
 
657
  }
658
 
659
  @media (max-width: 768px){
660
+ #bc_app{
661
  max-width: 96vw;
662
  }
663
  .bc-bubble{
 
674
  history_state = gr.State([])
675
  quiz_state = gr.State({"active": False, "quiz_data": None, "language_mode": "Auto"})
676
 
677
+ with gr.Column(elem_id="bc_app"):
678
+ with gr.Group(elem_classes="bc-settings"):
679
  with gr.Row():
680
  mode = gr.Dropdown(
681
  choices=["Explain", "Detailed", "Short Notes", "Flashcards", "Case-Based", "Quiz Me"],
 
696
  )
697
  show_sources = gr.Checkbox(value=True, label="Show Sources")
698
 
699
+ gr.HTML("""
700
+ <div class="bc-howto">
701
+ <strong>How to use</strong><br>
702
+ 1. Choose a tutor mode such as Explain, Detailed, Flashcards, or Quiz Me.<br>
703
+ 2. Type your topic or question in the message box below.<br>
704
+ 3. For Quiz Me, the next message you send will be evaluated automatically.<br>
705
+ 4. Turn on Show Sources if you want references from the books.
706
+ </div>
707
+ """)
708
+
709
  with gr.Group(elem_classes="bc-phone"):
710
  gr.HTML(f'<div class="bc-logo-holder">{render_logo()}</div>')
711