keyvan-Re commited on
Commit
941b827
·
unverified ·
1 Parent(s): 882c22c

Update app.py

Browse files
Files changed (1) hide show
  1. Emma/app.py +76 -166
Emma/app.py CHANGED
@@ -1,4 +1,4 @@
1
- # -*- coding:utf-8 -*-
2
 
3
  import os
4
  import sys
@@ -10,6 +10,7 @@ import signal
10
  import logging
11
  import platform
12
  import re
 
13
 
14
  import gradio as gr
15
  import nltk
@@ -49,7 +50,6 @@ tokenizer = tiktoken.get_encoding("cl100k_base")
49
  GAPGPT_BASE_URL = os.getenv("GAPGPT_BASE_URL", "https://api.gapgpt.app/v1")
50
  openai_client_cache = {}
51
 
52
- import torch
53
  import torch.nn.functional as F
54
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
55
 
@@ -173,14 +173,14 @@ def chatgpt_chat(prompt, system, history, gpt_config, api_index=0):
173
  "content": f"Hi! I'm {boot_actual_name}! I will give you warm companion!"},
174
  ]
175
 
176
- # --- FIX: Handle History as List of Dicts ---
177
  if history:
178
  # Verify if history is old format (list of lists) or new (list of dicts)
179
  if isinstance(history[0], list):
180
- # Convert old format temporarily if encountered
181
- for q, a in history:
182
- message.append({"role": "user", "content": str(q)})
183
- message.append({"role": "assistant", "content": str(a)})
184
  elif isinstance(history[0], dict):
185
  # New format
186
  for msg in history:
@@ -195,7 +195,7 @@ def chatgpt_chat(prompt, system, history, gpt_config, api_index=0):
195
 
196
  client = get_gapgpt_client(api_keys[api_index])
197
 
198
- # New OpenAI 1.x Syntax
199
  response = client.chat.completions.create(messages=message, **request)
200
 
201
  except Exception as e:
@@ -219,8 +219,6 @@ def chatgpt_chat(prompt, system, history, gpt_config, api_index=0):
219
 
220
 
221
  def classify_query_local(text):
222
-
223
-
224
  # 0: episodic, 1: semantic, 2: semantic_episodic, 3: unrelated
225
  id2label_map = {
226
  0: "episodic_memory",
@@ -230,7 +228,6 @@ def classify_query_local(text):
230
  }
231
 
232
  try:
233
-
234
  inputs = _tokenizer(
235
  text,
236
  return_tensors="pt",
@@ -239,17 +236,13 @@ def classify_query_local(text):
239
  max_length=512
240
  )
241
 
242
-
243
-
244
  with torch.no_grad():
245
  outputs = _classifier_model(**inputs)
246
 
247
-
248
  logits = outputs.logits
249
  probabilities = F.softmax(logits, dim=-1)
250
  predicted_class_id = torch.argmax(probabilities, dim=-1).item()
251
 
252
-
253
  category = id2label_map.get(predicted_class_id, "unknown")
254
 
255
  print(f"Local Classifier: '{text}' -> {category} (Class ID: {predicted_class_id})")
@@ -260,7 +253,6 @@ def classify_query_local(text):
260
  return "unknown"
261
 
262
 
263
-
264
  def classify_query_openai(text, gpt_config, api_index=0, retry_times=5):
265
  response = None
266
  count = 0
@@ -283,7 +275,7 @@ Output ONLY one of these three strings: 'episodic_memory', 'semantic_memory', or
283
  try:
284
  client = get_gapgpt_client(api_keys[api_index])
285
 
286
- # New OpenAI 1.x Syntax
287
  response = client.chat.completions.create(
288
  messages=[
289
  {"role": "system", "content": system_prompt},
@@ -329,8 +321,6 @@ def predict_new(
329
  semantic_memory_text,
330
  query_category,
331
  user_profile=None
332
-
333
-
334
  ):
335
  chatgpt_cfg = {
336
  "model": "gpt-4o",
@@ -352,7 +342,7 @@ def predict_new(
352
 
353
  system_prompt, related_memos = build_prompt_with_search_memory_llamaindex(
354
  history=history,
355
- query=text, # Changed name to match prompt_utils
356
  user_memory=user_memory,
357
  user_name=user_name,
358
  user_memory_index=user_memory_index,
@@ -370,7 +360,7 @@ def predict_new(
370
  )
371
 
372
  # ==========================================
373
- # اضافه کردن پروفایل به System Prompt
374
  # ==========================================
375
  if user_profile:
376
  profile_str = (
@@ -384,10 +374,9 @@ def predict_new(
384
  system_prompt += profile_str
385
  # ==========================================
386
 
387
- # Handle context window slicing manually if needed,
388
- # though usually OpenAI manages this, or we slice the list of dicts.
389
  current_history_for_llm = history
390
- if len(history) > data_args.max_history * 2: # *2 because 1 user + 1 bot
391
  current_history_for_llm = history[-(data_args.max_history * 2):]
392
 
393
  response = chatgpt_chat(
@@ -400,31 +389,24 @@ def predict_new(
400
 
401
  torch.cuda.empty_cache()
402
 
403
- # --- FIX: Update History with Dictionaries (Gradio Chatbot format) ---
404
  new_history = history + [
405
  {"role": "user", "content": text},
406
  {"role": "assistant", "content": response}
407
  ]
408
 
409
- # Save memory logic (Needs to handle dict format, assuming save_local_memory can handle it
410
- # OR we convert strictly for saving if your legacy code needs it.
411
- # For now, assuming we pass the object as is or convert for save)
412
  if user_name:
413
- # If save_local_memory expects list of lists, we might need to adapt it inside that function
414
- # or pass a converted version. Let's try passing the new format.
415
  save_local_memory(memory, new_history, user_name, data_args)
416
 
417
  # Return: (Chatbot View, State History, Textbox Reset)
418
  return new_history, new_history, "Generating..."
419
 
420
 
421
-
422
  def create_gradio_interface(service_context, api_keys):
423
-
424
-
425
- # ابتدا متغیر CSS را تعریف کنید
426
  custom_css = """
427
- /* استایل دکمه ارسال (Send) */
428
  .send-btn {
429
  border: 3px solid #0066cc !important;
430
  border-radius: 12px !important;
@@ -439,9 +421,9 @@ def create_gradio_interface(service_context, api_keys):
439
  color: white !important;
440
  }
441
 
442
- /* استایل نوار پایین و دکمه‌های آن */
443
  .bottom-actions-row {
444
- background-color: #e5e7eb !important; /* رنگ خاکستری پس‌زمینه */
445
  border-radius: 8px !important;
446
  padding: 5px !important;
447
  margin-top: 10px !important;
@@ -457,36 +439,37 @@ def create_gradio_interface(service_context, api_keys):
457
  }
458
 
459
  .action-btn:hover {
460
- background-color: #d1d5db !important; /* تغییر رنگ ملایم هنگام هاور */
461
  border-radius: 8px !important;
462
  }
 
463
  /* ========================================== */
464
- /* لودینگ تمام‌صفحه و غیرفعال‌سازی تعاملات */
465
  /* ========================================== */
466
 
467
- /* مخفی کردن نوار پیشرفت پیش‌فرض Gradio */
468
  .progress-level, .progress-text, .progress-level svg, .progress-level img {
469
  display: none !important;
470
  }
471
 
472
- /* وقتی کلاسی به نام progress-level در صفحه ایجاد شود، این استایل‌ها روی body اعمال می‌شود */
473
  body:has(.progress-level) {
474
- pointer-events: none !important; /* غیرفعال کردن تمام کلیک‌ها */
475
  }
476
 
477
- /* ایجاد هاله نیمه‌شفاف روی کل صفحه */
478
  body:has(.progress-level)::after {
479
  content: "";
480
  position: fixed;
481
  top: 0; left: 0;
482
  width: 100vw; height: 100vh;
483
- background-color: rgba(255, 255, 255, 0.7); /* رنگ سفید نیمه‌شفاف */
484
  z-index: 9998;
485
- pointer-events: all; /* برای گرفتن کلیک‌ها و جلوگیری از عبور آن‌ها */
486
  cursor: wait;
487
  }
488
 
489
- /* ایجاد اسپینر لودینگ در مرکز صفحه */
490
  body:has(.progress-level)::before {
491
  content: "";
492
  position: fixed;
@@ -505,21 +488,7 @@ body:has(.progress-level)::before {
505
  100% { transform: translate(-50%, -50%) rotate(360deg); }
506
  }
507
 
508
-
509
- /* انیمیشن چرخش */
510
- @keyframes spin {
511
- 0% { transform: rotate(0deg); }
512
- 100% { transform: rotate(360deg); }
513
- }
514
-
515
- /* تغییر کلمه Processing (اختیاری) - مخفی کردن متن پیش فرض */
516
- /* اگر می‌خواهید کلا متنی نباشد و فقط دایره بچرخد، کد زیر را از کامنت در بیاورید */
517
- /*
518
- .progress-text {
519
- display: none !important;
520
- }
521
- */
522
- /* انیمیشن سه نقطه در حال تایپ */
523
  .typing-dots::after {
524
  content: '';
525
  animation: typing 1.5s infinite;
@@ -531,22 +500,16 @@ body:has(.progress-level)::before {
531
  75% { content: ' . . .'; }
532
  100% { content: ''; }
533
  }
534
-
535
-
536
  """
537
 
538
  with gr.Blocks(title="EMMA") as demo:
539
- # حالا متغیر را به HTML پاس دهید (بدون علامت مساوی)
540
  gr.HTML(f"<style>{custom_css}</style>")
541
 
542
- # بقیه کدهای شما...
543
-
544
-
545
  state = gr.State({
546
  "history": [],
547
  "user_name": None,
548
- "memory": memory, # فرض بر این است که متغیر memory خارج از تابع تعریف شده
549
- "data_args": data_args, # فرض بر این است که data_args خارج از تابع تعریف شده
550
  "service_context": service_context,
551
  "api_keys": api_keys,
552
  "api_index": 0,
@@ -555,17 +518,11 @@ body:has(.progress-level)::before {
555
  "initialized": False
556
  })
557
 
558
-
559
-
560
- # فرض بر این است که این کد داخل with gr.Blocks() قرار دارد
561
  with gr.Row():
562
-
563
- # ==========================================
564
- # 1. LOGIN PAGE (سایدبار سمت چپ)
565
  # ==========================================
566
- # استفاده از gr.Sidebar به جای gr.Column و gr.Accordion
 
567
  with gr.Sidebar(open=True) as login_page:
568
-
569
  gr.Markdown("<h1 style='text-align: center;'>🧠 EMMA</h1><h3 style='text-align: center;'>Your Empathetic Mental Health Assistant</h3><br><p style='text-align: center;'>Please login or register to begin.</p>")
570
 
571
  username_input = gr.Textbox(label="Your Name (Required)", placeholder="e.g., Alex")
@@ -581,26 +538,20 @@ body:has(.progress-level)::before {
581
  residence_input = gr.Textbox(label="Place of Residence", placeholder="e.g., Berlin")
582
  register_btn = gr.Button("🎯 Complete Registration", variant="primary", elem_classes=["login-btn"])
583
 
584
-
585
  # ==========================================
586
- # 2. CHAT PAGE (ستون سمت راست)
587
  # ==========================================
588
- # مقدار visible=False حذف شده و scale=3 اضافه شده است تا بخش چت بزرگتر باشد
589
  with gr.Column(scale=3, elem_classes=["chat-box"]) as chat_page:
590
  active_header = gr.Markdown("<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session</h2>")
591
- #system_msg = gr.Textbox(label="🔔 System Messages", interactive=False, max_lines=2)
592
 
593
  with gr.Group():
594
  chatbot = gr.Chatbot(label="💬 EMMA Conversation", height=350)
595
 
596
  with gr.Row():
597
  user_input = gr.Textbox(placeholder="Type your message here...", show_label=False, scale=6)
598
- # اختصاص کلاس send-btn به دکمه Send
599
  submit_btn = gr.Button("📤 Send", elem_classes=["send-btn"], scale=1)
600
 
601
- # اختصاص کلاس bottom-actions-row به ردیف دکمه‌های پایین
602
  with gr.Row(equal_height=True, elem_classes=["bottom-actions-row"]):
603
- # اختصاص کلاس action-btn به دکمه‌های عملیاتی
604
  clear_btn = gr.Button("🧹 Clear", elem_classes=["action-btn"])
605
  new_session_btn = gr.Button("🔄 New Session", elem_classes=["action-btn"])
606
  switch_user_btn = gr.Button("👥 Logout", elem_classes=["action-btn"])
@@ -623,17 +574,14 @@ body:has(.progress-level)::before {
623
  new_state["initialized"] = True
624
  new_state["semantic_memory_text"] = user_memory.get("semantic_memory", {})
625
  new_state["semantic_memory_index"] = semantic_memory
626
- new_state["user_memory_index"] = sessions_memory # یا هر نامی که ایندکس در آن است
627
  new_state["episodic_memory_index"] = episodic_memory
628
  new_state["history"] = []
629
 
630
-
631
-
632
-
633
  welcome_msg = hello_msg if hello_msg else f"Welcome back, {name}!"
634
 
635
  return (
636
- gr.update(open=False), # 2. افزودن کلاس انیمیشن به جای visible=False
637
  gr.update(visible=False),
638
  gr.update(visible=True),
639
  gr.update(visible=False, value=""),
@@ -643,7 +591,7 @@ body:has(.progress-level)::before {
643
  )
644
  else:
645
  return (
646
- gr.update(), # تغییری در صفحه اصلی لاگین ایجاد نمیشود
647
  gr.update(visible=True),
648
  gr.update(visible=False),
649
  gr.update(visible=True, value="📝 New user detected. Please fill the details below."),
@@ -724,7 +672,7 @@ body:has(.progress-level)::before {
724
  ),
725
  gr.update(value=welcome_msg),
726
  new_state,
727
- )
728
 
729
  def switch_user(state):
730
  if state["initialized"] and state["user_name"] in state["memory"]:
@@ -732,9 +680,8 @@ body:has(.progress-level)::before {
732
  previous_session = state["memory"][state["user_name"]]["sessions"][-1]
733
  summary = extract_session_summary(previous_session["conversation"], previous_session["date"], len(state["memory"][state["user_name"]]["sessions"]) - 1)
734
  state["memory"][state["user_name"]]["episodic_memory"].append(summary)
735
- # ==========================================
736
- # به‌روزرسانی حافظه سمانتیک زمان خروج
737
- # ==========================================
738
  try:
739
  user_mem = state["memory"][state["user_name"]]
740
  existing_semantic = user_mem.get("semantic_memory", {})
@@ -744,32 +691,29 @@ body:has(.progress-level)::before {
744
  state["memory"][state["user_name"]]["semantic_memory"] = updated_semantic
745
  except Exception as e:
746
  print(f"Error updating semantic memory on logout: {e}")
747
- # ==========================================
748
 
749
  new_state = state.copy()
750
  new_state.update({"history": [], "user_name": None, "semantic_memory_text": "", "initialized": False})
751
 
752
  return (
753
- gr.update(open=True), # 1. باز شدن سایدبار
754
- gr.update(visible=False), # 2. بسته شدن فرم ثبت‌نام
755
- gr.update(visible=False), # 3. مخفی شدن صفحه چت
756
- gr.update(value="Logged out successfully.", visible=True), # 4. پیام خروج
757
- gr.update(value=""), # 5. پاک کردن نام کاربری
758
- new_state, # 6. استیت جدید
759
- gr.update(value=[]), # 7. پاک کردن چت‌بات
760
- gr.update(value=""), # 8. پاک کردن age_input
761
- gr.update(value=""), # 9. پاک کردن gender_input
762
- gr.update(value=""), # 10. پاک کردن occupation_input
763
- gr.update(value=""), # 11. پاک کردن residence_input
764
- gr.update(value="") # 12. پاک کردن پیام سیستم (system_msg) اضافه شد
765
  )
766
 
767
-
768
  def handle_chat(user_message, state):
769
  if not user_message.strip():
770
  print("[handle_chat] empty message")
771
  print("[handle_chat] state =", state)
772
- # تبدیل return به yield
773
  yield gr.update(), state.get("history", []), state
774
  return
775
 
@@ -783,41 +727,34 @@ body:has(.progress-level)::before {
783
  print("[handle_chat] semantic_memory_text =", repr(state.get("semantic_memory_text", "")))
784
  print("[handle_chat] history len =", len(state.get("history", [])))
785
 
786
- # ==========================================
787
- # ۱. نمایش فوری پیام کاربر و انیمیشن لودینگ
788
- # ==========================================
789
  current_history = state.get("history", [])
790
  temp_history = current_history + [
791
  {"role": "user", "content": user_message},
792
  {"role": "assistant", "content": "<span class='typing-dots'>Generating</span>"}
793
  ]
794
 
795
- # باکس متنی را پاک کرده و وضعیت موقت را بلافاصله به UI می‌فرستیم
796
  yield gr.update(value=""), temp_history, state
797
 
798
-
799
- # ا��امه پردازش‌های اصلی تابع شما
800
  user_name = state.get("user_name")
801
  user_memory_index = state.get("user_memory_index")
802
  user_memory = state.get("user_memory", {})
803
- user_memory_index = state.get("user_memory_index")
804
  semantic_memory_text = state.get("semantic_memory_text", {})
805
  service_context = state.get("service_context")
806
  api_index = state.get("api_index", 0)
807
  semantic_memory_text = state.get("semantic_memory_text", "")
808
 
809
- # ==========================================
810
- # استخراج پروفایل کاربر از حافظه
811
- # ==========================================
812
  user_profile = {}
813
  if user_name and state.get("memory") and user_name in state["memory"]:
814
  user_profile = state["memory"][user_name].get("profile", {})
815
- # ==========================================
816
 
817
  query_category = classify_query_local(user_message)
818
  print("[handle_chat] query_category =", query_category)
819
 
820
- # ارسال current_history به مدل (بدون انیمیشن موقت)
821
  new_history, _, status_msg = predict_new(
822
  text=user_message,
823
  history=current_history,
@@ -841,9 +778,7 @@ body:has(.progress-level)::before {
841
  state["history"] = new_history
842
  print("[handle_chat] updated state history len =", len(state.get("history", [])))
843
 
844
- # ==========================================
845
- # ۳. جایگزینی پیام لودینگ با پاسخ نهایی
846
- # ==========================================
847
  yield gr.update(), new_history, state
848
 
849
  def clear_history(state):
@@ -851,49 +786,39 @@ body:has(.progress-level)::before {
851
  return [], state
852
 
853
  def handle_new_session(state):
854
- # بررسی اینکه آیا کاربر لاگین کرده است یا خیر
855
  user_name = state.get("user_name")
856
  if not user_name or "memory" not in state or user_name not in state["memory"]:
857
  print("Error: User not logged in or memory missing.")
858
- # خروجی اول برای chatbot (خالی) و خروجی دوم برای state
859
- return [], state, gr.update() # خروجی سوم اضافه شد
860
 
861
- # دریافت اطلاعات کاربر فعلی از داخل حافظه کلی
862
  user_data = state["memory"][user_name]
863
 
864
- # --- بخش اول: استخراج حافظه از جلسه قبلی ---
865
  if user_data.get("sessions") and len(user_data["sessions"]) > 0:
866
  last_session = user_data["sessions"][-1]
867
 
868
- # فقط در صورتی حافظه را استخراج کن که جلسه قبلی خالی نباشد
869
  if len(last_session.get("conversation", [])) > 0:
870
  session_date = last_session.get("date", "")
871
  session_id = last_session.get("session_id", 0)
872
 
873
- # پاس دادن هر ۳ آرگومان به تابع
874
  ep_summary = extract_session_summary(last_session["conversation"], session_date, session_id)
875
 
876
  if "episodic_memory" not in user_data:
877
  user_data["episodic_memory"] = []
878
  user_data["episodic_memory"].append(ep_summary)
879
 
880
- # ==========================================
881
- # به‌روزرسانی حافظه سمانتیک
882
- # ==========================================
883
  try:
884
- # دریافت حافظه سمانتیک قبلی کاربر (اگر نداشت، یک دیکشنری خالی می‌دهیم)
885
  existing_semantic = user_data.get("semantic_memory", {})
886
-
887
- # فراخوانی تابع با هر دو آرگومان
888
  updated_semantic = extract_semantic_memory(ep_summary, existing_semantic)
889
-
890
- # ذخیره دیتای جدید
891
  user_data["semantic_memory"] = updated_semantic
892
  except Exception as e:
893
  print(f"Error updating semantic memory: {e}")
894
- # ==========================================
895
 
896
- # --- بخش دوم: ایجاد جلسه جدید با اختصاص ID ---
897
  new_session_id = len(user_data.get("sessions", []))
898
 
899
  new_session = {
@@ -907,66 +832,55 @@ body:has(.progress-level)::before {
907
 
908
  user_data["sessions"].append(new_session)
909
 
910
- # پاک کردن تاریخچه چت در state
911
  state["history"] = []
912
 
913
- # محاسبه شماره جلسه فعلی (چون از 0 شروع میشود، +1 میکنیم)
914
  session_number = len(user_data["sessions"])
915
  new_header_text = f"<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session {session_number} for {user_name}</h2>"
916
 
917
- # بازگرداندن خروجی جدید برای active_header
918
  return [], state, gr.update(value=new_header_text)
919
 
920
  check_user_btn.click(
921
  handle_login_check,
922
  inputs=[username_input, state],
923
  outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
924
- # show_progress="hidden"
925
  )
926
 
927
  register_btn.click(
928
  handle_register,
929
  inputs=[username_input, age_input, gender_input, occupation_input, residence_input, state],
930
  outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
931
- # show_progress="hidden"
932
  )
933
 
934
  switch_user_btn.click(
935
  switch_user,
936
  inputs=[state],
937
- # system_msg به انتهای لیست خروجی‌ها اضافه شد
938
  outputs=[login_page, registration_fields, chat_page, login_status, username_input, state, chatbot, age_input, gender_input, occupation_input, residence_input, system_msg],
939
- # show_progress="hidden"
940
  )
941
 
942
-
943
  submit_btn.click(
944
  handle_chat,
945
  inputs=[user_input, state],
946
  outputs=[user_input, chatbot, state],
947
- # show_progress="hidden"
948
  )
949
 
950
  user_input.submit(
951
  handle_chat,
952
  inputs=[user_input, state],
953
  outputs=[user_input, chatbot, state],
954
- # show_progress="hidden"
955
  )
956
 
957
  clear_btn.click(
958
  clear_history,
959
  inputs=[state],
960
  outputs=[chatbot, state],
961
- # show_progress="hidden"
962
  )
963
 
964
- # تغییر از clear_history به handle_new_session
965
  new_session_btn.click(
966
  handle_new_session,
967
  inputs=[state],
968
- outputs=[chatbot, state, active_header], # active_header به خروجی اضافه شد
969
- # show_progress="hidden"
970
  )
971
 
972
  return demo
@@ -979,10 +893,10 @@ def main():
979
  if not gapgpt_api_key:
980
  print("Warning: GAPGPT_API_KEY environment variable is not set. Proceeding with keys from file.")
981
  else:
982
- # تغییر مهم ۱: ست کردن متغیر محیطی برای جلوگیری از خطاهای ناگهانی کتابخانه‌های وابسته
983
  os.environ["OPENAI_API_KEY"] = gapgpt_api_key
984
 
985
- # تنظیم LLM (مدل زبانی)
986
  llm = LlamaIndexOpenAI(
987
  model="gpt-4o",
988
  temperature=1,
@@ -994,17 +908,16 @@ def main():
994
  api_base=GAPGPT_BASE_URL,
995
  )
996
 
997
- # تغییر مهم ۲: تنظیم مدل Embedding با همان کلید و آدرس GapGPT
998
- # اگر سرویس دهنده شما از امبدینگ پشتیبانی نمی‌کند، این بخش نیاز به تغییر به مدل لوکال دارد
999
  embed_model = OpenAIEmbedding(
1000
  api_key=gapgpt_api_key,
1001
  api_base=GAPGPT_BASE_URL,
1002
- model="text-embedding-3-small" # یا هر مدلی که سرویس شما پشتیبانی می‌کند
1003
  )
1004
 
1005
- # اعمال تنظیمات سراسری
1006
  Settings.llm = llm
1007
- Settings.embed_model = embed_model # <--- این خط جلوی خطای فعلی را می‌گیرد
1008
 
1009
  Settings.prompt_helper = PromptHelper(
1010
  context_window=4096,
@@ -1013,11 +926,8 @@ def main():
1013
  tokenizer=tokenizer,
1014
  )
1015
 
1016
- # توجه: مطمئن شوید متغیر api_keys در اینجا تعریف شده باشد یا از args خوانده شود
1017
- # اگر api_keys در کد شما تعریف نشده، احتمالاً باید آن را بارگذاری کنید یا اگر استفاده نمی‌شود حذف کنید.
1018
- # فرض بر این است که api_keys قبلاً در کد شما تعریف شده است:
1019
  if 'api_keys' not in locals():
1020
- api_keys = {} # یا هر مقداری که کد شما انتظار دارد
1021
 
1022
  demo = create_gradio_interface(Settings, api_keys)
1023
  demo.launch(
 
1
+ # -*- coding: utf-8 -*-
2
 
3
  import os
4
  import sys
 
10
  import logging
11
  import platform
12
  import re
13
+ import datetime
14
 
15
  import gradio as gr
16
  import nltk
 
50
  GAPGPT_BASE_URL = os.getenv("GAPGPT_BASE_URL", "https://api.gapgpt.app/v1")
51
  openai_client_cache = {}
52
 
 
53
  import torch.nn.functional as F
54
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
55
 
 
173
  "content": f"Hi! I'm {boot_actual_name}! I will give you warm companion!"},
174
  ]
175
 
176
+ # --- Handle History as List of Dicts ---
177
  if history:
178
  # Verify if history is old format (list of lists) or new (list of dicts)
179
  if isinstance(history[0], list):
180
+ # Convert old format temporarily if encountered
181
+ for q, a in history:
182
+ message.append({"role": "user", "content": str(q)})
183
+ message.append({"role": "assistant", "content": str(a)})
184
  elif isinstance(history[0], dict):
185
  # New format
186
  for msg in history:
 
195
 
196
  client = get_gapgpt_client(api_keys[api_index])
197
 
198
+ # OpenAI 1.x Syntax
199
  response = client.chat.completions.create(messages=message, **request)
200
 
201
  except Exception as e:
 
219
 
220
 
221
  def classify_query_local(text):
 
 
222
  # 0: episodic, 1: semantic, 2: semantic_episodic, 3: unrelated
223
  id2label_map = {
224
  0: "episodic_memory",
 
228
  }
229
 
230
  try:
 
231
  inputs = _tokenizer(
232
  text,
233
  return_tensors="pt",
 
236
  max_length=512
237
  )
238
 
 
 
239
  with torch.no_grad():
240
  outputs = _classifier_model(**inputs)
241
 
 
242
  logits = outputs.logits
243
  probabilities = F.softmax(logits, dim=-1)
244
  predicted_class_id = torch.argmax(probabilities, dim=-1).item()
245
 
 
246
  category = id2label_map.get(predicted_class_id, "unknown")
247
 
248
  print(f"Local Classifier: '{text}' -> {category} (Class ID: {predicted_class_id})")
 
253
  return "unknown"
254
 
255
 
 
256
  def classify_query_openai(text, gpt_config, api_index=0, retry_times=5):
257
  response = None
258
  count = 0
 
275
  try:
276
  client = get_gapgpt_client(api_keys[api_index])
277
 
278
+ # OpenAI 1.x Syntax
279
  response = client.chat.completions.create(
280
  messages=[
281
  {"role": "system", "content": system_prompt},
 
321
  semantic_memory_text,
322
  query_category,
323
  user_profile=None
 
 
324
  ):
325
  chatgpt_cfg = {
326
  "model": "gpt-4o",
 
342
 
343
  system_prompt, related_memos = build_prompt_with_search_memory_llamaindex(
344
  history=history,
345
+ query=text,
346
  user_memory=user_memory,
347
  user_name=user_name,
348
  user_memory_index=user_memory_index,
 
360
  )
361
 
362
  # ==========================================
363
+ # Add User Profile to System Prompt
364
  # ==========================================
365
  if user_profile:
366
  profile_str = (
 
374
  system_prompt += profile_str
375
  # ==========================================
376
 
377
+ # Handle context window slicing
 
378
  current_history_for_llm = history
379
+ if len(history) > data_args.max_history * 2: # *2 because 1 user + 1 assistant
380
  current_history_for_llm = history[-(data_args.max_history * 2):]
381
 
382
  response = chatgpt_chat(
 
389
 
390
  torch.cuda.empty_cache()
391
 
392
+ # Update History with Dictionaries (Gradio Chatbot format)
393
  new_history = history + [
394
  {"role": "user", "content": text},
395
  {"role": "assistant", "content": response}
396
  ]
397
 
398
+ # Save local memory
 
 
399
  if user_name:
 
 
400
  save_local_memory(memory, new_history, user_name, data_args)
401
 
402
  # Return: (Chatbot View, State History, Textbox Reset)
403
  return new_history, new_history, "Generating..."
404
 
405
 
 
406
  def create_gradio_interface(service_context, api_keys):
407
+ # Custom CSS definitions
 
 
408
  custom_css = """
409
+ /* Style for Send button */
410
  .send-btn {
411
  border: 3px solid #0066cc !important;
412
  border-radius: 12px !important;
 
421
  color: white !important;
422
  }
423
 
424
+ /* Style for bottom actions row and buttons */
425
  .bottom-actions-row {
426
+ background-color: #e5e7eb !important; /* Gray background */
427
  border-radius: 8px !important;
428
  padding: 5px !important;
429
  margin-top: 10px !important;
 
439
  }
440
 
441
  .action-btn:hover {
442
+ background-color: #d1d5db !important; /* Subtle hover color change */
443
  border-radius: 8px !important;
444
  }
445
+
446
  /* ========================================== */
447
+ /* Full-screen loading overlay and interaction lock */
448
  /* ========================================== */
449
 
450
+ /* Hide default Gradio progress bar */
451
  .progress-level, .progress-text, .progress-level svg, .progress-level img {
452
  display: none !important;
453
  }
454
 
455
+ /* Disable pointer events when progress is active */
456
  body:has(.progress-level) {
457
+ pointer-events: none !important;
458
  }
459
 
460
+ /* Semi-transparent backdrop over entire screen */
461
  body:has(.progress-level)::after {
462
  content: "";
463
  position: fixed;
464
  top: 0; left: 0;
465
  width: 100vw; height: 100vh;
466
+ background-color: rgba(255, 255, 255, 0.7);
467
  z-index: 9998;
468
+ pointer-events: all;
469
  cursor: wait;
470
  }
471
 
472
+ /* Loading spinner at screen center */
473
  body:has(.progress-level)::before {
474
  content: "";
475
  position: fixed;
 
488
  100% { transform: translate(-50%, -50%) rotate(360deg); }
489
  }
490
 
491
+ /* Typing dots animation */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  .typing-dots::after {
493
  content: '';
494
  animation: typing 1.5s infinite;
 
500
  75% { content: ' . . .'; }
501
  100% { content: ''; }
502
  }
 
 
503
  """
504
 
505
  with gr.Blocks(title="EMMA") as demo:
 
506
  gr.HTML(f"<style>{custom_css}</style>")
507
 
 
 
 
508
  state = gr.State({
509
  "history": [],
510
  "user_name": None,
511
+ "memory": memory,
512
+ "data_args": data_args,
513
  "service_context": service_context,
514
  "api_keys": api_keys,
515
  "api_index": 0,
 
518
  "initialized": False
519
  })
520
 
 
 
 
521
  with gr.Row():
 
 
 
522
  # ==========================================
523
+ # 1. LOGIN PAGE (Left Sidebar)
524
+ # ==========================================
525
  with gr.Sidebar(open=True) as login_page:
 
526
  gr.Markdown("<h1 style='text-align: center;'>🧠 EMMA</h1><h3 style='text-align: center;'>Your Empathetic Mental Health Assistant</h3><br><p style='text-align: center;'>Please login or register to begin.</p>")
527
 
528
  username_input = gr.Textbox(label="Your Name (Required)", placeholder="e.g., Alex")
 
538
  residence_input = gr.Textbox(label="Place of Residence", placeholder="e.g., Berlin")
539
  register_btn = gr.Button("🎯 Complete Registration", variant="primary", elem_classes=["login-btn"])
540
 
 
541
  # ==========================================
542
+ # 2. CHAT PAGE (Right Column)
543
  # ==========================================
 
544
  with gr.Column(scale=3, elem_classes=["chat-box"]) as chat_page:
545
  active_header = gr.Markdown("<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session</h2>")
 
546
 
547
  with gr.Group():
548
  chatbot = gr.Chatbot(label="💬 EMMA Conversation", height=350)
549
 
550
  with gr.Row():
551
  user_input = gr.Textbox(placeholder="Type your message here...", show_label=False, scale=6)
 
552
  submit_btn = gr.Button("📤 Send", elem_classes=["send-btn"], scale=1)
553
 
 
554
  with gr.Row(equal_height=True, elem_classes=["bottom-actions-row"]):
 
555
  clear_btn = gr.Button("🧹 Clear", elem_classes=["action-btn"])
556
  new_session_btn = gr.Button("🔄 New Session", elem_classes=["action-btn"])
557
  switch_user_btn = gr.Button("👥 Logout", elem_classes=["action-btn"])
 
574
  new_state["initialized"] = True
575
  new_state["semantic_memory_text"] = user_memory.get("semantic_memory", {})
576
  new_state["semantic_memory_index"] = semantic_memory
577
+ new_state["user_memory_index"] = sessions_memory
578
  new_state["episodic_memory_index"] = episodic_memory
579
  new_state["history"] = []
580
 
 
 
 
581
  welcome_msg = hello_msg if hello_msg else f"Welcome back, {name}!"
582
 
583
  return (
584
+ gr.update(open=False),
585
  gr.update(visible=False),
586
  gr.update(visible=True),
587
  gr.update(visible=False, value=""),
 
591
  )
592
  else:
593
  return (
594
+ gr.update(),
595
  gr.update(visible=True),
596
  gr.update(visible=False),
597
  gr.update(visible=True, value="📝 New user detected. Please fill the details below."),
 
672
  ),
673
  gr.update(value=welcome_msg),
674
  new_state,
675
+ )
676
 
677
  def switch_user(state):
678
  if state["initialized"] and state["user_name"] in state["memory"]:
 
680
  previous_session = state["memory"][state["user_name"]]["sessions"][-1]
681
  summary = extract_session_summary(previous_session["conversation"], previous_session["date"], len(state["memory"][state["user_name"]]["sessions"]) - 1)
682
  state["memory"][state["user_name"]]["episodic_memory"].append(summary)
683
+
684
+ # Update semantic memory on logout
 
685
  try:
686
  user_mem = state["memory"][state["user_name"]]
687
  existing_semantic = user_mem.get("semantic_memory", {})
 
691
  state["memory"][state["user_name"]]["semantic_memory"] = updated_semantic
692
  except Exception as e:
693
  print(f"Error updating semantic memory on logout: {e}")
 
694
 
695
  new_state = state.copy()
696
  new_state.update({"history": [], "user_name": None, "semantic_memory_text": "", "initialized": False})
697
 
698
  return (
699
+ gr.update(open=True), # 1. Open sidebar
700
+ gr.update(visible=False), # 2. Hide registration form
701
+ gr.update(visible=False), # 3. Hide chat page
702
+ gr.update(value="Logged out successfully.", visible=True), # 4. Logout message
703
+ gr.update(value=""), # 5. Clear username
704
+ new_state, # 6. New state
705
+ gr.update(value=[]), # 7. Clear chatbot
706
+ gr.update(value=""), # 8. Clear age_input
707
+ gr.update(value=""), # 9. Clear gender_input
708
+ gr.update(value=""), # 10. Clear occupation_input
709
+ gr.update(value=""), # 11. Clear residence_input
710
+ gr.update(value="") # 12. Clear system message
711
  )
712
 
 
713
  def handle_chat(user_message, state):
714
  if not user_message.strip():
715
  print("[handle_chat] empty message")
716
  print("[handle_chat] state =", state)
 
717
  yield gr.update(), state.get("history", []), state
718
  return
719
 
 
727
  print("[handle_chat] semantic_memory_text =", repr(state.get("semantic_memory_text", "")))
728
  print("[handle_chat] history len =", len(state.get("history", [])))
729
 
730
+ # 1. Immediately show user message and loading animation
 
 
731
  current_history = state.get("history", [])
732
  temp_history = current_history + [
733
  {"role": "user", "content": user_message},
734
  {"role": "assistant", "content": "<span class='typing-dots'>Generating</span>"}
735
  ]
736
 
737
+ # Clear text box and yield temporary state immediately to UI
738
  yield gr.update(value=""), temp_history, state
739
 
740
+ # Continue with main processing logic
 
741
  user_name = state.get("user_name")
742
  user_memory_index = state.get("user_memory_index")
743
  user_memory = state.get("user_memory", {})
 
744
  semantic_memory_text = state.get("semantic_memory_text", {})
745
  service_context = state.get("service_context")
746
  api_index = state.get("api_index", 0)
747
  semantic_memory_text = state.get("semantic_memory_text", "")
748
 
749
+ # Extract user profile from memory
 
 
750
  user_profile = {}
751
  if user_name and state.get("memory") and user_name in state["memory"]:
752
  user_profile = state["memory"][user_name].get("profile", {})
 
753
 
754
  query_category = classify_query_local(user_message)
755
  print("[handle_chat] query_category =", query_category)
756
 
757
+ # Send current_history to LLM (without temporary animation)
758
  new_history, _, status_msg = predict_new(
759
  text=user_message,
760
  history=current_history,
 
778
  state["history"] = new_history
779
  print("[handle_chat] updated state history len =", len(state.get("history", [])))
780
 
781
+ # 3. Replace loading indicator with final response
 
 
782
  yield gr.update(), new_history, state
783
 
784
  def clear_history(state):
 
786
  return [], state
787
 
788
  def handle_new_session(state):
789
+ # Check if user is logged in
790
  user_name = state.get("user_name")
791
  if not user_name or "memory" not in state or user_name not in state["memory"]:
792
  print("Error: User not logged in or memory missing.")
793
+ return [], state, gr.update()
 
794
 
795
+ # Retrieve current user data from memory
796
  user_data = state["memory"][user_name]
797
 
798
+ # --- Part 1: Extract memory from previous session ---
799
  if user_data.get("sessions") and len(user_data["sessions"]) > 0:
800
  last_session = user_data["sessions"][-1]
801
 
802
+ # Only extract memory if previous session is not empty
803
  if len(last_session.get("conversation", [])) > 0:
804
  session_date = last_session.get("date", "")
805
  session_id = last_session.get("session_id", 0)
806
 
 
807
  ep_summary = extract_session_summary(last_session["conversation"], session_date, session_id)
808
 
809
  if "episodic_memory" not in user_data:
810
  user_data["episodic_memory"] = []
811
  user_data["episodic_memory"].append(ep_summary)
812
 
813
+ # Update semantic memory
 
 
814
  try:
 
815
  existing_semantic = user_data.get("semantic_memory", {})
 
 
816
  updated_semantic = extract_semantic_memory(ep_summary, existing_semantic)
 
 
817
  user_data["semantic_memory"] = updated_semantic
818
  except Exception as e:
819
  print(f"Error updating semantic memory: {e}")
 
820
 
821
+ # --- Part 2: Create new session with assigned ID ---
822
  new_session_id = len(user_data.get("sessions", []))
823
 
824
  new_session = {
 
832
 
833
  user_data["sessions"].append(new_session)
834
 
835
+ # Clear chat history in state
836
  state["history"] = []
837
 
838
+ # Calculate current session number (+1 since 0-indexed)
839
  session_number = len(user_data["sessions"])
840
  new_header_text = f"<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session {session_number} for {user_name}</h2>"
841
 
 
842
  return [], state, gr.update(value=new_header_text)
843
 
844
  check_user_btn.click(
845
  handle_login_check,
846
  inputs=[username_input, state],
847
  outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
 
848
  )
849
 
850
  register_btn.click(
851
  handle_register,
852
  inputs=[username_input, age_input, gender_input, occupation_input, residence_input, state],
853
  outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
 
854
  )
855
 
856
  switch_user_btn.click(
857
  switch_user,
858
  inputs=[state],
 
859
  outputs=[login_page, registration_fields, chat_page, login_status, username_input, state, chatbot, age_input, gender_input, occupation_input, residence_input, system_msg],
 
860
  )
861
 
 
862
  submit_btn.click(
863
  handle_chat,
864
  inputs=[user_input, state],
865
  outputs=[user_input, chatbot, state],
 
866
  )
867
 
868
  user_input.submit(
869
  handle_chat,
870
  inputs=[user_input, state],
871
  outputs=[user_input, chatbot, state],
 
872
  )
873
 
874
  clear_btn.click(
875
  clear_history,
876
  inputs=[state],
877
  outputs=[chatbot, state],
 
878
  )
879
 
 
880
  new_session_btn.click(
881
  handle_new_session,
882
  inputs=[state],
883
+ outputs=[chatbot, state, active_header],
 
884
  )
885
 
886
  return demo
 
893
  if not gapgpt_api_key:
894
  print("Warning: GAPGPT_API_KEY environment variable is not set. Proceeding with keys from file.")
895
  else:
896
+ # Set environment variable to prevent errors in dependent libraries
897
  os.environ["OPENAI_API_KEY"] = gapgpt_api_key
898
 
899
+ # Configure LLM
900
  llm = LlamaIndexOpenAI(
901
  model="gpt-4o",
902
  temperature=1,
 
908
  api_base=GAPGPT_BASE_URL,
909
  )
910
 
911
+ # Configure embedding model with GapGPT key and base URL
 
912
  embed_model = OpenAIEmbedding(
913
  api_key=gapgpt_api_key,
914
  api_base=GAPGPT_BASE_URL,
915
+ model="text-embedding-3-small"
916
  )
917
 
918
+ # Apply global settings
919
  Settings.llm = llm
920
+ Settings.embed_model = embed_model
921
 
922
  Settings.prompt_helper = PromptHelper(
923
  context_window=4096,
 
926
  tokenizer=tokenizer,
927
  )
928
 
 
 
 
929
  if 'api_keys' not in locals():
930
+ api_keys = {}
931
 
932
  demo = create_gradio_interface(Settings, api_keys)
933
  demo.launch(