EMMA commited on
Commit
43a1cd3
·
1 Parent(s): 941b827

Update Dockerfile and add app V19 backup

Browse files
Files changed (2) hide show
  1. Backup code/app_V19.py +1045 -0
  2. Dockerfile +5 -0
Backup code/app_V19.py ADDED
@@ -0,0 +1,1045 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding:utf-8 -*-
2
+
3
+ import os
4
+ import sys
5
+ import json
6
+ import time
7
+ import copy
8
+ import shutil
9
+ import signal
10
+ import logging
11
+ import platform
12
+ import re
13
+
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
26
+
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 *
34
+ from utils.app_modules.overwrites import *
35
+ from utils.prompt_utils import *
36
+ from utils.memory_utils import (
37
+ enter_name_llamaindex,
38
+ summarize_memory_event_personality,
39
+ save_local_memory,
40
+ extract_session_summary,
41
+ extract_semantic_memory,
42
+ )
43
+
44
+ # Ensure NLTK data path
45
+ nltk.data.path = [os.path.join(os.path.dirname(__file__), "nltk_data")] + nltk.data.path
46
+
47
+ tokenizer = tiktoken.get_encoding("cl100k_base")
48
+
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}")
60
+
61
+ try:
62
+ _tokenizer = AutoTokenizer.from_pretrained(LOCAL_MODEL_PATH)
63
+ _classifier_model = AutoModelForSequenceClassification.from_pretrained(LOCAL_MODEL_PATH)
64
+ _classifier_model.eval()
65
+ print("Local classifier loaded successfully.")
66
+ except Exception as e:
67
+ print(f"Error loading local classifier: {e}")
68
+
69
+
70
+ def get_gapgpt_client(api_key: str) -> OpenAIClient:
71
+ if not api_key:
72
+ raise ValueError("API key is missing while attempting to create a GapGPT client.")
73
+ if api_key not in openai_client_cache:
74
+ openai_client_cache[api_key] = OpenAIClient(api_key=api_key, base_url=GAPGPT_BASE_URL)
75
+ return openai_client_cache[api_key]
76
+
77
+
78
+ os_name = platform.system()
79
+ clear_command = 'cls' if os_name == 'Windows' else 'clear'
80
+ stop_stream = False
81
+
82
+
83
+ def signal_handler(signal_number, frame):
84
+ global stop_stream
85
+ stop_stream = True
86
+
87
+
88
+ VECTOR_SEARCH_TOP_K = 2
89
+
90
+ # Update this path to your actual file location
91
+ api_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "api_key_list.txt")
92
+
93
+
94
+ def read_apis(path):
95
+ api_keys_local = []
96
+ if os.path.exists(path):
97
+ with open(path, 'r', encoding='utf8') as f:
98
+ for line in f:
99
+ line = line.strip()
100
+ if line:
101
+ api_keys_local.append(line)
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
+
118
+ global memory
119
+ memory = json.load(open(memory_dir, "r", encoding="utf-8"))
120
+ language = 'en'
121
+ user_keyword = generate_user_keyword()[language]
122
+ ai_keyword = generate_ai_keyword()[language]
123
+ boot_name = boot_name_dict[language]
124
+ boot_actual_name = boot_actual_name_dict[language]
125
+ meta_prompt = generate_meta_prompt_dict_chatgpt()[language]
126
+ meta_prompt_semantic = generate_meta_prompt_dict_semantic_chatgpt()[language]
127
+ meta_prompt_semantic_episodic = generate_meta_prompt_dict_semantic_episodic_chatgpt()[language]
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
+
135
+ new_conversation = False
136
+ chatgpt_config = {
137
+ "model": "gpt-4o",
138
+ "temperature": 1,
139
+ "max_tokens": 1024,
140
+ "top_p": 0.95,
141
+ "frequency_penalty": 0.4,
142
+ "presence_penalty": 0.2,
143
+ "n": 1,
144
+ }
145
+
146
+ deactivated_keys = []
147
+ logging.basicConfig(
148
+ level=logging.DEBUG,
149
+ format="%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s",
150
+ )
151
+
152
+
153
+ def chatgpt_chat(prompt, system, history, gpt_config, api_index=0):
154
+ """
155
+ Handles the chat request to OpenAI.
156
+ History is expected to be a list of dictionaries: [{'role': 'user', 'content': '...'}, ...]
157
+ """
158
+ retry_times, count = 5, 0
159
+ response = None
160
+
161
+ while response is None and count < retry_times:
162
+ try:
163
+ request = copy.deepcopy(gpt_config)
164
+
165
+ # Initial system and greeting messages
166
+ if data_args.language == 'en':
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)
194
+
195
+ # Add the current prompt
196
+ message.append({"role": "user", "content": f"{prompt}"})
197
+
198
+ if not api_keys:
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:
207
+ print(f"Chat Error: {e}")
208
+ if 'This key is associated with a deactivated account' in str(e):
209
+ deactivated_keys.append(api_keys[api_index])
210
+
211
+ if api_keys:
212
+ api_index = api_index + 1 if api_index < len(api_keys) - 1 else 0
213
+ loop_check = 0
214
+ while api_keys[api_index] in deactivated_keys and loop_check < len(api_keys):
215
+ api_index = api_index + 1 if api_index < len(api_keys) - 1 else 0
216
+ loop_check += 1
217
+ count += 1
218
+
219
+ if response:
220
+ response = response.choices[0].message.content
221
+ else:
222
+ response = ''
223
+ return response
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",
232
+ 1: "semantic_memory",
233
+ 2: "semantic-episodic",
234
+ 3: "unknown"
235
+ }
236
+
237
+ try:
238
+
239
+ inputs = _tokenizer(
240
+ text,
241
+ return_tensors="pt",
242
+ truncation=True,
243
+ padding=True,
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
+
263
+ except Exception as e:
264
+ print(f"Error in local classification: {e}")
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
272
+ local_deactivated = []
273
+ print("text********:", text)
274
+
275
+ system_prompt = """
276
+ You are an AI that classifies user queries into one of the following memory types:
277
+ - 'episodic_memory': Queries about past personal events, daily life logs, or specific experiences the user has shared (e.g., "What did I eat yesterday?", "Tell me about my trip").
278
+ - 'semantic_memory': Queries about facts, preferences, general knowledge the user has taught you, or summaries of their personality (e.g., "What is my favorite color?", "Do I like sci-fi movies?").
279
+ - 'semantic-episodic': Complex queries requiring both specific past events and general facts/preferences (e.g., "Based on my food preferences, did I enjoy the dinner last night?").
280
+
281
+ Output ONLY one of these three strings: 'episodic_memory', 'semantic_memory', or 'semantic-episodic'. If unsure, output 'episodic_memory'.
282
+ """.strip()
283
+
284
+ if not api_keys:
285
+ return "unknown"
286
+
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},
295
+ {"role": "user", "content": text.strip()},
296
+ ],
297
+ **copy.deepcopy(gpt_config),
298
+ )
299
+ except Exception as e:
300
+ print(f"Classify Error: {e}")
301
+ if "This key is associated with a deactivated account" in str(e):
302
+ local_deactivated.append(api_keys[api_index])
303
+
304
+ api_index = api_index + 1 if api_index < len(api_keys) - 1 else 0
305
+ while api_keys[api_index] in local_deactivated:
306
+ api_index = api_index + 1 if api_index < len(api_keys) - 1 else 0
307
+ count += 1
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"
315
+ else: category = "unknown"
316
+ else:
317
+ category = "unknown"
318
+
319
+ return category
320
+
321
+
322
+ def predict_new(
323
+ text,
324
+ history,
325
+ top_p,
326
+ temperature,
327
+ max_length_tokens,
328
+ max_context_length_tokens,
329
+ user_name,
330
+ user_memory,
331
+ user_memory_index,
332
+ service_context,
333
+ api_index,
334
+ semantic_memory_text,
335
+ query_category,
336
+ user_profile=None
337
+
338
+
339
+ ):
340
+ chatgpt_cfg = {
341
+ "model": "gpt-4o",
342
+ "temperature": temperature,
343
+ "max_tokens": max_length_tokens,
344
+ "top_p": top_p,
345
+ "frequency_penalty": 0.4,
346
+ "presence_penalty": 0.2,
347
+ "n": 1,
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,
364
+ service_context=service_context,
365
+ api_keys=api_keys,
366
+ api_index=api_index,
367
+ meta_prompt=meta_prompt,
368
+ new_user_meta_prompt=new_user_meta_prompt,
369
+ data_args=data_args,
370
+ boot_actual_name=boot_actual_name,
371
+ semantic_memory_text=semantic_memory_text,
372
+ query_category=query_category,
373
+ meta_prompt_semantic=meta_prompt_semantic,
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"
383
+ f"- Age: {user_profile.get('age', 'Unknown')}\n"
384
+ f"- Gender: {user_profile.get('gender', 'Unknown')}\n"
385
+ f"- Occupation: {user_profile.get('occupation', 'Unknown')}\n"
386
+ f"- Residence: {user_profile.get('residence', 'Unknown')}\n"
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(
399
+ prompt=text,
400
+ system=system_prompt,
401
+ history=current_history_for_llm,
402
+ gpt_config=chatgpt_cfg,
403
+ api_index=api_index,
404
+ )
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;
436
+ background-color: white !important;
437
+ color: black !important;
438
+ font-weight: bold !important;
439
+ font-size: 16px !important;
440
+ transition: all 0.3s ease !important;
441
+ }
442
+ .send-btn:hover {
443
+ background-color: #0066cc !important;
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;
453
+ }
454
+
455
+ .action-btn {
456
+ border: none !important;
457
+ background: transparent !important;
458
+ color: black !important;
459
+ font-weight: bold !important;
460
+ box-shadow: none !important;
461
+ transition: all 0.3s ease !important;
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;
498
+ top: 50%; left: 50%;
499
+ transform: translate(-50%, -50%);
500
+ width: 60px; height: 60px;
501
+ border: 6px solid #e5e7eb;
502
+ border-top-color: #0066cc;
503
+ border-radius: 50%;
504
+ animation: spin 1s linear infinite;
505
+ z-index: 9999;
506
+ }
507
+
508
+ @keyframes spin {
509
+ 0% { transform: translate(-50%, -50%) rotate(0deg); }
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;
531
+ }
532
+ @keyframes typing {
533
+ 0% { content: ''; }
534
+ 25% { content: ' .'; }
535
+ 50% { content: ' . .'; }
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,
558
+ "semantic_memory_text": "",
559
+ "new_conversation": True,
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")
577
+ password_input = gr.Textbox(label="Password (Required)", placeholder="Enter your password", type="password")
578
+
579
+ login_status = gr.Textbox(
580
+ visible=False,
581
+ label="Status",
582
+ interactive=False,
583
+ lines=1,
584
+ max_lines=10
585
+ )
586
+
587
+ check_user_btn = gr.Button("🔍 Login / Check", variant="primary", elem_classes=["login-btn"])
588
+
589
+ with gr.Column(visible=False) as registration_fields:
590
+ gr.Markdown("### ✨ New User Registration")
591
+ age_input = gr.Textbox(label="Age", placeholder="e.g., 28")
592
+ gender_input = gr.Dropdown(label="Gender", choices=["Male", "Female", "Other"])
593
+ occupation_input = gr.Textbox(label="Occupation", placeholder="e.g., Student...")
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"])
620
+ system_msg = gr.Textbox(label="🔔 System Messages", interactive=False, max_lines=2)
621
+
622
+ # -------------------------------------------------------
623
+ # Internal Functions
624
+ # -------------------------------------------------------
625
+ def handle_login_check(name, password, state):
626
+ if not name.strip() or not password.strip():
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
+
641
+ new_state = state.copy()
642
+ new_state["user_name"] = name
643
+ new_state["memory"] = memory
644
+ new_state["initialized"] = True
645
+ new_state["semantic_memory_text"] = user_memory.get("semantic_memory", {})
646
+ new_state["semantic_memory_index"] = semantic_memory
647
+ new_state["user_memory_index"] = sessions_memory
648
+ new_state["episodic_memory_index"] = episodic_memory
649
+ new_state["history"] = []
650
+
651
+ welcome_msg = hello_msg if hello_msg else f"Welcome back, {name}!"
652
+
653
+ return (
654
+ gr.update(open=False),
655
+ gr.update(visible=False),
656
+ gr.update(visible=True),
657
+ gr.update(visible=False, value=""),
658
+ gr.update(value=f"<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session for {name}</h2>"),
659
+ gr.update(value=welcome_msg),
660
+ new_state
661
+ )
662
+ else:
663
+ return (
664
+ gr.update(),
665
+ gr.update(visible=True),
666
+ gr.update(visible=False),
667
+ gr.update(visible=True, value="📝 New user detected. Please fill the details below to register with this password."),
668
+ gr.update(),
669
+ gr.update(),
670
+ state
671
+ )
672
+
673
+ def handle_register(name, password, age, gender, occupation, residence, state):
674
+ name = str(name).strip()
675
+
676
+ if not name or not password.strip():
677
+ raise gr.Error("Please enter a valid user name and password.")
678
+
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,
686
+ "age": age,
687
+ "gender": gender,
688
+ "occupation": occupation,
689
+ "residence": residence,
690
+ },
691
+ "sessions": [],
692
+ "episodic_memory": [],
693
+ "semantic_memory": {},
694
+ }
695
+
696
+
697
+ try:
698
+ (
699
+ _,
700
+ user_memory,
701
+ sessions_memory,
702
+ episodic_memory,
703
+ semantic_memory,
704
+ ) = enter_name_llamaindex(
705
+ name=name,
706
+ memory=memory,
707
+ data_args=data_args,
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
+
715
+ new_state = dict(state or {})
716
+ new_state.update(
717
+ {
718
+ "user_name": name,
719
+ "memory": memory,
720
+ "initialized": True,
721
+ "history": [],
722
+ "semantic_memory_text": user_memory.get("semantic_memory", {}),
723
+ "user_memory_index": sessions_memory,
724
+ "episodic_memory_index": episodic_memory,
725
+ "semantic_memory_index": semantic_memory,
726
+ }
727
+ )
728
+
729
+ welcome_msg = f"Welcome {name}! Registration complete."
730
+
731
+ return (
732
+ gr.update(open=False),
733
+ gr.update(visible=False),
734
+ gr.update(visible=True),
735
+ gr.update(visible=False, value=""),
736
+ gr.update(
737
+ value=(
738
+ "<h2 style='text-align: center; color: #333;'>"
739
+ f"🧠 EMMA: Session for {name}</h2>"
740
+ )
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"]:
748
+ if state["memory"][state["user_name"]]["sessions"]:
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
+
794
+ print("[handle_chat] user_message =", repr(user_message))
795
+ print("[handle_chat] state keys =", list(state.keys()) if isinstance(state, dict) else type(state))
796
+ print("[handle_chat] user_name =", state.get("user_name"))
797
+ print("[handle_chat] user_memory_index =", state.get("user_memory_index"))
798
+ print("[handle_chat] memory exists =", state.get("memory") is not None)
799
+ print("[handle_chat] service_context exists =", state.get("service_context") is not None)
800
+ print("[handle_chat] api_index =", state.get("api_index", 0))
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,
842
+ top_p=0.95,
843
+ temperature=1.0,
844
+ max_length_tokens=1024,
845
+ max_context_length_tokens=4096,
846
+ user_name=user_name,
847
+ user_memory=user_memory,
848
+ user_memory_index=user_memory_index,
849
+ service_context=service_context,
850
+ api_index=api_index,
851
+ semantic_memory_text=semantic_memory_text,
852
+ query_category=query_category,
853
+ user_profile=user_profile
854
+ )
855
+
856
+ print("[handle_chat] status_msg =", status_msg)
857
+ print("[handle_chat] new_history len =", len(new_history))
858
+
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):
868
+ state["history"] = []
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 = {
918
+ "session_id": new_session_id,
919
+ "date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
920
+ "conversation": []
921
+ }
922
+
923
+ if "sessions" not in user_data:
924
+ user_data["sessions"] = []
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
+
950
+ switch_user_btn.click(
951
+ switch_user,
952
+ inputs=[state],
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
987
+
988
+
989
+ def main():
990
+ """Main function to initialize and launch the interface."""
991
+ global api_keys
992
+ gapgpt_api_key = os.getenv("GAPGPT_API_KEY")
993
+
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,
1010
+ max_tokens=1024,
1011
+ top_p=0.95,
1012
+ frequency_penalty=0.4,
1013
+ presence_penalty=0.2,
1014
+ api_key=gapgpt_api_key,
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
+
1029
+ Settings.prompt_helper = PromptHelper(
1030
+ context_window=4096,
1031
+ num_output=256,
1032
+ chunk_overlap_ratio=20 / 4096,
1033
+ tokenizer=tokenizer,
1034
+ )
1035
+
1036
+ demo = create_gradio_interface(Settings, api_keys)
1037
+ demo.launch(
1038
+ server_name="0.0.0.0",
1039
+ server_port=7860,
1040
+ share=False
1041
+ )
1042
+
1043
+ if __name__ == "__main__":
1044
+ signal.signal(signal.SIGINT, signal_handler)
1045
+ main()
Dockerfile CHANGED
@@ -6,6 +6,11 @@ COPY requirements.txt .
6
 
7
  RUN pip install --no-cache-dir -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu
8
 
 
 
 
 
 
9
  COPY . .
10
 
11
  EXPOSE 7860
 
6
 
7
  RUN pip install --no-cache-dir -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu
8
 
9
+ # تنظیم متغیر محیطی برای کش tiktoken و دانلود فایل توکنایزر در مرحله بیلد
10
+ ENV TIKTOKEN_CACHE_DIR=/tiktoken_cache
11
+ RUN mkdir -p /tiktoken_cache && \
12
+ python -c "import tiktoken; tiktoken.get_encoding('cl100k_base')"
13
+
14
  COPY . .
15
 
16
  EXPOSE 7860