keyvan-Re commited on
Commit
ed911d3
·
unverified ·
1 Parent(s): 28694b4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -190
app.py CHANGED
@@ -4,6 +4,7 @@ import os
4
  import sys
5
  import json
6
  import time
 
7
  import copy
8
  import shutil
9
  import signal
@@ -14,12 +15,12 @@ import re
14
  import gradio as gr
15
  import nltk
16
  import torch
 
17
  import tiktoken
 
18
 
19
  from openai import OpenAI as OpenAIClient
20
  from llama_index.embeddings.openai import OpenAIEmbedding
21
-
22
- # from langchain_openai import ChatOpenAI, OpenAI as LangChainOpenAI # (If not used, can be commented out)
23
  from llama_index.llms.openai import OpenAI as LlamaIndexOpenAI
24
  from llama_index.core import Settings
25
  from llama_index.core.indices.prompt_helper import PromptHelper
@@ -27,7 +28,7 @@ from llama_index.core.indices.prompt_helper import PromptHelper
27
  prompt_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../')
28
  sys.path.append(prompt_path)
29
 
30
- # Assuming these imports exist in your local environment
31
  from utils.sys_args import data_args, model_args
32
  from utils.app_modules.utils import *
33
  from utils.app_modules.presets import *
@@ -49,11 +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
-
56
-
57
  LOCAL_MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Classification_Model", "final_xlm_r_model_router")
58
 
59
  print(f"Loading model from: {LOCAL_MODEL_PATH}")
@@ -102,16 +98,15 @@ def read_apis(path):
102
  return api_keys_local
103
 
104
 
105
- # خواندن مسیر دیسک از متغیر محیطی (در صورت نبود، استفاده از مسیر پیش‌فرض)
106
  base_data_dir = os.getenv("EMMA_DATA_DIR", os.path.join(os.path.dirname(os.path.abspath(__file__)), "data"))
107
 
108
- # تنظیم مسیر فایل مموری
109
  memory_dir = os.path.join(base_data_dir, "memories", "update_memory_0512_eng.json")
110
 
111
  # Ensure directory exists
112
  os.makedirs(os.path.dirname(memory_dir), exist_ok=True)
113
 
114
-
115
  if not os.path.exists(memory_dir):
116
  json.dump({}, open(memory_dir, "w", encoding="utf-8"))
117
 
@@ -128,7 +123,6 @@ meta_prompt_semantic_episodic = generate_meta_prompt_dict_semantic_episodic_chat
128
  new_user_meta_prompt = generate_new_user_meta_prompt_dict_chatgpt()[language]
129
 
130
  api_keys = read_apis(api_path)
131
- # Fallback if file is empty or missing for testing purposes
132
  if not api_keys:
133
  print(f"Warning: No API keys found in {api_path}.")
134
 
@@ -167,27 +161,22 @@ def chatgpt_chat(prompt, system, history, gpt_config, api_index=0):
167
  message = [
168
  {"role": "system", "content": system.strip()},
169
  {"role": "user", "content": "Hi!"},
170
- {"role": "assistant",
171
- "content": f"Hi! I'm {boot_actual_name}! I will give you warm companion!"},
172
  ]
173
  else:
174
  message = [
175
  {"role": "system", "content": system.strip()},
176
  {"role": "user", "content": "Hi"},
177
- {"role": "assistant",
178
- "content": f"Hi! I'm {boot_actual_name}! I will give you warm companion!"},
179
  ]
180
 
181
- # --- FIX: Handle History as List of Dicts ---
182
  if history:
183
- # Verify if history is old format (list of lists) or new (list of dicts)
184
  if isinstance(history[0], list):
185
- # Convert old format temporarily if encountered
186
- for q, a in history:
187
- message.append({"role": "user", "content": str(q)})
188
- message.append({"role": "assistant", "content": str(a)})
189
  elif isinstance(history[0], dict):
190
- # New format
191
  for msg in history:
192
  if msg.get('role') in ['user', 'assistant']:
193
  message.append(msg)
@@ -199,8 +188,6 @@ def chatgpt_chat(prompt, system, history, gpt_config, api_index=0):
199
  return "Error: No API Keys available."
200
 
201
  client = get_gapgpt_client(api_keys[api_index])
202
-
203
- # New OpenAI 1.x Syntax
204
  response = client.chat.completions.create(messages=message, **request)
205
 
206
  except Exception as e:
@@ -224,8 +211,6 @@ def chatgpt_chat(prompt, system, history, gpt_config, api_index=0):
224
 
225
 
226
  def classify_query_local(text):
227
-
228
-
229
  # 0: episodic, 1: semantic, 2: semantic_episodic, 3: unrelated
230
  id2label_map = {
231
  0: "episodic_memory",
@@ -235,7 +220,6 @@ def classify_query_local(text):
235
  }
236
 
237
  try:
238
-
239
  inputs = _tokenizer(
240
  text,
241
  return_tensors="pt",
@@ -244,19 +228,14 @@ def classify_query_local(text):
244
  max_length=512
245
  )
246
 
247
-
248
-
249
  with torch.no_grad():
250
  outputs = _classifier_model(**inputs)
251
 
252
-
253
  logits = outputs.logits
254
  probabilities = F.softmax(logits, dim=-1)
255
  predicted_class_id = torch.argmax(probabilities, dim=-1).item()
256
 
257
-
258
  category = id2label_map.get(predicted_class_id, "unknown")
259
-
260
  print(f"Local Classifier: '{text}' -> {category} (Class ID: {predicted_class_id})")
261
  return category
262
 
@@ -265,7 +244,6 @@ def classify_query_local(text):
265
  return "unknown"
266
 
267
 
268
-
269
  def classify_query_openai(text, gpt_config, api_index=0, retry_times=5):
270
  response = None
271
  count = 0
@@ -287,8 +265,6 @@ Output ONLY one of these three strings: 'episodic_memory', 'semantic_memory', or
287
  while response is None and count < retry_times:
288
  try:
289
  client = get_gapgpt_client(api_keys[api_index])
290
-
291
- # New OpenAI 1.x Syntax
292
  response = client.chat.completions.create(
293
  messages=[
294
  {"role": "system", "content": system_prompt},
@@ -308,7 +284,6 @@ Output ONLY one of these three strings: 'episodic_memory', 'semantic_memory', or
308
 
309
  if response:
310
  category = response.choices[0].message.content.strip().lower()
311
- # Basic cleanup
312
  if "semantic_memory" in category: category = "semantic_memory"
313
  elif "semantic-episodic" in category: category = "semantic-episodic"
314
  elif "episodic_memory" in category: category = "episodic_memory"
@@ -334,8 +309,6 @@ def predict_new(
334
  semantic_memory_text,
335
  query_category,
336
  user_profile=None
337
-
338
-
339
  ):
340
  chatgpt_cfg = {
341
  "model": "gpt-4o",
@@ -348,16 +321,14 @@ def predict_new(
348
  }
349
 
350
  if text == "":
351
- # Return same history if empty input
352
  return history, history, "Empty context."
353
 
354
- # Ensure history is initialized
355
  if history is None:
356
  history = []
357
 
358
  system_prompt, related_memos = build_prompt_with_search_memory_llamaindex(
359
  history=history,
360
- query=text, # Changed name to match prompt_utils
361
  user_memory=user_memory,
362
  user_name=user_name,
363
  user_memory_index=user_memory_index,
@@ -374,9 +345,7 @@ def predict_new(
374
  meta_prompt_semantic_episodic=meta_prompt_semantic_episodic,
375
  )
376
 
377
- # ==========================================
378
- # اضافه کردن پروفایل به System Prompt
379
- # ==========================================
380
  if user_profile:
381
  profile_str = (
382
  f"\n\n[User Profile Information]:\n"
@@ -387,12 +356,9 @@ def predict_new(
387
  f"Use this context subtly when talking to the user."
388
  )
389
  system_prompt += profile_str
390
- # ==========================================
391
 
392
- # Handle context window slicing manually if needed,
393
- # though usually OpenAI manages this, or we slice the list of dicts.
394
  current_history_for_llm = history
395
- if len(history) > data_args.max_history * 2: # *2 because 1 user + 1 bot
396
  current_history_for_llm = history[-(data_args.max_history * 2):]
397
 
398
  response = chatgpt_chat(
@@ -405,31 +371,20 @@ def predict_new(
405
 
406
  torch.cuda.empty_cache()
407
 
408
- # --- FIX: Update History with Dictionaries (Gradio Chatbot format) ---
409
  new_history = history + [
410
  {"role": "user", "content": text},
411
  {"role": "assistant", "content": response}
412
  ]
413
 
414
- # Save memory logic (Needs to handle dict format, assuming save_local_memory can handle it
415
- # OR we convert strictly for saving if your legacy code needs it.
416
- # For now, assuming we pass the object as is or convert for save)
417
  if user_name:
418
- # If save_local_memory expects list of lists, we might need to adapt it inside that function
419
- # or pass a converted version. Let's try passing the new format.
420
  save_local_memory(memory, new_history, user_name, data_args)
421
 
422
- # Return: (Chatbot View, State History, Textbox Reset)
423
  return new_history, new_history, "Generating..."
424
 
425
 
426
-
427
  def create_gradio_interface(service_context, api_keys):
428
-
429
-
430
- # ابتدا متغیر CSS را تعریف کنید
431
  custom_css = """
432
- /* استایل دکمه ارسال (Send) */
433
  .send-btn {
434
  border: 3px solid #0066cc !important;
435
  border-radius: 12px !important;
@@ -444,9 +399,9 @@ def create_gradio_interface(service_context, api_keys):
444
  color: white !important;
445
  }
446
 
447
- /* استایل نوار پایین و دکمه‌های آن */
448
  .bottom-actions-row {
449
- background-color: #e5e7eb !important; /* رنگ خاکستری پس‌زمینه */
450
  border-radius: 8px !important;
451
  padding: 5px !important;
452
  margin-top: 10px !important;
@@ -462,36 +417,37 @@ def create_gradio_interface(service_context, api_keys):
462
  }
463
 
464
  .action-btn:hover {
465
- background-color: #d1d5db !important; /* تغییر رنگ ملایم هنگام هاور */
466
  border-radius: 8px !important;
467
  }
 
468
  /* ========================================== */
469
- /* لودینگ تمام‌صفحه و غیرفعال‌سازی تعاملات */
470
  /* ========================================== */
471
 
472
- /* مخفی کردن نوار پیشرفت پیش‌فرض Gradio */
473
  .progress-level, .progress-text, .progress-level svg, .progress-level img {
474
  display: none !important;
475
  }
476
 
477
- /* وقتی کلاسی به نام progress-level در صفحه ایجاد شود، این استایل‌ها روی body اعمال می‌شود */
478
  body:has(.progress-level) {
479
- pointer-events: none !important; /* غیرفعال کردن تمام کلیک‌ها */
480
  }
481
 
482
- /* ایجاد هاله نیمه‌شفاف روی کل صفحه */
483
  body:has(.progress-level)::after {
484
  content: "";
485
  position: fixed;
486
  top: 0; left: 0;
487
  width: 100vw; height: 100vh;
488
- background-color: rgba(255, 255, 255, 0.7); /* رنگ سفید نیمه‌شفاف */
489
  z-index: 9998;
490
- pointer-events: all; /* برای گرفتن کلیک‌ها و جلوگیری از عبور آن‌ها */
491
  cursor: wait;
492
  }
493
 
494
- /* ایجاد اسپینر لودینگ در مرکز صفحه */
495
  body:has(.progress-level)::before {
496
  content: "";
497
  position: fixed;
@@ -510,21 +466,7 @@ body:has(.progress-level)::before {
510
  100% { transform: translate(-50%, -50%) rotate(360deg); }
511
  }
512
 
513
-
514
- /* انیمیشن چرخش */
515
- @keyframes spin {
516
- 0% { transform: rotate(0deg); }
517
- 100% { transform: rotate(360deg); }
518
- }
519
-
520
- /* تغییر کلمه Processing (اختیاری) - مخفی کردن متن پیش فرض */
521
- /* اگر می‌خواهید کلا متنی نباشد و فقط دایره بچرخد، کد زیر را از کامنت در بیاورید */
522
- /*
523
- .progress-text {
524
- display: none !important;
525
- }
526
- */
527
- /* انیمیشن سه نقطه در حال تایپ */
528
  .typing-dots::after {
529
  content: '';
530
  animation: typing 1.5s infinite;
@@ -536,22 +478,16 @@ body:has(.progress-level)::before {
536
  75% { content: ' . . .'; }
537
  100% { content: ''; }
538
  }
539
-
540
-
541
  """
542
 
543
  with gr.Blocks(title="EMMA") as demo:
544
- # حالا متغیر را به HTML پاس دهید (بدون علامت مساوی)
545
  gr.HTML(f"<style>{custom_css}</style>")
546
 
547
- # بقیه کدهای شما...
548
-
549
-
550
  state = gr.State({
551
  "history": [],
552
  "user_name": None,
553
- "memory": memory, # فرض بر این است که متغیر memory خارج از تابع تعریف شده
554
- "data_args": data_args, # فرض بر این است که data_args خارج از تابع تعریف شده
555
  "service_context": service_context,
556
  "api_keys": api_keys,
557
  "api_index": 0,
@@ -560,17 +496,11 @@ body:has(.progress-level)::before {
560
  "initialized": False
561
  })
562
 
563
-
564
-
565
- # فرض بر این است که این کد داخل with gr.Blocks() قرار دارد
566
  with gr.Row():
567
-
568
- # ==========================================
569
- # 1. LOGIN PAGE (سایدبار سمت چپ)
570
  # ==========================================
571
- # استفاده از gr.Sidebar به جای gr.Column و gr.Accordion
 
572
  with gr.Sidebar(open=True) as login_page:
573
-
574
  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>")
575
 
576
  username_input = gr.Textbox(label="Your Name (Required)", placeholder="e.g., Alex")
@@ -594,26 +524,20 @@ body:has(.progress-level)::before {
594
  residence_input = gr.Textbox(label="Place of Residence", placeholder="e.g., Berlin")
595
  register_btn = gr.Button("🎯 Complete Registration", variant="primary", elem_classes=["login-btn"])
596
 
597
-
598
  # ==========================================
599
- # 2. CHAT PAGE (ستون سمت راست)
600
  # ==========================================
601
- # مقدار visible=False حذف شده و scale=3 اضافه شده است تا بخش چت بزرگتر باشد
602
  with gr.Column(scale=3, elem_classes=["chat-box"]) as chat_page:
603
  active_header = gr.Markdown("<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session</h2>")
604
- #system_msg = gr.Textbox(label="🔔 System Messages", interactive=False, max_lines=2)
605
 
606
  with gr.Group():
607
  chatbot = gr.Chatbot(label="💬 EMMA Conversation", height=350)
608
 
609
  with gr.Row():
610
  user_input = gr.Textbox(placeholder="Type your message here...", show_label=False, scale=6)
611
- # اختصاص کلاس send-btn به دکمه Send
612
  submit_btn = gr.Button("📤 Send", elem_classes=["send-btn"], scale=1)
613
 
614
- # اختصاص کلاس bottom-actions-row به ردیف دکمه‌های پایین
615
  with gr.Row(equal_height=True, elem_classes=["bottom-actions-row"]):
616
- # اختصاص کلاس action-btn به دکمه‌های عملیاتی
617
  clear_btn = gr.Button("🧹 Clear", elem_classes=["action-btn"])
618
  new_session_btn = gr.Button("🔄 New Session", elem_classes=["action-btn"])
619
  switch_user_btn = gr.Button("👥 Logout", elem_classes=["action-btn"])
@@ -627,14 +551,13 @@ body:has(.progress-level)::before {
627
  return (gr.update(), gr.update(), gr.update(), gr.update(visible=True, value="⚠️ Please enter both name and password."), gr.update(), gr.update(), state)
628
 
629
  if name in memory:
630
- # بررسی صحت رمز عبور
631
  stored_password = memory[name].get("profile", {}).get("password")
632
 
633
- # اگر کاربر از قبل رمز دارد ولی رمز وارد شده اشتباه است
634
  if stored_password and stored_password != password:
635
  return (gr.update(), gr.update(), gr.update(), gr.update(visible=True, value="⚠️ Incorrect password!"), gr.update(), gr.update(), state)
636
 
637
- # ادامه فرآیند لاگین موفق ...
638
  hello_msg, user_memory, sessions_memory, episodic_memory, semantic_memory = enter_name_llamaindex(name, memory, data_args)
639
  user_memory = summarize_memory_event_personality(data_args, memory, name)
640
 
@@ -679,7 +602,7 @@ body:has(.progress-level)::before {
679
  if name in memory:
680
  raise gr.Error(f"User '{name}' already exists. Please use the login form.")
681
 
682
- # ---> ذخیره رمز عبور در پروفایل <---
683
  memory[name] = {
684
  "profile": {
685
  "password": password,
@@ -692,7 +615,6 @@ body:has(.progress-level)::before {
692
  "episodic_memory": [],
693
  "semantic_memory": {},
694
  }
695
-
696
 
697
  try:
698
  (
@@ -708,7 +630,6 @@ body:has(.progress-level)::before {
708
  update_memory_index=True,
709
  )
710
  except Exception:
711
- # Avoid leaving a partially registered user in memory.
712
  memory.pop(name, None)
713
  raise
714
 
@@ -741,7 +662,7 @@ body:has(.progress-level)::before {
741
  ),
742
  gr.update(value=welcome_msg),
743
  new_state,
744
- )
745
 
746
  def switch_user(state):
747
  if state["initialized"] and state["user_name"] in state["memory"]:
@@ -749,45 +670,39 @@ body:has(.progress-level)::before {
749
  previous_session = state["memory"][state["user_name"]]["sessions"][-1]
750
  summary = extract_session_summary(previous_session["conversation"], previous_session["date"], len(state["memory"][state["user_name"]]["sessions"]) - 1)
751
  state["memory"][state["user_name"]]["episodic_memory"].append(summary)
752
- # ==========================================
753
- # به‌روزرسانی حافظه سمانتیک زمان خروج
754
- # ==========================================
755
  try:
756
  user_mem = state["memory"][state["user_name"]]
757
  existing_semantic = user_mem.get("semantic_memory", {})
758
-
759
  updated_semantic = extract_semantic_memory(summary, existing_semantic)
760
-
761
  state["memory"][state["user_name"]]["semantic_memory"] = updated_semantic
762
  except Exception as e:
763
  print(f"Error updating semantic memory on logout: {e}")
764
- # ==========================================
765
 
766
  new_state = state.copy()
767
  new_state.update({"history": [], "user_name": None, "semantic_memory_text": "", "initialized": False})
768
 
769
  return (
770
- gr.update(open=True), # 1. باز شدن سایدبار
771
- gr.update(visible=False), # 2. بسته شدن فرم ثبت‌نام
772
- gr.update(visible=False), # 3. مخفی شدن صفحه چت
773
- gr.update(value="Logged out successfully.", visible=True), # 4. پیام خروج
774
  gr.update(value=""),
775
- gr.update(value=""), # 5. پاک کردن نام کاربری
776
- new_state, # 6. استیت جدید
777
- gr.update(value=[]), # 7. پاک کردن چت‌بات
778
- gr.update(value=""), # 8. پاک کردن age_input
779
- gr.update(value=""), # 9. پاک کردن gender_input
780
- gr.update(value=""), # 10. پاک کردن occupation_input
781
- gr.update(value=""), # 11. پاک کردن residence_input
782
- gr.update(value="") # 12. پاک کردن پیام سیستم (system_msg) اضافه شد
783
  )
784
 
785
-
786
  def handle_chat(user_message, state):
787
  if not user_message.strip():
788
  print("[handle_chat] empty message")
789
  print("[handle_chat] state =", state)
790
- # تبدیل return به yield
791
  yield gr.update(), state.get("history", []), state
792
  return
793
 
@@ -801,41 +716,32 @@ body:has(.progress-level)::before {
801
  print("[handle_chat] semantic_memory_text =", repr(state.get("semantic_memory_text", "")))
802
  print("[handle_chat] history len =", len(state.get("history", [])))
803
 
804
- # ==========================================
805
- # ۱. نمایش فوری پیام کاربر و انیمیشن لودینگ
806
- # ==========================================
807
  current_history = state.get("history", [])
808
  temp_history = current_history + [
809
  {"role": "user", "content": user_message},
810
  {"role": "assistant", "content": "<span class='typing-dots'>Generating</span>"}
811
  ]
812
 
813
- # باکس متنی را پاک کرده و وضعیت موقت را بلافاصله به UI می‌فرستیم
814
  yield gr.update(value=""), temp_history, state
815
 
816
-
817
- # ادامه پردازش‌های اصلی تابع شما
818
  user_name = state.get("user_name")
819
  user_memory_index = state.get("user_memory_index")
820
  user_memory = state.get("user_memory", {})
821
- user_memory_index = state.get("user_memory_index")
822
- semantic_memory_text = state.get("semantic_memory_text", {})
823
  service_context = state.get("service_context")
824
  api_index = state.get("api_index", 0)
825
- semantic_memory_text = state.get("semantic_memory_text", "")
826
 
827
- # ==========================================
828
- # استخراج پروفایل کاربر از حافظه
829
- # ==========================================
830
  user_profile = {}
831
  if user_name and state.get("memory") and user_name in state["memory"]:
832
  user_profile = state["memory"][user_name].get("profile", {})
833
- # ==========================================
834
 
835
  query_category = classify_query_local(user_message)
836
  print("[handle_chat] query_category =", query_category)
837
 
838
- # ارسال current_history به مدل (بدون انیمیشن موقت)
839
  new_history, _, status_msg = predict_new(
840
  text=user_message,
841
  history=current_history,
@@ -859,9 +765,7 @@ body:has(.progress-level)::before {
859
  state["history"] = new_history
860
  print("[handle_chat] updated state history len =", len(state.get("history", [])))
861
 
862
- # ==========================================
863
- # ۳. جایگزینی پیام لودینگ با پاسخ نهایی
864
- # ==========================================
865
  yield gr.update(), new_history, state
866
 
867
  def clear_history(state):
@@ -869,49 +773,37 @@ body:has(.progress-level)::before {
869
  return [], state
870
 
871
  def handle_new_session(state):
872
- # بررسی اینکه آیا کاربر لاگین کرده است یا خیر
873
  user_name = state.get("user_name")
874
  if not user_name or "memory" not in state or user_name not in state["memory"]:
875
  print("Error: User not logged in or memory missing.")
876
- # خروجی اول برای chatbot (خالی) و خروجی دوم برای state
877
- return [], state, gr.update() # خروجی سوم اضافه شد
878
 
879
- # دریافت اطلاعات کاربر فعلی از داخل حافظه کلی
880
  user_data = state["memory"][user_name]
881
 
882
- # --- بخش اول: استخراج حافظه از جلسه قبلی ---
883
  if user_data.get("sessions") and len(user_data["sessions"]) > 0:
884
  last_session = user_data["sessions"][-1]
885
 
886
- # فقط در صورتی حافظه را استخراج کن که جلسه قبلی خالی نباشد
887
  if len(last_session.get("conversation", [])) > 0:
888
  session_date = last_session.get("date", "")
889
  session_id = last_session.get("session_id", 0)
890
 
891
- # پاس دادن هر ۳ آرگومان به تابع
892
  ep_summary = extract_session_summary(last_session["conversation"], session_date, session_id)
893
 
894
  if "episodic_memory" not in user_data:
895
  user_data["episodic_memory"] = []
896
  user_data["episodic_memory"].append(ep_summary)
897
 
898
- # ==========================================
899
- # به‌روزرسانی حافظه سمانتیک
900
- # ==========================================
901
  try:
902
- # دریافت حافظه سمانتیک قبلی کاربر (اگر نداشت، یک دیکشنری خالی می‌دهیم)
903
  existing_semantic = user_data.get("semantic_memory", {})
904
-
905
- # فراخوانی تابع با هر دو آرگومان
906
  updated_semantic = extract_semantic_memory(ep_summary, existing_semantic)
907
-
908
- # ذخیره دیتای جدید
909
  user_data["semantic_memory"] = updated_semantic
910
  except Exception as e:
911
  print(f"Error updating semantic memory: {e}")
912
- # ==========================================
913
 
914
- # --- بخش دوم: ایجاد جلسه جدید با اختصاص ID ---
915
  new_session_id = len(user_data.get("sessions", []))
916
 
917
  new_session = {
@@ -925,25 +817,21 @@ body:has(.progress-level)::before {
925
 
926
  user_data["sessions"].append(new_session)
927
 
928
- # پاک کردن تاریخچه چت در state
929
  state["history"] = []
930
-
931
- # محاسبه شماره جلسه فعلی (چون از 0 شروع میشود، +1 میکنیم)
932
  session_number = len(user_data["sessions"])
933
  new_header_text = f"<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session {session_number} for {user_name}</h2>"
934
 
935
- # بازگرداندن خروجی جدید برای active_header
936
  return [], state, gr.update(value=new_header_text)
937
 
938
  check_user_btn.click(
939
  handle_login_check,
940
- inputs=[username_input, password_input, state], # password_input اضافه شد
941
  outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
942
  )
943
 
944
  register_btn.click(
945
  handle_register,
946
- inputs=[username_input, password_input, age_input, gender_input, occupation_input, residence_input, state], # password_input اضافه شد
947
  outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
948
  )
949
 
@@ -953,34 +841,28 @@ body:has(.progress-level)::before {
953
  outputs=[login_page, registration_fields, chat_page, login_status, username_input, password_input, state, chatbot, age_input, gender_input, occupation_input, residence_input, system_msg],
954
  )
955
 
956
-
957
  submit_btn.click(
958
  handle_chat,
959
  inputs=[user_input, state],
960
  outputs=[user_input, chatbot, state],
961
- # show_progress="hidden"
962
  )
963
 
964
  user_input.submit(
965
  handle_chat,
966
  inputs=[user_input, state],
967
  outputs=[user_input, chatbot, state],
968
- # show_progress="hidden"
969
  )
970
 
971
  clear_btn.click(
972
  clear_history,
973
  inputs=[state],
974
  outputs=[chatbot, state],
975
- # show_progress="hidden"
976
  )
977
 
978
- # تغییر از clear_history به handle_new_session
979
  new_session_btn.click(
980
  handle_new_session,
981
  inputs=[state],
982
- outputs=[chatbot, state, active_header], # active_header به خروجی اضافه شد
983
- # show_progress="hidden"
984
  )
985
 
986
  return demo
@@ -994,16 +876,15 @@ def main():
994
  if not gapgpt_api_key:
995
  print("Warning: GAPGPT_API_KEY environment variable is not set. Proceeding with keys from file.")
996
  else:
997
- # ست کردن متغیر محیطی برای جلوگیری از خطاهای ناگهانی کتابخانه‌های وابسته
998
  os.environ["OPENAI_API_KEY"] = gapgpt_api_key
999
- # حتماً Base URL را هم به متغیرهای محیطی اضافه کنید
1000
  os.environ["OPENAI_API_BASE"] = GAPGPT_BASE_URL
1001
  os.environ["OPENAI_BASE_URL"] = GAPGPT_BASE_URL
1002
 
1003
  if gapgpt_api_key not in api_keys:
1004
  api_keys.insert(0, gapgpt_api_key)
1005
 
1006
- # تنظیم LLM (مدل زبانی) - این بخش برای چت است و درست کار می‌کند
1007
  llm = LlamaIndexOpenAI(
1008
  model="gpt-4o",
1009
  temperature=1,
@@ -1015,14 +896,14 @@ def main():
1015
  api_base=GAPGPT_BASE_URL,
1016
  )
1017
 
1018
- # تغییر بسیار مهم: مدل را به ada-002 تغییر دهید تا ثبت نام کاربر جدید کار کند
1019
  embed_model = OpenAIEmbedding(
1020
  api_key=gapgpt_api_key,
1021
  api_base=GAPGPT_BASE_URL,
1022
- model="text-embedding-ada-002" # <--- این خط تغییر کرد
1023
  )
1024
 
1025
- # اعمال تنظیمات سراسری
1026
  Settings.llm = llm
1027
  Settings.embed_model = embed_model
1028
 
 
4
  import sys
5
  import json
6
  import time
7
+ import datetime
8
  import copy
9
  import shutil
10
  import signal
 
15
  import gradio as gr
16
  import nltk
17
  import torch
18
+ import torch.nn.functional as F
19
  import tiktoken
20
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
21
 
22
  from openai import OpenAI as OpenAIClient
23
  from llama_index.embeddings.openai import OpenAIEmbedding
 
 
24
  from llama_index.llms.openai import OpenAI as LlamaIndexOpenAI
25
  from llama_index.core import Settings
26
  from llama_index.core.indices.prompt_helper import PromptHelper
 
28
  prompt_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../')
29
  sys.path.append(prompt_path)
30
 
31
+ # Local module imports
32
  from utils.sys_args import data_args, model_args
33
  from utils.app_modules.utils import *
34
  from utils.app_modules.presets import *
 
50
  GAPGPT_BASE_URL = os.getenv("GAPGPT_BASE_URL", "https://api.gapgpt.app/v1")
51
  openai_client_cache = {}
52
 
 
 
 
 
 
53
  LOCAL_MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Classification_Model", "final_xlm_r_model_router")
54
 
55
  print(f"Loading model from: {LOCAL_MODEL_PATH}")
 
98
  return api_keys_local
99
 
100
 
101
+ # Load base storage path from environment variable (fallback to default data folder)
102
  base_data_dir = os.getenv("EMMA_DATA_DIR", os.path.join(os.path.dirname(os.path.abspath(__file__)), "data"))
103
 
104
+ # Define memory file path
105
  memory_dir = os.path.join(base_data_dir, "memories", "update_memory_0512_eng.json")
106
 
107
  # Ensure directory exists
108
  os.makedirs(os.path.dirname(memory_dir), exist_ok=True)
109
 
 
110
  if not os.path.exists(memory_dir):
111
  json.dump({}, open(memory_dir, "w", encoding="utf-8"))
112
 
 
123
  new_user_meta_prompt = generate_new_user_meta_prompt_dict_chatgpt()[language]
124
 
125
  api_keys = read_apis(api_path)
 
126
  if not api_keys:
127
  print(f"Warning: No API keys found in {api_path}.")
128
 
 
161
  message = [
162
  {"role": "system", "content": system.strip()},
163
  {"role": "user", "content": "Hi!"},
164
+ {"role": "assistant", "content": f"Hi! I'm {boot_actual_name}! I will give you warm companion!"},
 
165
  ]
166
  else:
167
  message = [
168
  {"role": "system", "content": system.strip()},
169
  {"role": "user", "content": "Hi"},
170
+ {"role": "assistant", "content": f"Hi! I'm {boot_actual_name}! I will give you warm companion!"},
 
171
  ]
172
 
173
+ # Handle History as List of Dicts
174
  if history:
 
175
  if isinstance(history[0], list):
176
+ for q, a in history:
177
+ message.append({"role": "user", "content": str(q)})
178
+ message.append({"role": "assistant", "content": str(a)})
 
179
  elif isinstance(history[0], dict):
 
180
  for msg in history:
181
  if msg.get('role') in ['user', 'assistant']:
182
  message.append(msg)
 
188
  return "Error: No API Keys available."
189
 
190
  client = get_gapgpt_client(api_keys[api_index])
 
 
191
  response = client.chat.completions.create(messages=message, **request)
192
 
193
  except Exception as e:
 
211
 
212
 
213
  def classify_query_local(text):
 
 
214
  # 0: episodic, 1: semantic, 2: semantic_episodic, 3: unrelated
215
  id2label_map = {
216
  0: "episodic_memory",
 
220
  }
221
 
222
  try:
 
223
  inputs = _tokenizer(
224
  text,
225
  return_tensors="pt",
 
228
  max_length=512
229
  )
230
 
 
 
231
  with torch.no_grad():
232
  outputs = _classifier_model(**inputs)
233
 
 
234
  logits = outputs.logits
235
  probabilities = F.softmax(logits, dim=-1)
236
  predicted_class_id = torch.argmax(probabilities, dim=-1).item()
237
 
 
238
  category = id2label_map.get(predicted_class_id, "unknown")
 
239
  print(f"Local Classifier: '{text}' -> {category} (Class ID: {predicted_class_id})")
240
  return category
241
 
 
244
  return "unknown"
245
 
246
 
 
247
  def classify_query_openai(text, gpt_config, api_index=0, retry_times=5):
248
  response = None
249
  count = 0
 
265
  while response is None and count < retry_times:
266
  try:
267
  client = get_gapgpt_client(api_keys[api_index])
 
 
268
  response = client.chat.completions.create(
269
  messages=[
270
  {"role": "system", "content": system_prompt},
 
284
 
285
  if response:
286
  category = response.choices[0].message.content.strip().lower()
 
287
  if "semantic_memory" in category: category = "semantic_memory"
288
  elif "semantic-episodic" in category: category = "semantic-episodic"
289
  elif "episodic_memory" in category: category = "episodic_memory"
 
309
  semantic_memory_text,
310
  query_category,
311
  user_profile=None
 
 
312
  ):
313
  chatgpt_cfg = {
314
  "model": "gpt-4o",
 
321
  }
322
 
323
  if text == "":
 
324
  return history, history, "Empty context."
325
 
 
326
  if history is None:
327
  history = []
328
 
329
  system_prompt, related_memos = build_prompt_with_search_memory_llamaindex(
330
  history=history,
331
+ query=text,
332
  user_memory=user_memory,
333
  user_name=user_name,
334
  user_memory_index=user_memory_index,
 
345
  meta_prompt_semantic_episodic=meta_prompt_semantic_episodic,
346
  )
347
 
348
+ # Attach profile information to System Prompt
 
 
349
  if user_profile:
350
  profile_str = (
351
  f"\n\n[User Profile Information]:\n"
 
356
  f"Use this context subtly when talking to the user."
357
  )
358
  system_prompt += profile_str
 
359
 
 
 
360
  current_history_for_llm = history
361
+ if len(history) > data_args.max_history * 2:
362
  current_history_for_llm = history[-(data_args.max_history * 2):]
363
 
364
  response = chatgpt_chat(
 
371
 
372
  torch.cuda.empty_cache()
373
 
 
374
  new_history = history + [
375
  {"role": "user", "content": text},
376
  {"role": "assistant", "content": response}
377
  ]
378
 
 
 
 
379
  if user_name:
 
 
380
  save_local_memory(memory, new_history, user_name, data_args)
381
 
 
382
  return new_history, new_history, "Generating..."
383
 
384
 
 
385
  def create_gradio_interface(service_context, api_keys):
 
 
 
386
  custom_css = """
387
+ /* Send button styling */
388
  .send-btn {
389
  border: 3px solid #0066cc !important;
390
  border-radius: 12px !important;
 
399
  color: white !important;
400
  }
401
 
402
+ /* Bottom action row and button styling */
403
  .bottom-actions-row {
404
+ background-color: #e5e7eb !important;
405
  border-radius: 8px !important;
406
  padding: 5px !important;
407
  margin-top: 10px !important;
 
417
  }
418
 
419
  .action-btn:hover {
420
+ background-color: #d1d5db !important;
421
  border-radius: 8px !important;
422
  }
423
+
424
  /* ========================================== */
425
+ /* Fullscreen loading overlay & interaction lock */
426
  /* ========================================== */
427
 
428
+ /* Hide default Gradio progress indicators */
429
  .progress-level, .progress-text, .progress-level svg, .progress-level img {
430
  display: none !important;
431
  }
432
 
433
+ /* Disable interaction while loading */
434
  body:has(.progress-level) {
435
+ pointer-events: none !important;
436
  }
437
 
438
+ /* Semi-transparent backdrop */
439
  body:has(.progress-level)::after {
440
  content: "";
441
  position: fixed;
442
  top: 0; left: 0;
443
  width: 100vw; height: 100vh;
444
+ background-color: rgba(255, 255, 255, 0.7);
445
  z-index: 9998;
446
+ pointer-events: all;
447
  cursor: wait;
448
  }
449
 
450
+ /* Centered spinner */
451
  body:has(.progress-level)::before {
452
  content: "";
453
  position: fixed;
 
466
  100% { transform: translate(-50%, -50%) rotate(360deg); }
467
  }
468
 
469
+ /* Typing dots animation */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  .typing-dots::after {
471
  content: '';
472
  animation: typing 1.5s infinite;
 
478
  75% { content: ' . . .'; }
479
  100% { content: ''; }
480
  }
 
 
481
  """
482
 
483
  with gr.Blocks(title="EMMA") as demo:
 
484
  gr.HTML(f"<style>{custom_css}</style>")
485
 
 
 
 
486
  state = gr.State({
487
  "history": [],
488
  "user_name": None,
489
+ "memory": memory,
490
+ "data_args": data_args,
491
  "service_context": service_context,
492
  "api_keys": api_keys,
493
  "api_index": 0,
 
496
  "initialized": False
497
  })
498
 
 
 
 
499
  with gr.Row():
 
 
 
500
  # ==========================================
501
+ # 1. LOGIN PAGE (Left Sidebar)
502
+ # ==========================================
503
  with gr.Sidebar(open=True) as login_page:
 
504
  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>")
505
 
506
  username_input = gr.Textbox(label="Your Name (Required)", placeholder="e.g., Alex")
 
524
  residence_input = gr.Textbox(label="Place of Residence", placeholder="e.g., Berlin")
525
  register_btn = gr.Button("🎯 Complete Registration", variant="primary", elem_classes=["login-btn"])
526
 
 
527
  # ==========================================
528
+ # 2. CHAT PAGE (Main Column)
529
  # ==========================================
 
530
  with gr.Column(scale=3, elem_classes=["chat-box"]) as chat_page:
531
  active_header = gr.Markdown("<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session</h2>")
 
532
 
533
  with gr.Group():
534
  chatbot = gr.Chatbot(label="💬 EMMA Conversation", height=350)
535
 
536
  with gr.Row():
537
  user_input = gr.Textbox(placeholder="Type your message here...", show_label=False, scale=6)
 
538
  submit_btn = gr.Button("📤 Send", elem_classes=["send-btn"], scale=1)
539
 
 
540
  with gr.Row(equal_height=True, elem_classes=["bottom-actions-row"]):
 
541
  clear_btn = gr.Button("🧹 Clear", elem_classes=["action-btn"])
542
  new_session_btn = gr.Button("🔄 New Session", elem_classes=["action-btn"])
543
  switch_user_btn = gr.Button("👥 Logout", elem_classes=["action-btn"])
 
551
  return (gr.update(), gr.update(), gr.update(), gr.update(visible=True, value="⚠️ Please enter both name and password."), gr.update(), gr.update(), state)
552
 
553
  if name in memory:
554
+ # Password validation
555
  stored_password = memory[name].get("profile", {}).get("password")
556
 
 
557
  if stored_password and stored_password != password:
558
  return (gr.update(), gr.update(), gr.update(), gr.update(visible=True, value="⚠️ Incorrect password!"), gr.update(), gr.update(), state)
559
 
560
+ # Proceed with successful login
561
  hello_msg, user_memory, sessions_memory, episodic_memory, semantic_memory = enter_name_llamaindex(name, memory, data_args)
562
  user_memory = summarize_memory_event_personality(data_args, memory, name)
563
 
 
602
  if name in memory:
603
  raise gr.Error(f"User '{name}' already exists. Please use the login form.")
604
 
605
+ # Store credentials and initial structures
606
  memory[name] = {
607
  "profile": {
608
  "password": password,
 
615
  "episodic_memory": [],
616
  "semantic_memory": {},
617
  }
 
618
 
619
  try:
620
  (
 
630
  update_memory_index=True,
631
  )
632
  except Exception:
 
633
  memory.pop(name, None)
634
  raise
635
 
 
662
  ),
663
  gr.update(value=welcome_msg),
664
  new_state,
665
+ )
666
 
667
  def switch_user(state):
668
  if state["initialized"] and state["user_name"] in state["memory"]:
 
670
  previous_session = state["memory"][state["user_name"]]["sessions"][-1]
671
  summary = extract_session_summary(previous_session["conversation"], previous_session["date"], len(state["memory"][state["user_name"]]["sessions"]) - 1)
672
  state["memory"][state["user_name"]]["episodic_memory"].append(summary)
673
+
674
+ # Update semantic memory on logout
 
675
  try:
676
  user_mem = state["memory"][state["user_name"]]
677
  existing_semantic = user_mem.get("semantic_memory", {})
 
678
  updated_semantic = extract_semantic_memory(summary, existing_semantic)
 
679
  state["memory"][state["user_name"]]["semantic_memory"] = updated_semantic
680
  except Exception as e:
681
  print(f"Error updating semantic memory on logout: {e}")
 
682
 
683
  new_state = state.copy()
684
  new_state.update({"history": [], "user_name": None, "semantic_memory_text": "", "initialized": False})
685
 
686
  return (
687
+ gr.update(open=True), # 1. Open sidebar
688
+ gr.update(visible=False), # 2. Hide registration form
689
+ gr.update(visible=False), # 3. Hide chat view
690
+ gr.update(value="Logged out successfully.", visible=True), # 4. Logout message
691
  gr.update(value=""),
692
+ gr.update(value=""), # 5. Clear username/password
693
+ new_state, # 6. Updated state
694
+ gr.update(value=[]), # 7. Clear chatbot view
695
+ gr.update(value=""), # 8. Clear age_input
696
+ gr.update(value=""), # 9. Clear gender_input
697
+ gr.update(value=""), # 10. Clear occupation_input
698
+ gr.update(value=""), # 11. Clear residence_input
699
+ gr.update(value="") # 12. Clear system_msg
700
  )
701
 
 
702
  def handle_chat(user_message, state):
703
  if not user_message.strip():
704
  print("[handle_chat] empty message")
705
  print("[handle_chat] state =", state)
 
706
  yield gr.update(), state.get("history", []), state
707
  return
708
 
 
716
  print("[handle_chat] semantic_memory_text =", repr(state.get("semantic_memory_text", "")))
717
  print("[handle_chat] history len =", len(state.get("history", [])))
718
 
719
+ # 1. Immediate display of user message and typing animation
 
 
720
  current_history = state.get("history", [])
721
  temp_history = current_history + [
722
  {"role": "user", "content": user_message},
723
  {"role": "assistant", "content": "<span class='typing-dots'>Generating</span>"}
724
  ]
725
 
 
726
  yield gr.update(value=""), temp_history, state
727
 
728
+ # Core processing
 
729
  user_name = state.get("user_name")
730
  user_memory_index = state.get("user_memory_index")
731
  user_memory = state.get("user_memory", {})
732
+ semantic_memory_text = state.get("semantic_memory_text", "")
 
733
  service_context = state.get("service_context")
734
  api_index = state.get("api_index", 0)
 
735
 
736
+ # Retrieve user profile from memory
 
 
737
  user_profile = {}
738
  if user_name and state.get("memory") and user_name in state["memory"]:
739
  user_profile = state["memory"][user_name].get("profile", {})
 
740
 
741
  query_category = classify_query_local(user_message)
742
  print("[handle_chat] query_category =", query_category)
743
 
744
+ # Generate response from model
745
  new_history, _, status_msg = predict_new(
746
  text=user_message,
747
  history=current_history,
 
765
  state["history"] = new_history
766
  print("[handle_chat] updated state history len =", len(state.get("history", [])))
767
 
768
+ # 2. Replace loading placeholder with final response
 
 
769
  yield gr.update(), new_history, state
770
 
771
  def clear_history(state):
 
773
  return [], state
774
 
775
  def handle_new_session(state):
776
+ # Check user login status
777
  user_name = state.get("user_name")
778
  if not user_name or "memory" not in state or user_name not in state["memory"]:
779
  print("Error: User not logged in or memory missing.")
780
+ return [], state, gr.update()
 
781
 
 
782
  user_data = state["memory"][user_name]
783
 
784
+ # --- Part 1: Extract memory from previous session ---
785
  if user_data.get("sessions") and len(user_data["sessions"]) > 0:
786
  last_session = user_data["sessions"][-1]
787
 
 
788
  if len(last_session.get("conversation", [])) > 0:
789
  session_date = last_session.get("date", "")
790
  session_id = last_session.get("session_id", 0)
791
 
 
792
  ep_summary = extract_session_summary(last_session["conversation"], session_date, session_id)
793
 
794
  if "episodic_memory" not in user_data:
795
  user_data["episodic_memory"] = []
796
  user_data["episodic_memory"].append(ep_summary)
797
 
798
+ # Update semantic memory
 
 
799
  try:
 
800
  existing_semantic = user_data.get("semantic_memory", {})
 
 
801
  updated_semantic = extract_semantic_memory(ep_summary, existing_semantic)
 
 
802
  user_data["semantic_memory"] = updated_semantic
803
  except Exception as e:
804
  print(f"Error updating semantic memory: {e}")
 
805
 
806
+ # --- Part 2: Create a new session with assigned ID ---
807
  new_session_id = len(user_data.get("sessions", []))
808
 
809
  new_session = {
 
817
 
818
  user_data["sessions"].append(new_session)
819
 
 
820
  state["history"] = []
 
 
821
  session_number = len(user_data["sessions"])
822
  new_header_text = f"<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session {session_number} for {user_name}</h2>"
823
 
 
824
  return [], state, gr.update(value=new_header_text)
825
 
826
  check_user_btn.click(
827
  handle_login_check,
828
+ inputs=[username_input, password_input, state],
829
  outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
830
  )
831
 
832
  register_btn.click(
833
  handle_register,
834
+ inputs=[username_input, password_input, age_input, gender_input, occupation_input, residence_input, state],
835
  outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
836
  )
837
 
 
841
  outputs=[login_page, registration_fields, chat_page, login_status, username_input, password_input, state, chatbot, age_input, gender_input, occupation_input, residence_input, system_msg],
842
  )
843
 
 
844
  submit_btn.click(
845
  handle_chat,
846
  inputs=[user_input, state],
847
  outputs=[user_input, chatbot, state],
 
848
  )
849
 
850
  user_input.submit(
851
  handle_chat,
852
  inputs=[user_input, state],
853
  outputs=[user_input, chatbot, state],
 
854
  )
855
 
856
  clear_btn.click(
857
  clear_history,
858
  inputs=[state],
859
  outputs=[chatbot, state],
 
860
  )
861
 
 
862
  new_session_btn.click(
863
  handle_new_session,
864
  inputs=[state],
865
+ outputs=[chatbot, state, active_header],
 
866
  )
867
 
868
  return demo
 
876
  if not gapgpt_api_key:
877
  print("Warning: GAPGPT_API_KEY environment variable is not set. Proceeding with keys from file.")
878
  else:
879
+ # Set environment variables for third-party library compatibility
880
  os.environ["OPENAI_API_KEY"] = gapgpt_api_key
 
881
  os.environ["OPENAI_API_BASE"] = GAPGPT_BASE_URL
882
  os.environ["OPENAI_BASE_URL"] = GAPGPT_BASE_URL
883
 
884
  if gapgpt_api_key not in api_keys:
885
  api_keys.insert(0, gapgpt_api_key)
886
 
887
+ # Initialize LlamaIndex LLM
888
  llm = LlamaIndexOpenAI(
889
  model="gpt-4o",
890
  temperature=1,
 
896
  api_base=GAPGPT_BASE_URL,
897
  )
898
 
899
+ # Configure embedding model
900
  embed_model = OpenAIEmbedding(
901
  api_key=gapgpt_api_key,
902
  api_base=GAPGPT_BASE_URL,
903
+ model="text-embedding-ada-002"
904
  )
905
 
906
+ # Apply global settings
907
  Settings.llm = llm
908
  Settings.embed_model = embed_model
909