kltn21110 commited on
Commit
33251a9
·
verified ·
1 Parent(s): 6f8d9d0

Update service/ChatService.py

Browse files
Files changed (1) hide show
  1. service/ChatService.py +761 -758
service/ChatService.py CHANGED
@@ -1,759 +1,762 @@
1
- from langchain_community.utilities.sql_database import SQLDatabase
2
- from langchain_experimental.sql import SQLDatabaseChain
3
- import sys
4
- import os
5
- import pymysql
6
- from fastapi import HTTPException
7
- from fastapi.encoders import jsonable_encoder
8
- import re
9
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".")))
10
- import function.prompt.prompt_main as prompt
11
- import function.prompt.prompt_custom as prompt_cus
12
- os.environ["GOOGLE_API_KEY"] = "AIzaSyDAVIagntGC7kL93qmLgNZ-is1fsb7tsN4"
13
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
14
- from bson import ObjectId
15
- import os
16
- from dotenv import load_dotenv
17
- import os
18
- from dotenv import load_dotenv
19
-
20
- import os
21
- from dotenv import load_dotenv
22
-
23
- from dotenv import load_dotenv, find_dotenv
24
- load_dotenv(find_dotenv(), override=True)
25
- DB_HOST = os.getenv("DB_HOST")
26
- DB_USER = os.getenv("DB_USER")
27
- DB_PASSWORD = os.getenv("DB_PASSWORD")
28
- DB_NAME = os.getenv("DB_NAME")
29
- DB_PORT = os.getenv("DB_PORT")
30
- import re
31
-
32
- def contains_delete(sql: str) -> bool:
33
- return bool(re.search(r'\bdelete\b', sql, re.IGNORECASE))
34
- # Tạo connection string
35
- import os
36
- from urllib.parse import quote
37
-
38
- password = os.getenv("DB_PASSWORD") # VD: 'Yahana0509@'
39
- DB_PASSWORD = quote(password)
40
- connection_uri = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
41
- db = SQLDatabase.from_uri(connection_uri)
42
- # db = SQLDatabase.from_uri("mysql+pymysql://root@127.0.0.1:3306/demohmdrinks")
43
- from dotenv import load_dotenv
44
- import function.filter.filter_role as filter_role_1
45
- import function.filter.filter_sql_injection as filter_sql_injection_1
46
- import function.filter.result as query_result_1
47
- import support.get_key as get_key
48
- import response.ResponseChat as res_chat
49
- from datetime import datetime
50
- import pytz
51
- from mongoengine import connect
52
- import sys
53
- import os
54
- import nltk
55
- import function.agent.pipeline_agent as pipeline_agent
56
- nltk.download('punkt')
57
- from models.Database_Entity import User, ChatHistory, DetailChat
58
- from dotenv import load_dotenv
59
- load_dotenv()
60
- MONGO_URI = os.getenv("MONGO_URI", "")
61
- connect("chatbot_hmdrinks", host=MONGO_URI)
62
-
63
- load_dotenv()
64
-
65
- #setup model
66
- from bson import ObjectId
67
- import random
68
- from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI
69
-
70
-
71
-
72
- BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
73
- sys.path.insert(0, BASE_DIR)
74
- from repository.MySQL import UserRepository
75
- from function.prompt.prompt_syntax_insert import is_insert_related_to_product_category_variant, filter_syntax_sql
76
- import sqlparse
77
- import sqlparse
78
- import sys
79
- import os
80
- sys.path.append(os.path.abspath(os.path.join(os.getcwd(), '..')))
81
- from function.prompt import prompt_detail_table
82
-
83
- schema_mapping = {
84
- "user": prompt_detail_table.prompt_users,
85
- "user_voucher": prompt_detail_table.prompt_user_voucher,
86
- "category": prompt_detail_table.prompt_categort,
87
- "category_translation": prompt_detail_table.prompt_category_translation,
88
- "cart": prompt_detail_table.prompt_cart,
89
- "cart_item": prompt_detail_table.prompt_cart_item,
90
- "orders":prompt_detail_table.prompt_orders,
91
- "order_item": prompt_detail_table.prompt_order_item,
92
- "payment": prompt_detail_table.prompt_payments,
93
- "favourite": prompt_detail_table.prompt_favourite,
94
- "favourite_item": prompt_detail_table.prompt_fav_item,
95
- "post": prompt_detail_table.prompt_post,
96
- "post_translation": prompt_detail_table.prompt_post_translation,
97
- "product": prompt_detail_table.prompt_product,
98
- "product_translation": prompt_detail_table.prompt_product_translation,
99
- "shipment": prompt_detail_table.prompt_shipment,
100
- "product_variants": prompt_detail_table.prompt_product_variants,
101
- "review": prompt_detail_table.prompt_review,
102
- "user_coin": prompt_detail_table.prompt_user_coin,
103
- "absence": prompt_detail_table.prompt_absence,
104
- "cart_group": prompt_detail_table.prompt_cart_group,
105
- "cart_item_group": prompt_detail_table.prompt_cartitem_group,
106
- "group_orders": prompt_detail_table.prompt_group_orders,
107
- "payments_group": prompt_detail_table.prompt_payments_group,
108
- "group_order_members":prompt_detail_table.prompt_group_orders_member,
109
- "shipment_group":prompt_detail_table.prompt_shipment_group,
110
- "shipper_attendance":prompt_detail_table.prompt_shipper_attendance,
111
- "shipper_commission_detail":prompt_detail_table.prompt_shipper_commission_detail,
112
- "shipper_salary_summary":prompt_detail_table.prompt_shipper_salary_summary,
113
- "voucher": prompt_detail_table.prompt_voucher
114
- }
115
-
116
-
117
- def get_schemas_from_sql(sql_query: str, schema_mapping: dict):
118
- import sqlglot
119
-
120
- parsed_query = sqlglot.parse_one(sql_query, read="sqlite")
121
-
122
- # Lấy danh sách bảng duy nhất trong query
123
- table_names = list({t.name for t in parsed_query.find_all(sqlglot.exp.Table)})
124
-
125
- schemas_used = {}
126
- for table in table_names:
127
- if table in schema_mapping:
128
- schemas_used[table] = schema_mapping[table]
129
- else:
130
- print(f"⚠️ Warning: Table '{table}' not found in schema_mapping")
131
-
132
- # Gom toàn bộ schema thành 1 chuỗi duy nhất
133
- all_schemas = "\n\n".join(
134
- [f"Schema for table '{table}':\n{schemas_used[table]}" for table in schemas_used]
135
- )
136
-
137
- return all_schemas
138
-
139
-
140
- def build_sql_fix_prompt(schemas_result: dict, sql: str) -> str:
141
-
142
- prompt = f"""
143
- Bạn một chuyên gia sở dữ liệu.
144
-
145
- Dưới đây mô tả schema chi tiết của các bảng có trong hệ thống:
146
-
147
- {schemas_result}
148
-
149
- ---
150
-
151
- Dưới đây là một câu SQL đang bị lỗi do không đúng tên bảng hoặc tên cột:
152
-
153
- ```sql
154
- {sql.strip()}
155
- Yêu cầu của bạn là:
156
-
157
- Dựa trên các schema ở trên, hãy kiểm tra và chỉnh sửa câu SQL sao cho:
158
- Tên bảng, tên cột phải chính xác theo schema.
159
- Logic và mục đích của truy vấn được giữ nguyên.
160
- Chỉ trả lại phần SQL đã được chỉnh sửa (không giải thích, không chú thích, không thêm nhận xét).
161
- Trả lời dưới dạng một truy vấn SQL duy nhất.
162
- """.strip()
163
- return prompt
164
-
165
- async def execute_query_user(user_input: str, user_id: int, languages: str, role: str):
166
- api_key = get_key.get_random_api_key()
167
- os.environ["GOOGLE_API_KEY"] = api_key
168
- llm1 = ChatGoogleGenerativeAI(model='gemini-2.5-flash-preview-04-17',temperature=0.6)
169
- # db = SQLDatabase.from_uri("mysql+pymysql://root@127.0.0.1:3306/demohmdrinks")
170
- db = SQLDatabase.from_uri(connection_uri)
171
- PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_input)
172
- check_insert = is_insert_related_to_product_category_variant(user_input)
173
-
174
- db_config = {
175
- "host": os.getenv("DB_HOST"),
176
- "user": os.getenv("DB_USER"),
177
- "database": os.getenv("DB_NAME"),
178
- "password": os.getenv("DB_PASSWORD"),
179
- "port": int(os.getenv("DB_PORT", 3306)),
180
- "charset": "utf8mb4",
181
- "cursorclass": pymysql.cursors.DictCursor,
182
- }
183
-
184
- def execute_query_with_pymysql(query, multi=False):
185
- connection = pymysql.connect(**db_config)
186
- try:
187
- with connection.cursor() as cursor:
188
- results = []
189
- if multi:
190
- statements = sqlparse.split(query)
191
- for stmt in statements:
192
- stmt = stmt.strip()
193
- if stmt:
194
- try:
195
- cursor.execute(stmt)
196
- try:
197
- results.append(cursor.fetchall())
198
- except pymysql.ProgrammingError:
199
- results.append("✅ Executed")
200
- except Exception as e:
201
- results.append(f"❌ Error in query: {stmt}\n{str(e)}")
202
- else:
203
- try:
204
- cursor.execute(query)
205
- results = cursor.fetchall()
206
- except Exception as e:
207
- return f"❌ Error executing query: {str(e)}"
208
- connection.commit()
209
- return results
210
- except pymysql.MySQLError as e:
211
- return f"❌ MySQL Error: {str(e)}"
212
- finally:
213
- connection.close()
214
-
215
- def clean_sql(sql: str) -> str:
216
- sql = re.sub(r"```sql", "", sql, flags=re.IGNORECASE)
217
- sql = re.sub(r'%%s', r'%s', sql)
218
- sql = re.sub(r"```", "", sql)
219
- return sql.strip()
220
-
221
- def extract_sql_from_response(data):
222
- match = re.search(r"SQLQuery:\s*(.*)", data, re.DOTALL)
223
- return clean_sql(match.group(1)) if match else None
224
-
225
- def extract_sql_from_error(error_msg):
226
- match = re.search(r"```sql\n(.*?)```", error_msg, re.DOTALL)
227
- return clean_sql(match.group(1)) if match else None
228
-
229
- def process_and_execute_sql(sql):
230
- sql_clean = clean_sql(sql)
231
- result = get_schemas_from_sql(sql_clean, schema_mapping)
232
- prompt = build_sql_fix_prompt(result,sql =sql_clean)
233
- from function.advance_shopping.call_gemini import tool_call
234
- data = tool_call.generate(prompt = prompt)
235
- sql_clean = clean_sql(data)
236
- print("SQL step2: ", sql_clean)
237
- if contains_delete(sql_clean):
238
- return "Lỗi: Bạn không dược phép thực hiện truy vấn DELETE trong hệ thống này."
239
- if re.search(r"\\bIF\\b.*\\bTHEN\\b", sql_clean, re.IGNORECASE):
240
- return "❌ Lỗi: Không được dùng IF...THEN trong SQL. Vui lòng chia nhỏ truy vấn."
241
-
242
- if check_insert:
243
- check_syntax = filter_syntax_sql(sql_clean, PROMPT_CUSTOM, user_input)
244
- if check_syntax is True:
245
- try:
246
- connection = pymysql.connect(**db_config)
247
- with connection.cursor() as cursor:
248
- statements = sqlparse.split(sql_clean)
249
- results = []
250
- for stmt in statements:
251
- stmt = stmt.strip()
252
- if stmt:
253
- try:
254
- cursor.execute(stmt)
255
- try:
256
- results.append(cursor.fetchall())
257
- except:
258
- results.append("✅ OK")
259
- except Exception as e:
260
- return f"❌ Lỗi tại truy vấn: `{stmt}`\nChi tiết: {str(e)}"
261
- connection.commit()
262
- return results
263
- except Exception as e:
264
- return f"❌ Lỗi khi thực thi từng truy vấn: {str(e)}"
265
- finally:
266
- connection.close()
267
- else:
268
- try:
269
- regenerated_data = db_chain.run(f"""
270
- Role: {text_role}
271
- Language: {languages}
272
- Question: {user_input}.
273
- """)
274
- regenerated_sql = extract_sql_from_response(regenerated_data)
275
- if regenerated_sql:
276
- return process_and_execute_sql(regenerated_sql)
277
- else:
278
- return "❌ Lỗi: Không thể tạo lại truy vấn hợp lệ."
279
- except Exception as regen_error:
280
- return f"❌ Lỗi khi tạo lại truy vấn: {str(regen_error)}"
281
- else:
282
- return execute_query_with_pymysql(sql_clean, multi=True)
283
-
284
- if "Do not use IF...THEN" not in PROMPT_CUSTOM.template:
285
- PROMPT_CUSTOM.template += (
286
- "\n\n⚠️ Note: Do NOT use IF...THEN...ELSE...END in SQL. "
287
- "Only use plain SELECT, INSERT, UPDATE, DELETE, SET statements."
288
- )
289
-
290
- db_chain = SQLDatabaseChain.from_llm(llm=llm1, db=db, prompt=PROMPT_CUSTOM)
291
- text_role = f"{role} (userId = {user_id})" if role == "ADMIN" else f"{role} (userId = {user_id}), not role ADMIN"
292
-
293
- try:
294
- data = db_chain.run(f"""
295
- Role: {text_role}
296
- Language: {languages}
297
- Question: {user_input}.
298
- """)
299
- extracted_sql = extract_sql_from_response(data)
300
- if extracted_sql:
301
- return process_and_execute_sql(extracted_sql)
302
- else:
303
- return data
304
-
305
- except Exception as e:
306
- error_message = str(e)
307
- extracted_sql = extract_sql_from_error(error_message)
308
- fix_sql = extracted_sql.replace("```","")
309
- fix_sql = re.sub(r"```sql", "", fix_sql)
310
- fix_sql = re.sub(r'%%s', r'%s', fix_sql)
311
- if contains_delete(fix_sql):
312
- return "Lỗi: Bạn không dược phép thực hiện truy vấn DELETE trong hệ thống này."
313
- if extracted_sql:
314
-
315
- return process_and_execute_sql(fix_sql)
316
- else:
317
- return f"❌ Lỗi không thể thực thi truy vấn: {error_message}"
318
-
319
-
320
- async def create_new_chat_history(user_id: int) -> str:
321
- """
322
- Tạo một đoạn chat mới cho user_id và trả về ObjectId của đoạn chat.
323
- """
324
- check = UserRepository.getUserByUserId(user_id)
325
- if check is None:
326
- raise HTTPException(status_code=404, detail="User not found or has been deleted in MySQL")
327
-
328
- user = User.objects(user_id=user_id).first()
329
- if not user:
330
- user = User(id=ObjectId(), user_id=user_id, user_name=f"User_{user_id}")
331
- user.save()
332
- random_name_chat = str(random.randint(10**14, 10**15 - 1))
333
- name_chat = f"Chat_{random_name_chat}"
334
- new_chat = ChatHistory(id=ObjectId(), user=user, name_chat=name_chat)
335
- new_chat.save()
336
-
337
- return res_chat.CreateNewChat(idMongo=str(new_chat.id), chat_name=name_chat)
338
-
339
-
340
- async def update_chat_name(chat_id: str, new_name: str,user_id:int) -> str:
341
- """
342
- Cập nhật name_chat của một ChatHistory dựa trên chat_id.
343
- """
344
- check = UserRepository.getUserByUserId(user_id)
345
- if check is None:
346
- raise HTTPException(status_code=404, detail="User not found or has been deleted in MySQL")
347
-
348
- user = User.objects(user_id=user_id, is_deleted=False).first()
349
- if not user:
350
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
351
-
352
- chat = ChatHistory.objects(_id=ObjectId(chat_id)).first()
353
-
354
- if not chat:
355
- raise HTTPException(status_code=404, detail="Chat not found in MongoDB")
356
-
357
- chat.name_chat = new_name
358
- chat.save()
359
-
360
- return f"Updated chat name to {new_name}"
361
-
362
-
363
- async def soft_delete_chat(chat_id: str,user_id:int):
364
- """
365
- Cập nhật `is_deleted=True` và `date_deleted` cho `ChatHistory` và các `DetailChat` liên quan.
366
- """
367
- check = UserRepository.getUserByUserId(user_id)
368
- if check is None:
369
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
370
-
371
- check_history_id = UserRepository.getChatHistory(user_id,chat_id)
372
- if check_history_id is None:
373
- raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MySQL")
374
-
375
- user = User.objects(user_id=user_id, is_deleted=False).first()
376
- if not user:
377
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
378
-
379
- chat = ChatHistory.objects(_id=ObjectId(chat_id)).first()
380
-
381
- if not chat:
382
- raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MongoDB")
383
- chat.is_deleted = True
384
- chat.date_deleted = datetime.now(pytz.UTC)
385
- chat.save()
386
-
387
- DetailChat.objects(chat_history=chat).update(
388
- set__is_deleted=True,
389
- set__date_deleted=datetime.now(pytz.UTC)
390
- )
391
-
392
- return {"message": "Chat and related details marked as deleted"}
393
- from typing import Optional
394
- from models.Database_Entity import StopSignal
395
- from datetime import datetime, timedelta
396
- import asyncio
397
- async def chat_with_user(
398
- user_input: str,
399
- user_id: int,
400
- languages: str,
401
- role: str,
402
- token: str,
403
- chat_history_id: str = None,
404
- stop_event: Optional[asyncio.Event] = None
405
-
406
- ) -> str:
407
- """
408
- Xử lý tin nhắn của người dùng, lưu vào lịch sử chat và trả về phản hồi từ AI.
409
- """
410
-
411
- if role not in ["ADMIN", "CUSTOMER", "SHIPPER"]:
412
- raise HTTPException(status_code=400, detail="ROLE not valid")
413
- user_id = int(user_id)
414
- if languages not in ["VN", "EN"]:
415
- raise HTTPException(status_code=400, detail="Language not valid")
416
-
417
- if not user_input:
418
- raise HTTPException(status_code=400, detail="User input empty")
419
- if not isinstance(user_id, int) or user_id <= 0:
420
- raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer")
421
-
422
- languages = "Vietnamese" if languages == "VN" else "English"
423
-
424
- check = UserRepository.getUserByUserId(user_id)
425
- if check is None:
426
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
427
-
428
- check_history_id = UserRepository.getChatHistory(user_id,chat_history_id)
429
- if check_history_id is None:
430
- raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL")
431
-
432
- user = User.objects(user_id=user_id).first()
433
- if not user:
434
- return {"error": "User not found or has been deleted in MongoDB"}
435
-
436
- chat_history = None
437
- if chat_history_id:
438
- try:
439
- chat_history_obj_id = ObjectId(chat_history_id) # Chuyển đổi sang ObjectId
440
- chat_history = ChatHistory.objects(_id=chat_history_obj_id, user=user).first()
441
- except Exception as e:
442
-
443
- print(f"⚠️ Invalid chat_history_id: {e}")
444
- if not chat_history:
445
- raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB")
446
- await asyncio.sleep(0.1)
447
- if stop_event and stop_event.is_set():
448
- raise asyncio.CancelledError(" Task was cancelled before processing")
449
- await asyncio.sleep(0.1)
450
- if StopSignal.objects(chat_history=chat_history_id, is_stopped=True).first():
451
- print("🛑 Dừng StopSignal trong DB.")
452
- raise asyncio.CancelledError("⛔ Task was cancelled before processing")
453
- result_final = await pipeline_agent.multi_query_user(
454
- user_input, user_id, role, languages, chat_history_id, token,stop_event
455
- )
456
- detail_chat = DetailChat(
457
- id=ObjectId(),
458
- chat_history=chat_history,
459
- you_message=user_input,
460
- ai_message=result_final,
461
- timestamp=datetime.now(pytz.UTC)
462
- )
463
- detail_chat.save()
464
- chat_history_messages = DetailChat.objects(chat_history=chat_history).order_by('timestamp')
465
-
466
-
467
- def convert_to_vn_time(timestamp):
468
- return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S')
469
-
470
- sorted_messages = sorted(chat_history_messages, key=lambda msg: msg.timestamp, reverse=True)
471
-
472
- formatted_messages = [
473
- {
474
- "index": i + 1, # Đánh số từ 1
475
- "id": str(msg.id),
476
- "you_message": msg.you_message,
477
- "ai_message": msg.ai_message,
478
- "timestamp": convert_to_vn_time(msg.timestamp)
479
- }
480
- for i, msg in enumerate(sorted_messages)
481
- ]
482
-
483
- return jsonable_encoder({
484
- "new_message": {
485
- "id": str(detail_chat.id),
486
- "you_message": detail_chat.you_message,
487
- "ai_message": detail_chat.ai_message,
488
- "timestamp": convert_to_vn_time(detail_chat.timestamp)
489
- },
490
- "previous_messages": formatted_messages
491
- })
492
-
493
- from bson import ObjectId
494
-
495
-
496
- async def get_chat_details(chat_id: str,user_id:int):
497
- """
498
- Lấy tất cả `DetailChat` thuộc `ChatHistory` có `chat_id`, chỉ lấy bản ghi `is_deleted=False`.
499
- """
500
-
501
- check = UserRepository.getUserByUserId(user_id)
502
- if check is None:
503
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
504
-
505
- check_history_id = UserRepository.getChatHistory(user_id,chat_id)
506
- if check_history_id is None:
507
- raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MySQL")
508
-
509
- user = User.objects(user_id=user_id, is_deleted=False).first()
510
- if not user:
511
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
512
-
513
- chat = ChatHistory.objects(_id=ObjectId(chat_id), is_deleted=False).first()
514
-
515
- if not chat:
516
- raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB")
517
-
518
- chat_details = DetailChat.objects(chat_history=chat, is_deleted=False)
519
- def convert_to_vn_time(timestamp):
520
- return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S')
521
-
522
- list_detail_response = [
523
- res_chat.DetailResponse(
524
- id=str(index + 1), # ✅ Đánh số lại từ 1
525
- you_message=detail.you_message,
526
- ai_message=detail.ai_message,
527
- timestamp=convert_to_vn_time(detail.timestamp) # ✅ Chuyển sang GMT+7
528
- )
529
- for index, detail in enumerate(sorted(chat_details, key=lambda d: d.timestamp, reverse=True)) # ✅ Sắp xếp giảm dần
530
- ]
531
-
532
- return res_chat.ListDetailResponse(
533
- chat_id=str(chat.id),
534
- chat_name=chat.name_chat,
535
- list_detail_response=list_detail_response
536
- )
537
-
538
-
539
-
540
- async def regenerate(
541
- user_question_new: str,
542
- user_id: int,
543
- languages: str,
544
- role: str,
545
- token: str,
546
- chat_history_id: str = None,
547
- stop_event: Optional[asyncio.Event] = None
548
- ) -> str:
549
- """
550
- Xử tin nhắn của người dùng, lưu vào lịch sử chat và trả về phản hồi từ AI.
551
- """
552
- PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_question_new)
553
- if role not in ["ADMIN", "CUSTOMER", "SHIPPER"]:
554
- raise HTTPException(status_code=400, detail="ROLE not valid")
555
- user_id = int(user_id)
556
- if languages not in ["VN", "EN"]:
557
- raise HTTPException(status_code=400, detail="Language not valid")
558
-
559
- if not user_question_new:
560
- raise HTTPException(status_code=400, detail="User input empty")
561
- if not isinstance(user_id, int) or user_id <= 0:
562
- raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer")
563
-
564
- languages = "Vietnamese" if languages == "VN" else "English"
565
-
566
- check = UserRepository.getUserByUserId(user_id)
567
- if check is None:
568
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
569
-
570
- check_history_id = UserRepository.getChatHistory(user_id,chat_history_id)
571
- if check_history_id is None:
572
- raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL")
573
-
574
- user = User.objects(user_id=user_id).first()
575
- if not user:
576
- return {"error": "User not found or has been deleted in MongoDB"}
577
-
578
- chat_history = None
579
- if chat_history_id:
580
- try:
581
- chat_history_obj_id = ObjectId(chat_history_id) # Chuyển đổi sang ObjectId
582
- chat_history = ChatHistory.objects(_id=chat_history_obj_id, user=user).first()
583
- except Exception as e:
584
-
585
- print(f"⚠️ Invalid chat_history_id: {e}")
586
- if not chat_history:
587
- raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB")
588
- # filtered_input = filter_sql_injection_1.filter_sql_injection(user_input)
589
- # filtered_role_input = filter_role_1.filter_role(filtered_input)
590
- # result = await execute_query_user(filtered_role_input, user_id, languages, role)
591
- # result_final = query_result_1.query_result(user_input, result)
592
- if chat_history:
593
- last_chat = (
594
- DetailChat.objects(chat_history=chat_history, is_deleted=False)
595
- .order_by("-timestamp")
596
- .first()
597
- )
598
-
599
- await asyncio.sleep(0.1)
600
- if stop_event and stop_event.is_set():
601
- raise asyncio.CancelledError("⛔ Task was cancelled before processing")
602
- await asyncio.sleep(0.1)
603
- if StopSignal.objects(chat_history=chat_history_id, is_stopped=True).first():
604
- print("🛑 Dừng StopSignal trong DB.")
605
- raise asyncio.CancelledError("⛔ Task was cancelled before processing")
606
- result_final = await pipeline_agent.multi_query_user(user_question_new,user_id,role,languages,chat_history_id,token,stop_event)
607
- last_chat.update(set__you_message=user_question_new, set__ai_message=result_final, set__timestamp = datetime.now(pytz.UTC))
608
-
609
- last_chat_result = (
610
- DetailChat.objects(chat_history=chat_history, is_deleted=False)
611
- .order_by("-timestamp")
612
- .first()
613
- )
614
-
615
- chat_history_messages = DetailChat.objects(chat_history=chat_history).order_by('timestamp')
616
-
617
-
618
- def convert_to_vn_time(timestamp):
619
- return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S')
620
-
621
- sorted_messages = sorted(chat_history_messages, key=lambda msg: msg.timestamp, reverse=True)
622
-
623
- formatted_messages = [
624
- {
625
- "index": i + 1, # Đánh số từ 1
626
- "id": str(msg.id),
627
- "you_message": msg.you_message,
628
- "ai_message": msg.ai_message,
629
- "timestamp": convert_to_vn_time(msg.timestamp)
630
- }
631
- for i, msg in enumerate(sorted_messages)
632
- ]
633
-
634
- return jsonable_encoder({
635
- "new_message": {
636
- "id": str(last_chat_result.id),
637
- "you_message": last_chat_result.you_message,
638
- "ai_message": last_chat_result.ai_message,
639
- "timestamp": convert_to_vn_time(last_chat_result.timestamp)
640
- },
641
- "previous_messages": formatted_messages
642
- })
643
-
644
- from bson import ObjectId
645
-
646
-
647
- async def get_user_chat_history(user_id: int):
648
- """
649
- API lấy danh sách tất cả các đoạn chat của một user_id.
650
- """
651
- check = UserRepository.getUserByUserId(user_id)
652
- if check is None:
653
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
654
- user = User.objects(user_id=user_id, is_deleted=False).first()
655
- if not user:
656
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
657
- chat_histories = ChatHistory.objects(user=user, is_deleted=False)
658
- chat_list = [
659
- res_chat.ChatResponse(
660
- chat_id=str(chat.id),
661
- chat_name=chat.name_chat,
662
- timestamp=chat.date_deleted if chat.is_deleted else chat.id.generation_time
663
- )
664
- for chat in chat_histories
665
- ]
666
-
667
- return res_chat.UserChatHistoryResponse(
668
- user_id=user.user_id,
669
- user_name=user.user_name,
670
- chat_list=chat_list
671
- )
672
-
673
-
674
- from bson import ObjectId
675
-
676
- async def get_chat_details_text(chat_id: str, user_id: int):
677
- """
678
- Trích xuất tất cả các chi tiết chat của một chat_id, gom thành một đoạn văn bản.
679
- """
680
- # Kiểm tra xem user có tồn tại không
681
- user = User.objects(user_id=user_id, is_deleted=False).first()
682
- if not user:
683
- raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
684
-
685
- # Kiểm tra xem chat history có tồn tại không
686
- chat = ChatHistory.objects(_id=ObjectId(chat_id), user=user, is_deleted=False).first()
687
- if not chat:
688
- raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MongoDB")
689
- chat_details = DetailChat.objects(chat_history=chat, is_deleted=False).order_by('timestamp')
690
- if not chat_details:
691
- return list()
692
-
693
- # Gom tất cả các câu hỏi và câu trả lời vào danh sách
694
- chat_text_list = []
695
- for index, detail in enumerate(chat_details,start=1):
696
- chat_text_list.append({
697
- "order":str(index),
698
- "timestamp": detail.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
699
- "you_message": detail.you_message,
700
- "ai_message": detail.ai_message
701
- })
702
-
703
- return chat_text_list
704
-
705
-
706
- import asyncio
707
- import os
708
- import subprocess
709
- from datetime import datetime
710
- from pathlib import Path
711
- from function.analyze import main
712
- from function.analyze.gemini import result_analyze
713
- async def generate_and_save_code(question: str, user_id: int, role, languages: str, filename: str = "analyze_result.py"):
714
- code_test, folder_name = await main.analyze(
715
- question,
716
- user_id,
717
- languages,
718
- role
719
- )
720
-
721
- code_clean = code_test.strip()
722
- if code_clean.startswith("```python"):
723
- code_clean = code_clean[9:].strip()
724
- if code_clean.endswith("```"):
725
- code_clean = code_clean[:-3].strip()
726
-
727
- # code_clean = code_clean.replace("else:", "").strip()
728
- code_clean = code_clean.replace("os_path:", "os.path").strip()
729
- encoding_fix = 'import sys\nsys.stdout.reconfigure(encoding="utf-8")\n\n'
730
- encoding_fix1= 'import numpy as np\n\n'
731
- code_clean = encoding_fix + encoding_fix1 + code_clean
732
-
733
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
734
- output_dir = Path(f"./Temp/{folder_name}_{timestamp}")
735
- output_dir.mkdir(parents=True, exist_ok=True)
736
-
737
-
738
- file_path = output_dir / filename
739
- with open(file_path, "w", encoding="utf-8") as f:
740
- f.write(code_clean)
741
-
742
-
743
- env = os.environ.copy()
744
- env["OUTPUT_DIR"] = str(output_dir)
745
- result = subprocess.run(
746
- ["python", filename],
747
- capture_output=True,
748
- text=True,
749
- env=env,
750
- cwd=output_dir,
751
- encoding="utf-8",
752
- errors="replace"
753
- )
754
-
755
- output_folder = str(output_dir)
756
- absolute_path = os.path.abspath(output_folder)
757
- final_path = os.path.join(absolute_path, "test5")
758
- result_final = result_analyze.generate(image_folder=final_path,question=question)
 
 
 
759
  return result_final,final_path
 
1
+ from langchain_community.utilities.sql_database import SQLDatabase
2
+ from langchain_experimental.sql import SQLDatabaseChain
3
+ import sys
4
+ import os
5
+ import pymysql
6
+ from fastapi import HTTPException
7
+ from fastapi.encoders import jsonable_encoder
8
+ import re
9
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".")))
10
+ import function.prompt.prompt_main as prompt
11
+ import function.prompt.prompt_custom as prompt_cus
12
+ os.environ["GOOGLE_API_KEY"] = "AIzaSyDAVIagntGC7kL93qmLgNZ-is1fsb7tsN4"
13
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
14
+ from bson import ObjectId
15
+ import os
16
+ from dotenv import load_dotenv
17
+ import os
18
+ from dotenv import load_dotenv
19
+
20
+ import os
21
+ from dotenv import load_dotenv
22
+
23
+ from dotenv import load_dotenv, find_dotenv
24
+ load_dotenv(find_dotenv(), override=True)
25
+ DB_HOST = os.getenv("DB_HOST")
26
+ DB_USER = os.getenv("DB_USER")
27
+ DB_PASSWORD = os.getenv("DB_PASSWORD")
28
+ DB_NAME = os.getenv("DB_NAME")
29
+ DB_PORT = os.getenv("DB_PORT")
30
+ import re
31
+
32
+ def contains_delete(sql: str) -> bool:
33
+ return bool(re.search(r'\bdelete\b', sql, re.IGNORECASE))
34
+ # Tạo connection string
35
+ import os
36
+ from urllib.parse import quote
37
+
38
+ password = os.getenv("DB_PASSWORD") # VD: 'Yahana0509@'
39
+ DB_PASSWORD = quote(password)
40
+ connection_uri = (
41
+ f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
42
+ "?ssl_verify_cert=false&ssl_verify_identity=false"
43
+ )
44
+ db = SQLDatabase.from_uri(connection_uri)
45
+ # db = SQLDatabase.from_uri("mysql+pymysql://root@127.0.0.1:3306/demohmdrinks")
46
+ from dotenv import load_dotenv
47
+ import function.filter.filter_role as filter_role_1
48
+ import function.filter.filter_sql_injection as filter_sql_injection_1
49
+ import function.filter.result as query_result_1
50
+ import support.get_key as get_key
51
+ import response.ResponseChat as res_chat
52
+ from datetime import datetime
53
+ import pytz
54
+ from mongoengine import connect
55
+ import sys
56
+ import os
57
+ import nltk
58
+ import function.agent.pipeline_agent as pipeline_agent
59
+ nltk.download('punkt')
60
+ from models.Database_Entity import User, ChatHistory, DetailChat
61
+ from dotenv import load_dotenv
62
+ load_dotenv()
63
+ MONGO_URI = os.getenv("MONGO_URI", "")
64
+ connect("chatbot_hmdrinks", host=MONGO_URI)
65
+
66
+ load_dotenv()
67
+
68
+ #setup model
69
+ from bson import ObjectId
70
+ import random
71
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI
72
+
73
+
74
+
75
+ BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
76
+ sys.path.insert(0, BASE_DIR)
77
+ from repository.MySQL import UserRepository
78
+ from function.prompt.prompt_syntax_insert import is_insert_related_to_product_category_variant, filter_syntax_sql
79
+ import sqlparse
80
+ import sqlparse
81
+ import sys
82
+ import os
83
+ sys.path.append(os.path.abspath(os.path.join(os.getcwd(), '..')))
84
+ from function.prompt import prompt_detail_table
85
+
86
+ schema_mapping = {
87
+ "user": prompt_detail_table.prompt_users,
88
+ "user_voucher": prompt_detail_table.prompt_user_voucher,
89
+ "category": prompt_detail_table.prompt_categort,
90
+ "category_translation": prompt_detail_table.prompt_category_translation,
91
+ "cart": prompt_detail_table.prompt_cart,
92
+ "cart_item": prompt_detail_table.prompt_cart_item,
93
+ "orders":prompt_detail_table.prompt_orders,
94
+ "order_item": prompt_detail_table.prompt_order_item,
95
+ "payment": prompt_detail_table.prompt_payments,
96
+ "favourite": prompt_detail_table.prompt_favourite,
97
+ "favourite_item": prompt_detail_table.prompt_fav_item,
98
+ "post": prompt_detail_table.prompt_post,
99
+ "post_translation": prompt_detail_table.prompt_post_translation,
100
+ "product": prompt_detail_table.prompt_product,
101
+ "product_translation": prompt_detail_table.prompt_product_translation,
102
+ "shipment": prompt_detail_table.prompt_shipment,
103
+ "product_variants": prompt_detail_table.prompt_product_variants,
104
+ "review": prompt_detail_table.prompt_review,
105
+ "user_coin": prompt_detail_table.prompt_user_coin,
106
+ "absence": prompt_detail_table.prompt_absence,
107
+ "cart_group": prompt_detail_table.prompt_cart_group,
108
+ "cart_item_group": prompt_detail_table.prompt_cartitem_group,
109
+ "group_orders": prompt_detail_table.prompt_group_orders,
110
+ "payments_group": prompt_detail_table.prompt_payments_group,
111
+ "group_order_members":prompt_detail_table.prompt_group_orders_member,
112
+ "shipment_group":prompt_detail_table.prompt_shipment_group,
113
+ "shipper_attendance":prompt_detail_table.prompt_shipper_attendance,
114
+ "shipper_commission_detail":prompt_detail_table.prompt_shipper_commission_detail,
115
+ "shipper_salary_summary":prompt_detail_table.prompt_shipper_salary_summary,
116
+ "voucher": prompt_detail_table.prompt_voucher
117
+ }
118
+
119
+
120
+ def get_schemas_from_sql(sql_query: str, schema_mapping: dict):
121
+ import sqlglot
122
+
123
+ parsed_query = sqlglot.parse_one(sql_query, read="sqlite")
124
+
125
+ # Lấy danh sách bảng duy nhất trong query
126
+ table_names = list({t.name for t in parsed_query.find_all(sqlglot.exp.Table)})
127
+
128
+ schemas_used = {}
129
+ for table in table_names:
130
+ if table in schema_mapping:
131
+ schemas_used[table] = schema_mapping[table]
132
+ else:
133
+ print(f"⚠️ Warning: Table '{table}' not found in schema_mapping")
134
+
135
+ # Gom toàn bộ schema thành 1 chuỗi duy nhất
136
+ all_schemas = "\n\n".join(
137
+ [f"Schema for table '{table}':\n{schemas_used[table]}" for table in schemas_used]
138
+ )
139
+
140
+ return all_schemas
141
+
142
+
143
+ def build_sql_fix_prompt(schemas_result: dict, sql: str) -> str:
144
+
145
+ prompt = f"""
146
+ Bạn là một chuyên gia cơ sở dữ liệu.
147
+
148
+ Dưới đây là mô tả schema chi tiết của các bảng có trong hệ thống:
149
+
150
+ {schemas_result}
151
+
152
+ ---
153
+
154
+ Dưới đây là một câu SQL đang bị lỗi do không đúng tên bảng hoặc tên cột:
155
+
156
+ ```sql
157
+ {sql.strip()}
158
+ Yêu cầu của bạn là:
159
+
160
+ Dựa trên các schema trên, hãy kiểm tra chỉnh sửa câu SQL sao cho:
161
+ Tên bảng, tên cột phải chính xác theo schema.
162
+ Logic và mục đích của truy vấn được giữ nguyên.
163
+ Chỉ trả lại phần SQL đã được chỉnh sửa (không giải thích, không chú thích, không thêm nhận xét).
164
+ Trả lời dưới dạng một truy vấn SQL duy nhất.
165
+ """.strip()
166
+ return prompt
167
+
168
+ async def execute_query_user(user_input: str, user_id: int, languages: str, role: str):
169
+ api_key = get_key.get_random_api_key()
170
+ os.environ["GOOGLE_API_KEY"] = api_key
171
+ llm1 = ChatGoogleGenerativeAI(model='gemini-2.5-flash-preview-04-17',temperature=0.6)
172
+ # db = SQLDatabase.from_uri("mysql+pymysql://root@127.0.0.1:3306/demohmdrinks")
173
+ db = SQLDatabase.from_uri(connection_uri)
174
+ PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_input)
175
+ check_insert = is_insert_related_to_product_category_variant(user_input)
176
+
177
+ db_config = {
178
+ "host": os.getenv("DB_HOST"),
179
+ "user": os.getenv("DB_USER"),
180
+ "database": os.getenv("DB_NAME"),
181
+ "password": os.getenv("DB_PASSWORD"),
182
+ "port": int(os.getenv("DB_PORT", 3306)),
183
+ "charset": "utf8mb4",
184
+ "cursorclass": pymysql.cursors.DictCursor,
185
+ }
186
+
187
+ def execute_query_with_pymysql(query, multi=False):
188
+ connection = pymysql.connect(**db_config)
189
+ try:
190
+ with connection.cursor() as cursor:
191
+ results = []
192
+ if multi:
193
+ statements = sqlparse.split(query)
194
+ for stmt in statements:
195
+ stmt = stmt.strip()
196
+ if stmt:
197
+ try:
198
+ cursor.execute(stmt)
199
+ try:
200
+ results.append(cursor.fetchall())
201
+ except pymysql.ProgrammingError:
202
+ results.append("✅ Executed")
203
+ except Exception as e:
204
+ results.append(f"❌ Error in query: {stmt}\n{str(e)}")
205
+ else:
206
+ try:
207
+ cursor.execute(query)
208
+ results = cursor.fetchall()
209
+ except Exception as e:
210
+ return f"❌ Error executing query: {str(e)}"
211
+ connection.commit()
212
+ return results
213
+ except pymysql.MySQLError as e:
214
+ return f"❌ MySQL Error: {str(e)}"
215
+ finally:
216
+ connection.close()
217
+
218
+ def clean_sql(sql: str) -> str:
219
+ sql = re.sub(r"```sql", "", sql, flags=re.IGNORECASE)
220
+ sql = re.sub(r'%%s', r'%s', sql)
221
+ sql = re.sub(r"```", "", sql)
222
+ return sql.strip()
223
+
224
+ def extract_sql_from_response(data):
225
+ match = re.search(r"SQLQuery:\s*(.*)", data, re.DOTALL)
226
+ return clean_sql(match.group(1)) if match else None
227
+
228
+ def extract_sql_from_error(error_msg):
229
+ match = re.search(r"```sql\n(.*?)```", error_msg, re.DOTALL)
230
+ return clean_sql(match.group(1)) if match else None
231
+
232
+ def process_and_execute_sql(sql):
233
+ sql_clean = clean_sql(sql)
234
+ result = get_schemas_from_sql(sql_clean, schema_mapping)
235
+ prompt = build_sql_fix_prompt(result,sql =sql_clean)
236
+ from function.advance_shopping.call_gemini import tool_call
237
+ data = tool_call.generate(prompt = prompt)
238
+ sql_clean = clean_sql(data)
239
+ print("SQL step2: ", sql_clean)
240
+ if contains_delete(sql_clean):
241
+ return "Lỗi: Bạn không dược phép thực hiện truy vấn DELETE trong hệ thống này."
242
+ if re.search(r"\\bIF\\b.*\\bTHEN\\b", sql_clean, re.IGNORECASE):
243
+ return "❌ Lỗi: Không được dùng IF...THEN trong SQL. Vui lòng chia nhỏ truy vấn."
244
+
245
+ if check_insert:
246
+ check_syntax = filter_syntax_sql(sql_clean, PROMPT_CUSTOM, user_input)
247
+ if check_syntax is True:
248
+ try:
249
+ connection = pymysql.connect(**db_config)
250
+ with connection.cursor() as cursor:
251
+ statements = sqlparse.split(sql_clean)
252
+ results = []
253
+ for stmt in statements:
254
+ stmt = stmt.strip()
255
+ if stmt:
256
+ try:
257
+ cursor.execute(stmt)
258
+ try:
259
+ results.append(cursor.fetchall())
260
+ except:
261
+ results.append("✅ OK")
262
+ except Exception as e:
263
+ return f"❌ Lỗi tại truy vấn: `{stmt}`\nChi tiết: {str(e)}"
264
+ connection.commit()
265
+ return results
266
+ except Exception as e:
267
+ return f"❌ Lỗi khi thực thi từng truy vấn: {str(e)}"
268
+ finally:
269
+ connection.close()
270
+ else:
271
+ try:
272
+ regenerated_data = db_chain.run(f"""
273
+ Role: {text_role}
274
+ Language: {languages}
275
+ Question: {user_input}.
276
+ """)
277
+ regenerated_sql = extract_sql_from_response(regenerated_data)
278
+ if regenerated_sql:
279
+ return process_and_execute_sql(regenerated_sql)
280
+ else:
281
+ return "❌ Lỗi: Không thể tạo lại truy vấn hợp lệ."
282
+ except Exception as regen_error:
283
+ return f"❌ Lỗi khi tạo lại truy vấn: {str(regen_error)}"
284
+ else:
285
+ return execute_query_with_pymysql(sql_clean, multi=True)
286
+
287
+ if "Do not use IF...THEN" not in PROMPT_CUSTOM.template:
288
+ PROMPT_CUSTOM.template += (
289
+ "\n\n⚠️ Note: Do NOT use IF...THEN...ELSE...END in SQL. "
290
+ "Only use plain SELECT, INSERT, UPDATE, DELETE, SET statements."
291
+ )
292
+
293
+ db_chain = SQLDatabaseChain.from_llm(llm=llm1, db=db, prompt=PROMPT_CUSTOM)
294
+ text_role = f"{role} (userId = {user_id})" if role == "ADMIN" else f"{role} (userId = {user_id}), not role ADMIN"
295
+
296
+ try:
297
+ data = db_chain.run(f"""
298
+ Role: {text_role}
299
+ Language: {languages}
300
+ Question: {user_input}.
301
+ """)
302
+ extracted_sql = extract_sql_from_response(data)
303
+ if extracted_sql:
304
+ return process_and_execute_sql(extracted_sql)
305
+ else:
306
+ return data
307
+
308
+ except Exception as e:
309
+ error_message = str(e)
310
+ extracted_sql = extract_sql_from_error(error_message)
311
+ fix_sql = extracted_sql.replace("```","")
312
+ fix_sql = re.sub(r"```sql", "", fix_sql)
313
+ fix_sql = re.sub(r'%%s', r'%s', fix_sql)
314
+ if contains_delete(fix_sql):
315
+ return "Lỗi: Bạn không dược phép thực hiện truy vấn DELETE trong hệ thống này."
316
+ if extracted_sql:
317
+
318
+ return process_and_execute_sql(fix_sql)
319
+ else:
320
+ return f"❌ Lỗi không thể thực thi truy vấn: {error_message}"
321
+
322
+
323
+ async def create_new_chat_history(user_id: int) -> str:
324
+ """
325
+ Tạo một đoạn chat mới cho user_id và trả về ObjectId của đoạn chat.
326
+ """
327
+ check = UserRepository.getUserByUserId(user_id)
328
+ if check is None:
329
+ raise HTTPException(status_code=404, detail="User not found or has been deleted in MySQL")
330
+
331
+ user = User.objects(user_id=user_id).first()
332
+ if not user:
333
+ user = User(id=ObjectId(), user_id=user_id, user_name=f"User_{user_id}")
334
+ user.save()
335
+ random_name_chat = str(random.randint(10**14, 10**15 - 1))
336
+ name_chat = f"Chat_{random_name_chat}"
337
+ new_chat = ChatHistory(id=ObjectId(), user=user, name_chat=name_chat)
338
+ new_chat.save()
339
+
340
+ return res_chat.CreateNewChat(idMongo=str(new_chat.id), chat_name=name_chat)
341
+
342
+
343
+ async def update_chat_name(chat_id: str, new_name: str,user_id:int) -> str:
344
+ """
345
+ Cập nhật name_chat của một ChatHistory dựa trên chat_id.
346
+ """
347
+ check = UserRepository.getUserByUserId(user_id)
348
+ if check is None:
349
+ raise HTTPException(status_code=404, detail="User not found or has been deleted in MySQL")
350
+
351
+ user = User.objects(user_id=user_id, is_deleted=False).first()
352
+ if not user:
353
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
354
+
355
+ chat = ChatHistory.objects(_id=ObjectId(chat_id)).first()
356
+
357
+ if not chat:
358
+ raise HTTPException(status_code=404, detail="Chat not found in MongoDB")
359
+
360
+ chat.name_chat = new_name
361
+ chat.save()
362
+
363
+ return f"Updated chat name to {new_name}"
364
+
365
+
366
+ async def soft_delete_chat(chat_id: str,user_id:int):
367
+ """
368
+ Cập nhật `is_deleted=True` và `date_deleted` cho `ChatHistory` và các `DetailChat` liên quan.
369
+ """
370
+ check = UserRepository.getUserByUserId(user_id)
371
+ if check is None:
372
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
373
+
374
+ check_history_id = UserRepository.getChatHistory(user_id,chat_id)
375
+ if check_history_id is None:
376
+ raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MySQL")
377
+
378
+ user = User.objects(user_id=user_id, is_deleted=False).first()
379
+ if not user:
380
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
381
+
382
+ chat = ChatHistory.objects(_id=ObjectId(chat_id)).first()
383
+
384
+ if not chat:
385
+ raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MongoDB")
386
+ chat.is_deleted = True
387
+ chat.date_deleted = datetime.now(pytz.UTC)
388
+ chat.save()
389
+
390
+ DetailChat.objects(chat_history=chat).update(
391
+ set__is_deleted=True,
392
+ set__date_deleted=datetime.now(pytz.UTC)
393
+ )
394
+
395
+ return {"message": "Chat and related details marked as deleted"}
396
+ from typing import Optional
397
+ from models.Database_Entity import StopSignal
398
+ from datetime import datetime, timedelta
399
+ import asyncio
400
+ async def chat_with_user(
401
+ user_input: str,
402
+ user_id: int,
403
+ languages: str,
404
+ role: str,
405
+ token: str,
406
+ chat_history_id: str = None,
407
+ stop_event: Optional[asyncio.Event] = None
408
+
409
+ ) -> str:
410
+ """
411
+ Xử tin nhắn của người dùng, lưu vào lịch sử chat và trả về phản hồi từ AI.
412
+ """
413
+
414
+ if role not in ["ADMIN", "CUSTOMER", "SHIPPER"]:
415
+ raise HTTPException(status_code=400, detail="ROLE not valid")
416
+ user_id = int(user_id)
417
+ if languages not in ["VN", "EN"]:
418
+ raise HTTPException(status_code=400, detail="Language not valid")
419
+
420
+ if not user_input:
421
+ raise HTTPException(status_code=400, detail="User input empty")
422
+ if not isinstance(user_id, int) or user_id <= 0:
423
+ raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer")
424
+
425
+ languages = "Vietnamese" if languages == "VN" else "English"
426
+
427
+ check = UserRepository.getUserByUserId(user_id)
428
+ if check is None:
429
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
430
+
431
+ check_history_id = UserRepository.getChatHistory(user_id,chat_history_id)
432
+ if check_history_id is None:
433
+ raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL")
434
+
435
+ user = User.objects(user_id=user_id).first()
436
+ if not user:
437
+ return {"error": "User not found or has been deleted in MongoDB"}
438
+
439
+ chat_history = None
440
+ if chat_history_id:
441
+ try:
442
+ chat_history_obj_id = ObjectId(chat_history_id) # Chuyển đổi sang ObjectId
443
+ chat_history = ChatHistory.objects(_id=chat_history_obj_id, user=user).first()
444
+ except Exception as e:
445
+
446
+ print(f"⚠️ Invalid chat_history_id: {e}")
447
+ if not chat_history:
448
+ raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB")
449
+ await asyncio.sleep(0.1)
450
+ if stop_event and stop_event.is_set():
451
+ raise asyncio.CancelledError(" Task was cancelled before processing")
452
+ await asyncio.sleep(0.1)
453
+ if StopSignal.objects(chat_history=chat_history_id, is_stopped=True).first():
454
+ print("🛑 Dừng StopSignal trong DB.")
455
+ raise asyncio.CancelledError("⛔ Task was cancelled before processing")
456
+ result_final = await pipeline_agent.multi_query_user(
457
+ user_input, user_id, role, languages, chat_history_id, token,stop_event
458
+ )
459
+ detail_chat = DetailChat(
460
+ id=ObjectId(),
461
+ chat_history=chat_history,
462
+ you_message=user_input,
463
+ ai_message=result_final,
464
+ timestamp=datetime.now(pytz.UTC)
465
+ )
466
+ detail_chat.save()
467
+ chat_history_messages = DetailChat.objects(chat_history=chat_history).order_by('timestamp')
468
+
469
+
470
+ def convert_to_vn_time(timestamp):
471
+ return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S')
472
+
473
+ sorted_messages = sorted(chat_history_messages, key=lambda msg: msg.timestamp, reverse=True)
474
+
475
+ formatted_messages = [
476
+ {
477
+ "index": i + 1, # Đánh số từ 1
478
+ "id": str(msg.id),
479
+ "you_message": msg.you_message,
480
+ "ai_message": msg.ai_message,
481
+ "timestamp": convert_to_vn_time(msg.timestamp)
482
+ }
483
+ for i, msg in enumerate(sorted_messages)
484
+ ]
485
+
486
+ return jsonable_encoder({
487
+ "new_message": {
488
+ "id": str(detail_chat.id),
489
+ "you_message": detail_chat.you_message,
490
+ "ai_message": detail_chat.ai_message,
491
+ "timestamp": convert_to_vn_time(detail_chat.timestamp)
492
+ },
493
+ "previous_messages": formatted_messages
494
+ })
495
+
496
+ from bson import ObjectId
497
+
498
+
499
+ async def get_chat_details(chat_id: str,user_id:int):
500
+ """
501
+ Lấy tất cả `DetailChat` thuộc `ChatHistory` có `chat_id`, chỉ lấy bản ghi `is_deleted=False`.
502
+ """
503
+
504
+ check = UserRepository.getUserByUserId(user_id)
505
+ if check is None:
506
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
507
+
508
+ check_history_id = UserRepository.getChatHistory(user_id,chat_id)
509
+ if check_history_id is None:
510
+ raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MySQL")
511
+
512
+ user = User.objects(user_id=user_id, is_deleted=False).first()
513
+ if not user:
514
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
515
+
516
+ chat = ChatHistory.objects(_id=ObjectId(chat_id), is_deleted=False).first()
517
+
518
+ if not chat:
519
+ raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB")
520
+
521
+ chat_details = DetailChat.objects(chat_history=chat, is_deleted=False)
522
+ def convert_to_vn_time(timestamp):
523
+ return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S')
524
+
525
+ list_detail_response = [
526
+ res_chat.DetailResponse(
527
+ id=str(index + 1), # ✅ Đánh số lại từ 1
528
+ you_message=detail.you_message,
529
+ ai_message=detail.ai_message,
530
+ timestamp=convert_to_vn_time(detail.timestamp) # ✅ Chuyển sang GMT+7
531
+ )
532
+ for index, detail in enumerate(sorted(chat_details, key=lambda d: d.timestamp, reverse=True)) # ✅ Sắp xếp giảm dần
533
+ ]
534
+
535
+ return res_chat.ListDetailResponse(
536
+ chat_id=str(chat.id),
537
+ chat_name=chat.name_chat,
538
+ list_detail_response=list_detail_response
539
+ )
540
+
541
+
542
+
543
+ async def regenerate(
544
+ user_question_new: str,
545
+ user_id: int,
546
+ languages: str,
547
+ role: str,
548
+ token: str,
549
+ chat_history_id: str = None,
550
+ stop_event: Optional[asyncio.Event] = None
551
+ ) -> str:
552
+ """
553
+ Xử tin nhắn của người dùng, lưu vào lịch sử chat và trả về phản hồi từ AI.
554
+ """
555
+ PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_question_new)
556
+ if role not in ["ADMIN", "CUSTOMER", "SHIPPER"]:
557
+ raise HTTPException(status_code=400, detail="ROLE not valid")
558
+ user_id = int(user_id)
559
+ if languages not in ["VN", "EN"]:
560
+ raise HTTPException(status_code=400, detail="Language not valid")
561
+
562
+ if not user_question_new:
563
+ raise HTTPException(status_code=400, detail="User input empty")
564
+ if not isinstance(user_id, int) or user_id <= 0:
565
+ raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer")
566
+
567
+ languages = "Vietnamese" if languages == "VN" else "English"
568
+
569
+ check = UserRepository.getUserByUserId(user_id)
570
+ if check is None:
571
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
572
+
573
+ check_history_id = UserRepository.getChatHistory(user_id,chat_history_id)
574
+ if check_history_id is None:
575
+ raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL")
576
+
577
+ user = User.objects(user_id=user_id).first()
578
+ if not user:
579
+ return {"error": "User not found or has been deleted in MongoDB"}
580
+
581
+ chat_history = None
582
+ if chat_history_id:
583
+ try:
584
+ chat_history_obj_id = ObjectId(chat_history_id) # Chuyển đổi sang ObjectId
585
+ chat_history = ChatHistory.objects(_id=chat_history_obj_id, user=user).first()
586
+ except Exception as e:
587
+
588
+ print(f"⚠️ Invalid chat_history_id: {e}")
589
+ if not chat_history:
590
+ raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB")
591
+ # filtered_input = filter_sql_injection_1.filter_sql_injection(user_input)
592
+ # filtered_role_input = filter_role_1.filter_role(filtered_input)
593
+ # result = await execute_query_user(filtered_role_input, user_id, languages, role)
594
+ # result_final = query_result_1.query_result(user_input, result)
595
+ if chat_history:
596
+ last_chat = (
597
+ DetailChat.objects(chat_history=chat_history, is_deleted=False)
598
+ .order_by("-timestamp")
599
+ .first()
600
+ )
601
+
602
+ await asyncio.sleep(0.1)
603
+ if stop_event and stop_event.is_set():
604
+ raise asyncio.CancelledError(" Task was cancelled before processing")
605
+ await asyncio.sleep(0.1)
606
+ if StopSignal.objects(chat_history=chat_history_id, is_stopped=True).first():
607
+ print("🛑 Dừng StopSignal trong DB.")
608
+ raise asyncio.CancelledError("⛔ Task was cancelled before processing")
609
+ result_final = await pipeline_agent.multi_query_user(user_question_new,user_id,role,languages,chat_history_id,token,stop_event)
610
+ last_chat.update(set__you_message=user_question_new, set__ai_message=result_final, set__timestamp = datetime.now(pytz.UTC))
611
+
612
+ last_chat_result = (
613
+ DetailChat.objects(chat_history=chat_history, is_deleted=False)
614
+ .order_by("-timestamp")
615
+ .first()
616
+ )
617
+
618
+ chat_history_messages = DetailChat.objects(chat_history=chat_history).order_by('timestamp')
619
+
620
+
621
+ def convert_to_vn_time(timestamp):
622
+ return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S')
623
+
624
+ sorted_messages = sorted(chat_history_messages, key=lambda msg: msg.timestamp, reverse=True)
625
+
626
+ formatted_messages = [
627
+ {
628
+ "index": i + 1, # Đánh số từ 1
629
+ "id": str(msg.id),
630
+ "you_message": msg.you_message,
631
+ "ai_message": msg.ai_message,
632
+ "timestamp": convert_to_vn_time(msg.timestamp)
633
+ }
634
+ for i, msg in enumerate(sorted_messages)
635
+ ]
636
+
637
+ return jsonable_encoder({
638
+ "new_message": {
639
+ "id": str(last_chat_result.id),
640
+ "you_message": last_chat_result.you_message,
641
+ "ai_message": last_chat_result.ai_message,
642
+ "timestamp": convert_to_vn_time(last_chat_result.timestamp)
643
+ },
644
+ "previous_messages": formatted_messages
645
+ })
646
+
647
+ from bson import ObjectId
648
+
649
+
650
+ async def get_user_chat_history(user_id: int):
651
+ """
652
+ API lấy danh sách tất cả các đoạn chat của một user_id.
653
+ """
654
+ check = UserRepository.getUserByUserId(user_id)
655
+ if check is None:
656
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL")
657
+ user = User.objects(user_id=user_id, is_deleted=False).first()
658
+ if not user:
659
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
660
+ chat_histories = ChatHistory.objects(user=user, is_deleted=False)
661
+ chat_list = [
662
+ res_chat.ChatResponse(
663
+ chat_id=str(chat.id),
664
+ chat_name=chat.name_chat,
665
+ timestamp=chat.date_deleted if chat.is_deleted else chat.id.generation_time
666
+ )
667
+ for chat in chat_histories
668
+ ]
669
+
670
+ return res_chat.UserChatHistoryResponse(
671
+ user_id=user.user_id,
672
+ user_name=user.user_name,
673
+ chat_list=chat_list
674
+ )
675
+
676
+
677
+ from bson import ObjectId
678
+
679
+ async def get_chat_details_text(chat_id: str, user_id: int):
680
+ """
681
+ Trích xuất tất cả các chi tiết chat của một chat_id, gom thành một đoạn văn bản.
682
+ """
683
+ # Kiểm tra xem user tồn tại không
684
+ user = User.objects(user_id=user_id, is_deleted=False).first()
685
+ if not user:
686
+ raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB")
687
+
688
+ # Kiểm tra xem chat history tồn tại không
689
+ chat = ChatHistory.objects(_id=ObjectId(chat_id), user=user, is_deleted=False).first()
690
+ if not chat:
691
+ raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MongoDB")
692
+ chat_details = DetailChat.objects(chat_history=chat, is_deleted=False).order_by('timestamp')
693
+ if not chat_details:
694
+ return list()
695
+
696
+ # Gom tất cả các câu hỏi và câu trả lời vào danh sách
697
+ chat_text_list = []
698
+ for index, detail in enumerate(chat_details,start=1):
699
+ chat_text_list.append({
700
+ "order":str(index),
701
+ "timestamp": detail.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
702
+ "you_message": detail.you_message,
703
+ "ai_message": detail.ai_message
704
+ })
705
+
706
+ return chat_text_list
707
+
708
+
709
+ import asyncio
710
+ import os
711
+ import subprocess
712
+ from datetime import datetime
713
+ from pathlib import Path
714
+ from function.analyze import main
715
+ from function.analyze.gemini import result_analyze
716
+ async def generate_and_save_code(question: str, user_id: int, role, languages: str, filename: str = "analyze_result.py"):
717
+ code_test, folder_name = await main.analyze(
718
+ question,
719
+ user_id,
720
+ languages,
721
+ role
722
+ )
723
+
724
+ code_clean = code_test.strip()
725
+ if code_clean.startswith("```python"):
726
+ code_clean = code_clean[9:].strip()
727
+ if code_clean.endswith("```"):
728
+ code_clean = code_clean[:-3].strip()
729
+
730
+ # code_clean = code_clean.replace("else:", "").strip()
731
+ code_clean = code_clean.replace("os_path:", "os.path").strip()
732
+ encoding_fix = 'import sys\nsys.stdout.reconfigure(encoding="utf-8")\n\n'
733
+ encoding_fix1= 'import numpy as np\n\n'
734
+ code_clean = encoding_fix + encoding_fix1 + code_clean
735
+
736
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
737
+ output_dir = Path(f"./Temp/{folder_name}_{timestamp}")
738
+ output_dir.mkdir(parents=True, exist_ok=True)
739
+
740
+
741
+ file_path = output_dir / filename
742
+ with open(file_path, "w", encoding="utf-8") as f:
743
+ f.write(code_clean)
744
+
745
+
746
+ env = os.environ.copy()
747
+ env["OUTPUT_DIR"] = str(output_dir)
748
+ result = subprocess.run(
749
+ ["python", filename],
750
+ capture_output=True,
751
+ text=True,
752
+ env=env,
753
+ cwd=output_dir,
754
+ encoding="utf-8",
755
+ errors="replace"
756
+ )
757
+
758
+ output_folder = str(output_dir)
759
+ absolute_path = os.path.abspath(output_folder)
760
+ final_path = os.path.join(absolute_path, "test5")
761
+ result_final = result_analyze.generate(image_folder=final_path,question=question)
762
  return result_final,final_path