diff --git a/.env b/.env new file mode 100644 index 0000000000000000000000000000000000000000..ca671adc48dab64f2717ec7134c9056665cd72ae --- /dev/null +++ b/.env @@ -0,0 +1,12 @@ +DB_HOST=mysql-22f29707-student-4328.h.aivencloud.com +DB_USER=avnadmin +DB_PASSWORD=AVNS_D-m-KFb1aPKlV4JBIw7 +DB_NAME=demohmdrinks +DB_PORT=18340 +MONGO_URI = mongodb+srv://muyxue:389126@cluster0.190kw.mongodb.net/hmdrinks?retryWrites=true&w=majority&appName=Cluster0 +GROQ_API_KEY=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 +COHERE_API_KEY=v34SUVXvG16NlmEZQt1mtwSeUbbdbxCy5KCFue11 +GOOGLE_API_KEY_1=AIzaSyCO-RlqYewC4e9BEPoC8m-AxHUY7J3_o2E +COHERE_API_KEY_2= v34SUVXvG16NlmEZQt1mtwSeUbbdbxCy5KCFue11 +COHERE_API_KEY_3= v34SUVXvG16NlmEZQt1mtwSeUbbdbxCy5KCFue11 +GOOGLE_API_KEY=AIzaSyCO-RlqYewC4e9BEPoC8m-AxHUY7J3_o2E \ No newline at end of file diff --git a/api_key.txt b/api_key.txt new file mode 100644 index 0000000000000000000000000000000000000000..d6b810540c6fdbf8b93dae1349d85859c11fb9c8 --- /dev/null +++ b/api_key.txt @@ -0,0 +1,2 @@ +AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac +AIzaSyAW23eFBB3u2bUfM0sxk-WS_nS2-sV4cUA \ No newline at end of file diff --git a/ca.pem b/ca.pem new file mode 100644 index 0000000000000000000000000000000000000000..cfa43e13dfd4ddf565b0d013caffa3f2a833ce8c --- /dev/null +++ b/ca.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEUDCCArigAwIBAgIUfxHeji94PVdRnLYTj22XRvvdXXwwDQYJKoZIhvcNAQEM +BQAwQDE+MDwGA1UEAww1NDY4MDI2MDYtZGM1OC00NDM4LTg0ODctNzQxNWE5MjVl +YjJmIEdFTiAxIFByb2plY3QgQ0EwHhcNMjUwNjE5MDkyNzM2WhcNMzUwNjE3MDky +NzM2WjBAMT4wPAYDVQQDDDU0NjgwMjYwNi1kYzU4LTQ0MzgtODQ4Ny03NDE1YTky +NWViMmYgR0VOIDEgUHJvamVjdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCC +AYoCggGBAMqawd0fx+VhLXb1sOcvAOJAjnybF4phtphIZywDc+wrSiJKmKpFdIEV +9d4hyzHqby7uyFvXp4qe710D/1vYzTfAtWyYra3Fq5FAR+rZoQfKeBJneLvWr4OM +O4nhntrXLWoz5z5dvrwwMQdFXK5zWL776gsWo9Zfved1/VrBma0PMnLWGCxBhe8D +CUvFwj9WDLcBu1Wpemj5hNUYV7HrIKYKnRXT6LsW8LAcwdVvwkFMeTYXoAvxZime +jgdm0l9aBHmkxVQB4yV2jzSjYl+RfBv9Fr0sFsTjYRhWTnqgotwQ/kWtV2lPvTC8 +9RrR+Zv4Npr5GFva856J8F7vJYE4u8iXtcW88/8R2BD6X7Is0bNSXN4ddb4e1uG1 +xibOwNdFAbaiRfQpdg5uA87LCbb4JsU0WRe705qJHOPynVMQsQHDqioexVkJwNot +FhRG7j9rQLMihC5IRbxYql0FLamm0xb32Z4bSTqmwQacQF+uB27i6rxB3w8wV2C0 +tXmBa9siVwIDAQABo0IwQDAdBgNVHQ4EFgQUHgH+oY6nEHz0RQ7Kov6/1L1/scYw +EgYDVR0TAQH/BAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQEMBQAD +ggGBAJSFK9FoV5d/PxtrP2z+BViDjgXeoJ6tLVykLjpVEr0OCIzwgi3F8O4a1U7P +2BQu5URiiiC28aoIvDXcmowQFJBw6DaVIadGACifNORCWals+gT+4l2kdnRFW8fy +gP+cHjj9IBLtmcgttMTTRArTYgYO9GfuK1PMD8srG/syue5V/bQ6+vmO6gAtuTs1 +cnQXzzMyZKNLb90MERfojegLXCyoI2O5nC604fhDSgnVUKzw7g6vRPHAWPQGCZn1 +DwpI7BydRRs57F/Tigz8EgbzC2cuTQaaWr3oOiBTsGl5Nkc2iPxKXzoPHMUT3MD3 +vRosEkl+LN5hupYbaHagHT50n9j+AG5KXiRoN4S0IxJZWSGMpYMYEQ4Qkd3AvRnW +ECdT5ZXXm19a4teTZIk1fZ9dITnn/vobfW0MhpG4kkHIOBHVy4VJrLqFrQfLOVQM +qVgKdc0K6FfDnfNdvkrfzJoLoR0X6AtVS60pfpolBXNQEPNWcdKDL0DSBbZ+rnid +9nYErA== +-----END CERTIFICATE----- diff --git a/controller/CallbackController.py b/controller/CallbackController.py new file mode 100644 index 0000000000000000000000000000000000000000..0b351281449dbfbac7a5f44f88cec62b0fc0ac30 --- /dev/null +++ b/controller/CallbackController.py @@ -0,0 +1,212 @@ +from fastapi import APIRouter, Request,HTTPException +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from fastapi.requests import Request +import yaml +from typing import Dict +from fastapi.responses import RedirectResponse +from service import CallBackService +import json +from fastapi.responses import JSONResponse +from models.Database_Entity import PaymentCallbackLog +with open("config.yaml", "r") as f: + config = yaml.safe_load(f) +base_backend_java = config["callback_urls"] +base_frontend = config["frontend"] + +router = APIRouter() + +class JWTBearer(HTTPBearer): + def __init__(self, auto_error: bool = True): + super(JWTBearer, self).__init__(auto_error=auto_error) + + async def __call__(self, request: Request): + credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request) + if credentials: + if credentials.scheme != "Bearer": + raise HTTPException(status_code=401, detail="Invalid authentication scheme.") + return credentials.credentials + else: + raise HTTPException(status_code=401, detail="Invalid authorization code.") + +jwt_bearer = JWTBearer() + + + +@router.post("/android/zalo") +async def zalo_callback_post(request: Request): + try: + json_data = await request.json() + data_str =json_data.get("data") + + if data_str: + data_dict = json.loads(data_str) + app_trans_id = data_dict.get("app_trans_id") + PaymentCallbackLog( + type="zalopay", + app_trans_id=data_dict.get("app_trans_id"), + raw_data=data_dict, + status="success" if data_dict.get("status") == "1" else "fail", + is_refund = False +).save() + + print(app_trans_id) + data = await CallBackService.zalo_callback(str(app_trans_id)) + return data + except Exception as e: + print("Lỗi khi gọi callback web zalo:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.post("/android/group/zalo") +async def zalo_callback_post(request: Request): + try: + json_data = await request.json() + data_str =json_data.get("data") + if data_str: + data_dict = json.loads(data_str) + app_trans_id = data_dict.get("app_trans_id") + PaymentCallbackLog( + type="zalopay", + app_trans_id=data_dict.get("app_trans_id"), + raw_data=data_dict, + status="success" if data_dict.get("status") == "1" else "fail", + is_refund = False +).save() + print(app_trans_id) + data = await CallBackService.zalo_group_callback(str(app_trans_id)) + return data + except Exception as e: + print("Lỗi khi gọi callback group ảndroid zalo:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.get("/android/payOs") +async def return_url_handler(code: str = None, id: str = None, cancel: bool = None, status: str = None, orderCode: str = None): + try: + return await CallBackService.android_payos_callback(status,orderCode) + except Exception as e: + print("Lỗi khi gọi callback android payos:", e) + # Trả về lỗi 500 - Internal Server Error + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.get("/android/group/payOs") +async def return_url_handler1(code: str = None, id: str = None, cancel: bool = None, status: str = None, orderCode: str = None): + try: + return await CallBackService.android_group_payOs_callback(status,orderCode) + except Exception as e: + print("Lỗi khi gọi callback group payos:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.get("/web/payOs") +async def return_url_handler(code: str = None, id: str = None, cancel: bool = None, status: str = None, orderCode: str = None): + try: + return await CallBackService.web_payos_callback(code, id, cancel, status, orderCode) + except Exception as e: + print("Lỗi khi gọi callback web payos:", e) + # Trả về lỗi 500 - Internal Server Error + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.post("/android/momo") +async def return_url_handler(request: Request): + json_data = await request.json() + print(json_data) + + data_str =json_data.get("data") + order_id_original = json_data.get("orderId") + order_id = "" + if order_id_original.startswith("MOMO-"): + order_id = order_id_original[len("MOMO-"):] + result_code = json_data.get("resultCode") + PaymentCallbackLog( + type="momo", + order_id=order_id, + raw_data=json_data, + status="success" if result_code == 0 else "fail", + is_refund = False +).save() + print(data_str) + + try: + result = await CallBackService.momo_callback(order_id_original,result_code) + print(result) + except Exception as e: + print("Lỗi khi gọi call back android momo:", e) + + if 0 != int(result_code): + return RedirectResponse(url=f"{base_frontend['android_redirect_base']}?status=-49") + else : + return RedirectResponse(url=f"{base_frontend['android_redirect_base']}?status=1") + + +@router.post("/android/group/momo") +async def return_url_handler(request: Request): + json_data = await request.json() + print(json_data) + + data_str =json_data.get("data") + order_id = json_data.get("orderId") + if order_id.startswith("MOMO-"): + order_id = order_id[len("MOMO-"):] + + result_code = json_data.get("resultCode") + print(data_str) + PaymentCallbackLog( + type="momo", + order_id=order_id, + raw_data=json_data, + status="success" if result_code == 0 else "fail", + is_refund = False +).save() + + try: + result = await CallBackService.momo_group_callback(order_id,result_code) + print(result) + except Exception as e: + print("Lỗi khi gọi callback android group momo:", e) + if 0 != int(result_code): + return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=-49""") + else : + return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=1""") + + +@router.get("/android/vnpay_ipn") +async def vnpay_ipn(request: Request): + params: Dict[str, str] = dict(request.query_params) + # Lưu callback vnpay + PaymentCallbackLog( + type="vnpay", + txn_ref=params.get("vnp_TxnRef"), + raw_data=params, + status="success" if params.get("vnp_ResponseCode") == "00" else "fail", + is_refund = False +).save() + + try: + return await CallBackService.vnpay_callbackk(params) + except Exception as e: + print("Lỗi khi gọi callback vnpay:", e) + # Trả về lỗi 500 - Internal Server Error + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.get("/android/group/vnpay_ipn") +async def vnpay_ipn(request: Request): + params: Dict[str, str] = dict(request.query_params) + # Lưu callback vnpay + PaymentCallbackLog( + type="vnpay", + txn_ref=params.get("vnp_TxnRef"), + raw_data=params, + status="success" if params.get("vnp_ResponseCode") == "00" else "fail", + is_refund = False +).save() + + try: + return await CallBackService.vnpay_group_callback(params) + except Exception as e: + print("Lỗi khi gọi callback group vnpay ipn:", e) + # Trả về lỗi 500 - Internal Server Error + return JSONResponse(content={"error": "Internal server error"}, status_code=500) \ No newline at end of file diff --git a/controller/ChatController.py b/controller/ChatController.py new file mode 100644 index 0000000000000000000000000000000000000000..754de05ebc417d9de6789746aaf4f95ba083a77e --- /dev/null +++ b/controller/ChatController.py @@ -0,0 +1,224 @@ +from fastapi import APIRouter, Form, Request,Depends,HTTPException,BackgroundTasks +from service import ChatService +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from request import RequestChat +from fastapi.requests import Request +from fastapi.responses import JSONResponse +import asyncio +router = APIRouter() +import asyncio +# Lưu task đang chạy theo chat_history_id +task_registry: dict[str, asyncio.Task] = {} +# Lưu stop_event để dừng từng task +stop_events: dict[str, asyncio.Event] = {} +import decode_token +from models.Database_Entity import StopSignal +from models.Database_Entity import ChatHistory, User +from repository.MySQL import UserRepository + +class JWTBearer(HTTPBearer): + def __init__(self, auto_error: bool = True): + super(JWTBearer, self).__init__(auto_error=auto_error) + + async def __call__(self, request: Request): + credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request) + if credentials: + if credentials.scheme != "Bearer": + raise HTTPException(status_code=401, detail="Invalid authentication scheme.") + return credentials.credentials + else: + raise HTTPException(status_code=401, detail="Invalid authorization code.") + +jwt_bearer = JWTBearer() + + +@router.get("/chat/user_history") +async def get_user_chat_history(token: str = Depends(jwt_bearer)): + try: + user_id_token = decode_token.JwtService.extract_user_id(token) + return await ChatService.get_user_chat_history(user_id_token) + except Exception as e: + print("Lỗi khi gọi user history chat:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.post("/new_chat/create/") +async def create_chat(token: str = Depends(jwt_bearer)): + try: + user_id_token = decode_token.JwtService.extract_user_id(token) + new_chat = await ChatService.create_new_chat_history(user_id_token) + return new_chat + except Exception as e: + print("Lỗi khi gọi create new chat:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +from bson import ObjectId +@router.post("/question") +async def question( + request: RequestChat.ChatWithServer, + background_tasks: BackgroundTasks, + token: str = Depends(jwt_bearer) +): + try: + user_id_token = decode_token.JwtService.extract_user_id(token) + user_role = decode_token.JwtService.extract_user_role(token) + chat_id = request.chat_history_id + + stop_event = asyncio.Event() + stop_events[chat_id] = stop_event + print(f"[CREATE] stop_event id: {id(stop_event)} for chat_id: {chat_id}") + chat_history = ChatHistory.objects(pk=ObjectId(chat_id)).first() + + if chat_history: + signal = StopSignal.objects(chat_history=chat_history).first() + if not signal: + signal = StopSignal(chat_history=chat_history) + signal.is_stopped = False + signal.stopped_at = None + signal.save() + + async def run_chat(): + try: + result = await ChatService.chat_with_user( + request.user_input, + user_id_token, + request.language, + user_role, + token, + chat_id, + stop_event + ) + return result + except asyncio.CancelledError: + print(f"🛑 Task {chat_id} was cancelled by asyncio.") + return {"status": "cancelled"} + except Exception as e: + print(f"❌ Lỗi trong task {chat_id}:", e) + return {"error": str(e)} + finally: + # Dọn dẹp + stop_events.pop(chat_id, None) + task_registry.pop(chat_id, None) + + task = asyncio.create_task(run_chat()) + task_registry[chat_id] = task + return await task + + except Exception as e: + print("Lỗi khi chạy:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +from datetime import datetime +@router.post("/stop-task/{chat_history_id}") +async def stop_task(chat_history_id: str, token: str = Depends(jwt_bearer)): + user_id = int(decode_token.JwtService.extract_user_id(token)) + if not isinstance(user_id, int) or user_id <= 0: + raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer") + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + + check_history_id = UserRepository.getChatHistory(user_id,chat_history_id) + if check_history_id is None: + raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id).first() + if not user: + return {"error": "User not found or has been deleted in MongoDB"} + event = stop_events.get(chat_history_id) + task = task_registry.get(chat_history_id) + print(f"🚨 Đã vào stop-task với chat_history_id = {chat_history_id}") + print(f"🔎 stop_event: {event}, task: {task}") + # Set event trong RAM + if event: + print(f"🛑 Setting stop_event for {chat_history_id}") + event.set() + # Cancel task + if task: + print(f"🔪 Cancelling task for {chat_history_id}") + task.cancel() + + # Cập nhật trạng thái stop vào MongoDB + from bson import ObjectId + + chat_history = ChatHistory.objects(pk=ObjectId(chat_history_id)).first() + + if chat_history: + signal = StopSignal.objects(chat_history=chat_history).first() + if not signal: + signal = StopSignal(chat_history=chat_history) + signal.is_stopped = True + signal.stopped_at = datetime.utcnow() + signal.save() + return {"message": f"Stop signal sent for chat_history_id {chat_history_id}"} + + +@router.put("/regenerate") +async def regenerate(request: RequestChat.Regenerate, token: str = Depends(jwt_bearer)): + try: + user_id_token = decode_token.JwtService.extract_user_id(token) + user_role = decode_token.JwtService.extract_user_role(token) + chat_id = request.chat_id + stop_event = asyncio.Event() + stop_events[chat_id] = stop_event + print(f"[CREATE] stop_event id: {id(stop_event)} for chat_id: {chat_id}") + chat_history = ChatHistory.objects(pk=ObjectId(chat_id)).first() + + if chat_history: + signal = StopSignal.objects(chat_history=chat_history).first() + if not signal: + signal = StopSignal(chat_history=chat_history) + signal.is_stopped = False + signal.stopped_at = None + signal.save() + + async def run_chat(): + try: + new_chat = await ChatService.regenerate(request.question_new ,user_id_token,request.languages ,user_role,token,request.chat_id,stop_event) + return new_chat + except asyncio.CancelledError: + print(f"🛑 Task {chat_id} was cancelled by asyncio.") + return {"status": "cancelled"} + except Exception as e: + print(f"❌ Lỗi trong task {chat_id}:", e) + return {"error": str(e)} + task = asyncio.create_task(run_chat()) + task_registry[chat_id] = task + return await task + except Exception as e: + print("Lỗi khi gọi regenerate:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.put("/update") +async def update_chat_name(request: RequestChat.UpdateNameChat, token: str = Depends(jwt_bearer)): + try: + user_id_token = decode_token.JwtService.extract_user_id(token) + updated_chat = await ChatService.update_chat_name(request.chat_id, request.name_chat,user_id_token) + return updated_chat + except Exception as e: + print("Lỗi khi gọi update:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.delete("/delete") +async def delete_chat(request: RequestChat.DeleteChatRequest, token: str = Depends(jwt_bearer)): + try: + user_id_token = decode_token.JwtService.extract_user_id(token) + deleted_chat = await ChatService.soft_delete_chat(request.chat_id,user_id_token) + return deleted_chat + except Exception as e: + print("Lỗi khi gọi deleted:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) + + +@router.get("/list_detail_chat/{chat_id}") +async def get_chat_details(chat_id: str, token: str = Depends(jwt_bearer)): + try: + user_id_token = decode_token.JwtService.extract_user_id(token) + return await ChatService.get_chat_details(chat_id,user_id_token) + except Exception as e: + print("Lỗi khi gọi list_detail_chat:", e) + return JSONResponse(content={"error": "Internal server error"}, status_code=500) \ No newline at end of file diff --git a/controller/GroupOrderController.py b/controller/GroupOrderController.py new file mode 100644 index 0000000000000000000000000000000000000000..098af29e906b907690aa2070106fc32c0a448e5b --- /dev/null +++ b/controller/GroupOrderController.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, Form, Request,Depends,HTTPException +from service import ChatService +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from request import RequestChat +from typing import Optional +from fastapi.requests import Request +from fastapi.responses import JSONResponse + +router = APIRouter() + +class JWTBearer(HTTPBearer): + def __init__(self, auto_error: bool = True): + super(JWTBearer, self).__init__(auto_error=auto_error) + + async def __call__(self, request: Request): + credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request) + if credentials: + if credentials.scheme != "Bearer": + raise HTTPException(status_code=401, detail="Invalid authentication scheme.") + return credentials.credentials + else: + raise HTTPException(status_code=401, detail="Invalid authorization code.") + +jwt_bearer = JWTBearer() +import decode_token \ No newline at end of file diff --git a/controller/RecommendController.py b/controller/RecommendController.py new file mode 100644 index 0000000000000000000000000000000000000000..65d51d2c92f8d209ffb4639ac21838ce0e2c8131 --- /dev/null +++ b/controller/RecommendController.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, Form, Request,Depends,HTTPException +from service import ChatService +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from request import RequestChat +from typing import Optional +from fastapi.requests import Request + + +router = APIRouter() + +class JWTBearer(HTTPBearer): + def __init__(self, auto_error: bool = True): + super(JWTBearer, self).__init__(auto_error=auto_error) + + async def __call__(self, request: Request): + credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request) + if credentials: + if credentials.scheme != "Bearer": + raise HTTPException(status_code=401, detail="Invalid authentication scheme.") + return credentials.credentials + else: + raise HTTPException(status_code=401, detail="Invalid authorization code.") + +jwt_bearer = JWTBearer() +import decode_token \ No newline at end of file diff --git a/controller/RedirectController.py b/controller/RedirectController.py new file mode 100644 index 0000000000000000000000000000000000000000..fdfb0bd4500f0d90b88c1b89679e931b783989d6 --- /dev/null +++ b/controller/RedirectController.py @@ -0,0 +1,96 @@ +from fastapi import APIRouter, Form, Request,Depends,HTTPException +from service import ChatService +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from request import RequestChat +from typing import Optional +from fastapi.requests import Request + + +router = APIRouter() + +class JWTBearer(HTTPBearer): + def __init__(self, auto_error: bool = True): + super(JWTBearer, self).__init__(auto_error=auto_error) + + async def __call__(self, request: Request): + credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request) + if credentials: + if credentials.scheme != "Bearer": + raise HTTPException(status_code=401, detail="Invalid authentication scheme.") + return credentials.credentials + else: + raise HTTPException(status_code=401, detail="Invalid authorization code.") + +jwt_bearer = JWTBearer() +import decode_token +from service import RedirectService +from typing import Optional +from fastapi import Request, Query +from fastapi.responses import JSONResponse +@router.get("/android/zalo") +async def zalo_callback_get( + amount: Optional[int] = Query(None), + appid: Optional[str] = Query(None), + apptransid: Optional[str] = Query(None), + bankcode: Optional[str] = Query(None), + checksum: Optional[str] = Query(None), + discountamount: Optional[int] = Query(None), + pmcid: Optional[int] = Query(None), + status: Optional[int] = Query(None) +): + return await RedirectService.zalo_redirect(status,apptransid) + + +@router.get("/android/group/zalo") +async def zalo_callback_get( + amount: Optional[int] = Query(None), + appid: Optional[str] = Query(None), + apptransid: Optional[str] = Query(None), + bankcode: Optional[str] = Query(None), + checksum: Optional[str] = Query(None), + discountamount: Optional[int] = Query(None), + pmcid: Optional[int] = Query(None), + status: Optional[int] = Query(None) +): + return await RedirectService.zalo_group_redirect(status,apptransid) + +@router.get("/web/zalo") +async def zalo_web_callback_get(request: Request): + return await RedirectService.zalo_web_redirect(request.query_params) + + +@router.get("/android/momo") +async def return_url_handler_1(request: Request): + query_params = request.query_params + return await RedirectService.android_momo_redirect(query_params) + +@router.get("/android/group/momo") +async def return_url_handler_group_momo(request: Request): + query_params = request.query_params + return await RedirectService.android_group_momo_redirect(query_params) + + +from urllib.parse import urlencode +@router.get("/web/momo") +async def return_url_handler_web(request: Request): + query_params = request.query_params + return await RedirectService.web_momo_redirect(query_params) + + + +from typing import Dict +@router.get("/android/vnpay") +async def vnpay_ipn_redirect(request: Request): + params: Dict[str, str] = dict(request.query_params) + return await RedirectService.android_vnpay_ipn_redirect(params) + + +@router.get("/android/group/vnpay") +async def vnpay_ipn_redirect(request: Request): + params: Dict[str, str] = dict(request.query_params) + return await RedirectService.android_vnpay_group_ipn_redirect(params) + +@router.get("/web/vnpay") +async def vnpay_ipn_redirect(request: Request): + query_params = request.query_params + return await RedirectService.web_vnpay_ipn_redirect(query_params) \ No newline at end of file diff --git a/controller/RefundController.py b/controller/RefundController.py new file mode 100644 index 0000000000000000000000000000000000000000..e3e69ab6546c56a02a7d9d0b117c72c24a9cb236 --- /dev/null +++ b/controller/RefundController.py @@ -0,0 +1,150 @@ +from fastapi import APIRouter, Request, Depends, HTTPException +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from fastapi.requests import Request +from fastapi.responses import JSONResponse +import asyncio +router = APIRouter() +import asyncio +class JWTBearer(HTTPBearer): + def __init__(self, auto_error: bool = True): + super(JWTBearer, self).__init__(auto_error=auto_error) + + async def __call__(self, request: Request): + credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request) + if credentials: + if credentials.scheme != "Bearer": + raise HTTPException(status_code=401, detail="Invalid authentication scheme.") + return credentials.credentials + else: + raise HTTPException(status_code=401, detail="Invalid authorization code.") +import sys,os +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +jwt_bearer = JWTBearer() +import decode_token + +from models.Database_Entity import StopSignal +from models.Database_Entity import ChatHistory,PaymentCallbackLog +from service.RefundService import * +from models.Database_Entity import PaymentCallbackLog +from bson import ObjectId +from mongoengine import connect +from dotenv import load_dotenv +load_dotenv() +MONGO_URI = os.getenv("MONGO_URI", "") +connect("chatbot_hmdrinks", host=MONGO_URI) + + +async def refund_by_type_and_code(type: str, code: str): + if type.lower() == "zalopay": + log = PaymentCallbackLog.objects(type="zalopay", app_trans_id=code).first() + if not log: + return {"code": -3, "message": "Không tìm thấy giao dịch ZaloPay"} + data = call_zalopay_refund( + app_trans_id=code, + amount=log.raw_data.get("amount", 0), + description="Hoàn tiền ZaloPay" + ) + return_code = data.get("return_code") + if return_code == 3: + log.is_refund = True + log.save() + return {"code": 0 , "message": f"Hoàn tiền thành công", "data": data} + else: + log.is_refund = False + log.save() + return {"code": -1 , "message": f"Hoàn tiền thất bại", "data": data} + + + elif type.lower() == "vnpay": + log = PaymentCallbackLog.objects(type="vnpay", txn_ref=code).first() + if not log: + return {"code": -2, "message": "Không tìm thấy giao dịch VNPay"} + raw = log.raw_data + data = call_vnpay_refund( + txn_ref=code, + amount=int(raw.get("vnp_Amount", 0)), # VNPAY trả về x100 + transaction_date=raw.get("vnp_PayDate"), + transaction_no=raw.get("vnp_TransactionNo"), + create_by="system_refund", + order_info=raw.get("vnp_OrderInfo", "Hoàn tiền VNPay") + ) + + resultCode = data.get("data").get("vnp_ResponseCode") + message = data.get("data").get("vnp_Message") + if resultCode == "00": + log.is_refund = True + log.save() + return {"code": 0 , "message": f"Hoàn tiền thành công {message}", "data": data} + else: + log.is_refund = False + log.save() + return {"code": -1 , "message": f"Hoàn tiền thất bại {message}", "data": data} + + elif type.lower() == "momo": + log = PaymentCallbackLog.objects(type="momo", order_id=code).first() + if not log: + return {"code": -3, "message": "Không tìm thấy giao dịch MoMo"} + if log.raw_data.get("payType", "") != "qr": + return {"code": -2 , "message": f"Payment không thể hoàn tiền với ", "data": data} + data = call_momo_refund( + trans_id=log.raw_data.get("transId", 0), + amount=log.raw_data.get("amount", 0), + description=f"Hoàn tiền MoMo cho giao dịch {code}" + ) + resultCode = data.get("resultCode") + if resultCode == 0: + log.is_refund = True + log.save() + return {"code": 0 , "message": f"Hoàn tiền thành công", "data": data} + else: + log.is_refund = False + log.save() + return {"code": -1 , "message": f"Hoàn tiền thất bại", "data": data} + else: + return {"code": -4, "message": f"Không hỗ trợ cổng thanh toán '{type}'"} + +from fastapi import HTTPException +from pydantic import BaseModel + + +class Refund(BaseModel): + type: str + code: str + + +@router.post("/refund") +async def refund_endpoint(request: Refund,token: str = Depends(jwt_bearer)): + type = request.type + code = request.code + user_role = decode_token.JwtService.extract_user_role(token) + if user_role != "ADMIN": + return JSONResponse(content={"message": "Not allow"}, status_code=404) + + data = asyncio.run(refund_by_type_and_code(type = type,code=code)) + if data.get("code") != 0 : + return JSONResponse(content={"message": f"Not allow type {type}","status": -1, "data":data}, status_code=404) + else: + return JSONResponse( + content={ + "status": 0, + "data": data + }, + status_code=200 + ) + + +@router.post("/refund-schedule") +async def refund_endpoint_schedule(request: Refund): + type = request.type + code = request.code + data = asyncio.run(refund_by_type_and_code(type = type,code=code)) + if data.get("code") != 0 : + return JSONResponse(content={"message": f"Not allow type {type}","status": -1, "data":data}, status_code=404) + else: + return JSONResponse( + content={ + "status": 0, + "data": data + }, + status_code=200 + ) \ No newline at end of file diff --git a/controller/__init__.py b/controller/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/controller/__pycache__/CallbackController.cpython-311.pyc b/controller/__pycache__/CallbackController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61a3da5625d78bb1b2ab8db7afc539d617d25f2c Binary files /dev/null and b/controller/__pycache__/CallbackController.cpython-311.pyc differ diff --git a/controller/__pycache__/ChatController.cpython-311.pyc b/controller/__pycache__/ChatController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e71f8db3bb3dba27415008c606eef950743206f8 Binary files /dev/null and b/controller/__pycache__/ChatController.cpython-311.pyc differ diff --git a/controller/__pycache__/GroupOrderController.cpython-311.pyc b/controller/__pycache__/GroupOrderController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e571fd863d698b29d76573aa0aa1b504cdda310e Binary files /dev/null and b/controller/__pycache__/GroupOrderController.cpython-311.pyc differ diff --git a/controller/__pycache__/RecommendController.cpython-311.pyc b/controller/__pycache__/RecommendController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23c974122a6a782c8e2679d63564fd453cf9ead3 Binary files /dev/null and b/controller/__pycache__/RecommendController.cpython-311.pyc differ diff --git a/controller/__pycache__/RedirectController.cpython-311.pyc b/controller/__pycache__/RedirectController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47f2bae014af5b87ce0e2e99e13407cc0cd88c80 Binary files /dev/null and b/controller/__pycache__/RedirectController.cpython-311.pyc differ diff --git a/controller/__pycache__/RefundController.cpython-311.pyc b/controller/__pycache__/RefundController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34d2073b5df9c99f226d1465415e922e8bd7ba67 Binary files /dev/null and b/controller/__pycache__/RefundController.cpython-311.pyc differ diff --git a/controller/__pycache__/__init__.cpython-311.pyc b/controller/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8209ba7777ad6fa94b9471ddd794deea00a0732f Binary files /dev/null and b/controller/__pycache__/__init__.cpython-311.pyc differ diff --git a/controller/__pycache__/task_manager.cpython-311.pyc b/controller/__pycache__/task_manager.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c959cad3cc11414ffa864215b765cb6ec1684f00 Binary files /dev/null and b/controller/__pycache__/task_manager.cpython-311.pyc differ diff --git a/function/__init__.py b/function/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cb81f40753ef1a78c0c9aea8a1abc92d63ca6d5d --- /dev/null +++ b/function/__init__.py @@ -0,0 +1 @@ +from function import * \ No newline at end of file diff --git a/function/__pycache__/__init__.cpython-311.pyc b/function/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..107c174252abc87001f56c4e91c452372962220e Binary files /dev/null and b/function/__pycache__/__init__.cpython-311.pyc differ diff --git a/function/__pycache__/__init__.cpython-312.pyc b/function/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cce81fe18f201ef10d1ef998aabc34f565d459af Binary files /dev/null and b/function/__pycache__/__init__.cpython-312.pyc differ diff --git a/function/__pycache__/chat.cpython-311.pyc b/function/__pycache__/chat.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51c7c832bd6c4befda78fa944e0dcc420e4017a8 Binary files /dev/null and b/function/__pycache__/chat.cpython-311.pyc differ diff --git a/function/__pycache__/chat.cpython-312.pyc b/function/__pycache__/chat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee995f04dda1da5283138950257120199a68dcff Binary files /dev/null and b/function/__pycache__/chat.cpython-312.pyc differ diff --git a/function/advance_shopping/call_gemini/__pycache__/tool_call.cpython-311.pyc b/function/advance_shopping/call_gemini/__pycache__/tool_call.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..274d9aaec9a8c041781c0810c96a5810db71d47f Binary files /dev/null and b/function/advance_shopping/call_gemini/__pycache__/tool_call.cpython-311.pyc differ diff --git a/function/advance_shopping/call_gemini/tool_call.py b/function/advance_shopping/call_gemini/tool_call.py new file mode 100644 index 0000000000000000000000000000000000000000..112056d5ab024a52e5ea987b2ec8698ac48c529b --- /dev/null +++ b/function/advance_shopping/call_gemini/tool_call.py @@ -0,0 +1,44 @@ +import base64 +import os +import json +from google import genai +from google.genai import types + +thinking_config = types.ThinkingConfig(include_thoughts=True, thinking_budget=5000) + +def generate(prompt: str, max_retries: int = 3): + client = genai.Client( + api_key="AIzaSyA4eTxl-suN_D8COMiRA5UmrF-6vW7etys", + ) + model = "gemini-2.5-flash-preview-05-20" + contents = [ + types.Content( + role="user", + parts=[ + types.Part.from_text(text=prompt), + ], + ), + ] + generate_content_config = types.GenerateContentConfig( + response_mime_type="application/json", + thinking_config=thinking_config + ) + + for attempt in range(max_retries + 1): + result = "" + try: + for chunk in client.models.generate_content_stream( + model=model, + contents=contents, + config=generate_content_config, + ): + if chunk.text: # Đảm bảo chunk.text không rỗng + result += chunk.text + + if result.strip(): # Nếu kết quả không rỗng + return json.loads(result) + + except Exception as e: + print(f"[Lần {attempt + 1}] Lỗi khi gọi Gemini hoặc phân tích JSON: {e}") + + raise RuntimeError("Không thể tạo nội dung sau nhiều lần thử.") diff --git a/function/advance_shopping/function/__pycache__/test.cpython-311.pyc b/function/advance_shopping/function/__pycache__/test.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..119b7700966813fee816d949dc197c7bb85088bf Binary files /dev/null and b/function/advance_shopping/function/__pycache__/test.cpython-311.pyc differ diff --git a/function/advance_shopping/function/test.py b/function/advance_shopping/function/test.py new file mode 100644 index 0000000000000000000000000000000000000000..cb7ec0c11925d872ee33d5773b0a906300a9104f --- /dev/null +++ b/function/advance_shopping/function/test.py @@ -0,0 +1,615 @@ +# def extract_quantity(input: str, full_test, data) -> str: +# import base64 +# import os +# from google import genai +# from google.genai import types +# client = genai.Client( +# api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac", +# ) + +# model = "gemini-2.5-flash-preview-05-20" +# prompt = f""" +# Bạn là một chuyên gia trong việc trích xuất và lấy số lượng trong câu hỏi đồng thời lấy size sản phẩm ngoài ra bạn sẽ lấy tên sản phẩm kèm theo. +# Bạn sẽ thực hiện lấy đúng, chính xác số lượng sản phẩm đó. +# Ngoài ra hãy tham khảo thêm: {full_test} để xác xem liệu là sản phẩm nào thực sự bạn không cần phải lấy trong này có đề cập các sản phẩm không tồn tại. Nếu gặp không cần phải trích xuất dữ liệu chúng. Hãy đọc kĩ phân tích để tránh việc lấy nhầm sản phẩm không tồn tại. +# Phân biệt rõ ràng dù là Trà đào hay trà đào thì nó giống nhau khi viết thường vậy thì các sản phẩm khác cũng giống vậy. Không phải ưng cái thì trà chanh, cái thì Trà chanh điều này là sai hoàn toàn. +# Nhớ kĩ câu hỏi đầu vào của người dùng trong đó Người dùng hỏi: là câu hỏi trước đó người dùng nói khi đó trợ lý thấy thiếu sót điều gì sẽ hỏi lại và người dùng trả lời tiếp. Hãy nhớ Người dùng trả lời luôn là câu trả lời cuối cùng. +# Khi thực hiện phân tích bạn hãy xem thêm phần data này: {data}. Đây là các trích xuất bản ghi trước đó khi câu hỏi người dùng đưa vào bạn phải vận dụng để tạo ra list phù hợp mà không làm hao phí data này. +# - Nếu data không có, không cần check nhưng nếu có data vui lòng check kĩ càng +# **Yêu cầu phản hồi:** +# - Chỉ trả về số lượng duy nhất sau khi thực hiện. Yêu cầu không giải thích gì thêm. +# - Nếu sản phẩm đó trùng tên nhung khác size thì vẫn để riêng ra ví dụ ['"size": "M", "quantity": "5", name: "trà đào"', '"size": "L", "quantity": "3", name: "trà đào"'] +# - Chỉ trả về kiểu số ví dụ 1,2,3,... Nếu không có bắt được thì vui lòng để None +# - Trả về Size duy nhất của sản phẩm. Nếu có nhiều size của sản phẩm thì phải trích xuất ra đúng ví dụ ['"size": "M", "quantity": "5", name: "trà đào"', '"size": "L", "quantity": "3", name: "trà đào"'] là cùng trà đào mà có sie khác nhau. Vui lòng không tự sinh ra size của sản phẩm chỉ khi trong đó có thông tin về size của đúng sản phẩm đó thì mới lấy ra size của sản phẩm đó. +# - Vui lòng để nguyên tên sản phẩm ví dụ trà đào là trà đào nếu họ ghi Trà đào thì mới lấy Trà đào tức là không được thay đổi tên sản phẩm, viết hoa hay viết thường +# - Formart trả về là: Json: "size": "M", "quantity": "5", name: "trà đào" +# - Nếu xóa sản phẩm đó thì vui lòng không hiển thị lại trong chuỗi. Xác định rõ sản phẩm nào cần xóa, sản phẩm nào cập nhật để điều chỉnh list một cách hợp lý +# - Ví dụ có nhiều sản phẩm thì json ghép trong 1 chuỗi + +# **Văn bản đầu vào cần xử lý:** +# \"{input}\" +# """ +# contents = [ +# types.Content( +# role="user", +# parts=[ +# types.Part.from_text(text=f"""{prompt}"""), +# ], +# ), +# ] +# generate_content_config = types.GenerateContentConfig( +# response_mime_type="application/json", +# ) + +# result = "" + +# for chunk in client.models.generate_content_stream( +# model=model, +# contents=contents, +# config=generate_content_config, +# ): +# result += chunk.text # Gom tất cả vào 1 chuỗi + +# result = result.strip() +# return result + + +def extract_quantity(input: str, full_test, data) -> str: + import base64 + import os + import time + from google import genai + from google.genai import types + + client = genai.Client( + api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac", + ) + + model = "gemini-2.5-flash-preview-05-20" + + prompt = f""" +Bạn là một chuyên gia trong việc trích xuất và lấy số lượng trong câu hỏi đồng thời lấy size sản phẩm ngoài ra bạn sẽ lấy tên sản phẩm kèm theo. +Bạn sẽ thực hiện lấy đúng, chính xác số lượng sản phẩm đó. +Ngoài ra hãy tham khảo thêm: {full_test} để xác xem liệu là sản phẩm nào thực sự bạn không cần phải lấy trong này có đề cập các sản phẩm không tồn tại. Nếu gặp không cần phải trích xuất dữ liệu chúng. Hãy đọc kĩ phân tích để tránh việc lấy nhầm sản phẩm không tồn tại. +Phân biệt rõ ràng dù là Trà đào hay trà đào thì nó giống nhau khi viết thường vậy thì các sản phẩm khác cũng giống vậy. Không phải ưng cái thì trà chanh, cái thì Trà chanh điều này là sai hoàn toàn. +Nhớ kĩ câu hỏi đầu vào của người dùng trong đó Người dùng hỏi: là câu hỏi trước đó người dùng nói khi đó trợ lý thấy thiếu sót điều gì sẽ hỏi lại và người dùng trả lời tiếp. Hãy nhớ Người dùng trả lời luôn là câu trả lời cuối cùng. +Khi thực hiện phân tích bạn hãy xem thêm phần data này: {data}. Đây là các trích xuất bản ghi trước đó khi câu hỏi người dùng đưa vào bạn phải vận dụng để tạo ra list phù hợp mà không làm hao phí data này. +- Nếu data không có, không cần check nhưng nếu có data vui lòng check kĩ càng + +**Yêu cầu phản hồi:** +- Chỉ trả về số lượng duy nhất sau khi thực hiện. Yêu cầu không giải thích gì thêm. +- Nếu sản phẩm đó trùng tên nhưng khác size thì vẫn để riêng ra ví dụ ['"size": "M", "quantity": "5", name: "trà đào"', '"size": "L", "quantity": "3", name: "trà đào"'] +- Chỉ trả về kiểu số ví dụ 1,2,3,... Nếu không có bắt được thì vui lòng để None +- Trả về Size duy nhất của sản phẩm. Nếu có nhiều size của sản phẩm thì phải trích xuất ra đúng ví dụ ['"size": "M", "quantity": "5", name: "trà đào"', '"size": "L", "quantity": "3", name: "trà đào"'] là cùng trà đào mà có size khác nhau. Vui lòng không tự sinh ra size của sản phẩm chỉ khi trong đó có thông tin về size của đúng sản phẩm đó thì mới lấy ra size của sản phẩm đó. +- Vui lòng để nguyên tên sản phẩm ví dụ trà đào là trà đào nếu họ ghi Trà đào thì mới lấy Trà đào tức là không được thay đổi tên sản phẩm, viết hoa hay viết thường +- Format trả về là: Json: "size": "M", "quantity": "5", name: "trà đào" +- Nếu xóa sản phẩm đó thì vui lòng không hiển thị lại trong chuỗi. Xác định rõ sản phẩm nào cần xóa, sản phẩm nào cập nhật để điều chỉnh list một cách hợp lý +- Ví dụ có nhiều sản phẩm thì json ghép trong 1 chuỗi + +**Văn bản đầu vào cần xử lý:** +\"{input}\"""" + + contents = [ + types.Content( + role="user", + parts=[ + types.Part.from_text(text=prompt), + ], + ) + ] + + generate_content_config = types.GenerateContentConfig( + response_mime_type="application/json", + ) + + retries = 0 + max_retries = 2 + while retries <= max_retries: + try: + result = "" + for chunk in client.models.generate_content_stream( + model=model, + contents=contents, + config=generate_content_config, + ): + result += chunk.text + result = result.strip() + if result: # Nếu có nội dung thì trả về luôn + return result + except Exception as e: + pass # Có thể log lỗi nếu muốn + retries += 1 + time.sleep(1) # Nghỉ 1 giây trước khi thử lại + + return "None" + + +def generate_question(user_input: str, data_cleant: list, data_check: list, full_test: str) -> str: + import base64 + import os + import time + from google import genai + from google.genai import types + + client = genai.Client( + api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac", + ) + + model = "gemini-2.5-flash-preview-05-20" + + prompt = f""" +Bạn là một chuyên gia trong việc đặt câu hỏi làm rõ thông tin sản phẩm còn thiếu từ người dùng, dựa trên kết quả phân tích đầu vào. + +### 1. Mục tiêu: +- Từ danh sách sản phẩm người dùng yêu cầu (`data_cleant`), kết hợp với kết quả kiểm tra (`data_check`), bạn cần tạo **một câu hỏi duy nhất** để làm rõ những thông tin còn thiếu hoặc chưa chính xác. +- Hãy đọc kĩ phần mô tả: {full_test} để xác định xem liệu là với câu hỏi người dùng và data truyền vào các nhận xét này sẽ chỉ ra điểm yếu kém mã mỗi sản phầm còn tồn đọng. Nếu có hãy vui lòng tạo câu hỏi một cách chính xác, công tâm. +- Luôn đảm bảo câu hỏi bao quanh được các nhận xét của mình. +- Với các sản phẩm không tồn tại thì không cần hỏi nữa tuy nhiên nếu không còn sản phẩm gì thì hãy hỏi người dùng liệu có muốn mua sản phẩm khác hay không +- Nếu không có gì cần hỏi thêm, chỉ trả về chuỗi "None" (không có dấu ngoặc) khi cảm thấy tất cả đã đáp ứng được. + +### 2. Định nghĩa dữ liệu: +- `data_cleant`: danh sách sản phẩm đã được tách từ văn bản người dùng, mỗi phần tử gồm: + - "name": tên sản phẩm. + - "size": kích cỡ (có thể thiếu). + - "quantity": số lượng (có thể thiếu). + +- `data_check`: danh sách tương ứng (cùng thứ tự) với `data_cleant`, mỗi phần tử chứa kết quả kiểm tra: + - Nếu bắt đầu bằng ❌: sản phẩm không tồn tại → loại khỏi danh sách hỏi. + - Nếu chứa ⚠️: sản phẩm có tồn tại nhưng thiếu thông tin → cần hỏi lại. + - Nếu chứa ✅: sản phẩm hợp lệ và đầy đủ → không cần hỏi lại. + +### 3. Yêu cầu tạo câu hỏi: +- Bỏ qua hoàn toàn các sản phẩm có `check` bắt đầu bằng ❌, không hỏi lại, chỉ có thể đề cập nhẹ rằng chúng đã bị loại. +- Với các sản phẩm còn lại (⚠️ hoặc ✅): + - Nếu thiếu `size` hoặc `quantity`, hãy đặt **một câu hỏi duy nhất** để làm rõ toàn bộ phần còn thiếu. + - Nếu mọi thứ đã đầy đủ và hợp lệ, chỉ trả về "None". +- Nếu data_cleant rỗng hoặc không có sản phẩm nào cần hỏi, hãy hỏi họ xem có muốn mua sản phẩm khác hay không + +### 4. Văn bản người dùng đã nhập: +"{user_input}" + +### 5. Danh sách sản phẩm đã xử lý (`data_cleant`): +{data_cleant} + +### 6. Kết quả kiểm tra từng sản phẩm (`data_check`): +{data_check} + +### 7. Tổng hợp kết quả kiểm tra: +{full_test} + +### ⚠️ Quy tắc bắt buộc: +- **Chỉ trả về một câu hỏi duy nhất** hoặc chuỗi "None". +- **Không giải thích, không liệt kê lại dữ liệu, không mô tả lại logic.** +""" + + contents = [ + types.Content( + role="user", + parts=[ + types.Part.from_text(text=prompt), + ], + ) + ] + + generate_content_config = types.GenerateContentConfig( + response_mime_type="application/json", + ) + + for attempt in range(3): # thử tối đa 3 lần + try: + result = "" + for chunk in client.models.generate_content_stream( + model=model, + contents=contents, + config=generate_content_config, + ): + if chunk.text: + result += chunk.text + + return result.strip() + except Exception as e: + if attempt == 2: + raise e # nếu thử 3 lần vẫn lỗi, raise ra ngoài + time.sleep(1) # chờ 1s trước khi retry + + +# def generate_question(user_input: str, data_cleant: list, data_check: list, full_test:str) -> str: +# import base64 +# import os +# from google import genai +# from google.genai import types + +# client = genai.Client( +# api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac", +# ) + +# model = "gemini-2.5-flash-preview-05-20" + +# prompt = f""" +# Bạn là một chuyên gia trong việc đặt câu hỏi làm rõ thông tin sản phẩm còn thiếu từ người dùng, dựa trên kết quả phân tích đầu vào. + +# ### 1. Mục tiêu: +# - Từ danh sách sản phẩm người dùng yêu cầu (`data_cleant`), kết hợp với kết quả kiểm tra (`data_check`), bạn cần tạo **một câu hỏi duy nhất** để làm rõ những thông tin còn thiếu hoặc chưa chính xác. +# - Hãy đọc kĩ phần mô tả: {full_test} để xác định xem liệu là với câu hỏi người dùng và data truyền vào các nhận xét này sẽ chỉ ra điểm yếu kém mã mỗi sản phầm còn tồn đọng. Nếu có hãy vui lòng tạo câu hỏi một cách chính xác, công tâm. +# - Luôn đảm bảo câu hỏi bao quanh được các nhận xét của mình. +# - Với các sản phẩm không tồn tại thì không cần hỏi nữa tuy nhiên nếu không còn sản phẩm gì thì hãy hỏi người dùng liệu có muốn mua sản phẩm khác hay không +# - Nếu không có gì cần hỏi thêm, chỉ trả về chuỗi `"None"` (không có dấu ngoặc) khi cảm thấy tất cả đã đáp ứng được. + +# ### 2. Định nghĩa dữ liệu: +# - `data_cleant`: danh sách sản phẩm đã được tách từ văn bản người dùng, mỗi phần tử gồm: +# - `"name"`: tên sản phẩm. +# - `"size"`: kích cỡ (có thể thiếu). +# - `"quantity"`: số lượng (có thể thiếu). + +# - `data_check`: danh sách tương ứng (cùng thứ tự) với `data_cleant`, mỗi phần tử chứa kết quả kiểm tra: +# - Nếu bắt đầu bằng `❌`: sản phẩm không tồn tại → loại khỏi danh sách hỏi. +# - Nếu chứa `⚠️`: sản phẩm có tồn tại nhưng thiếu thông tin → cần hỏi lại. +# - Nếu chứa `✅`: sản phẩm hợp lệ và đầy đủ → không cần hỏi lại. + +# ### 3. Yêu cầu tạo câu hỏi: +# - Bỏ qua hoàn toàn các sản phẩm có `check` bắt đầu bằng `❌`, không hỏi lại, chỉ có thể đề cập nhẹ rằng chúng đã bị loại. +# - Với các sản phẩm còn lại (⚠️ hoặc ✅): +# - Nếu thiếu `size` hoặc `quantity`, hãy đặt **một câu hỏi duy nhất** để làm rõ toàn bộ phần còn thiếu. +# - Nếu mọi thứ đã đầy đủ và hợp lệ, chỉ trả về `"None"`. +# - Nếu data_cleant rỗng hoặc không có sản phẩm nào cần hỏi, hãy hỏi họ xem có muốn mua sản phẩm khác hay không + +# ### 4. Văn bản người dùng đã nhập: +# \"{user_input}\" + +# ### 5. Danh sách sản phẩm đã xử lý (`data_cleant`): +# {data_cleant} + +# ### 6. Kết quả kiểm tra từng sản phẩm (`data_check`): +# {data_check} + +# ### 7. Tổng hợp kết quả kiểm tra: +# {full_test} + +# ### ⚠️ Quy tắc bắt buộc: +# - **Chỉ trả về một câu hỏi duy nhất** hoặc chuỗi `"None"`. +# - **Không giải thích, không liệt kê lại dữ liệu, không mô tả lại logic.** +# """ + +# contents = [ +# types.Content( +# role="user", +# parts=[ +# types.Part.from_text(text=prompt), +# ], +# ) +# ] + +# generate_content_config = types.GenerateContentConfig( +# response_mime_type="application/json", +# ) + +# result = "" + +# for chunk in client.models.generate_content_stream( +# model=model, +# contents=contents, +# config=generate_content_config, +# ): +# result += chunk.text + +# return result.strip() + + + + +example_json = [ + { "name": "trà đào", "size": "S", "quantity": 2, "action": "update" }, + { "name": "trà sữa", "size": "S", "action": "delete" }, + { "name": "hồng trà", "size": "M", "quantity": 1, "action": "add" } +] + + +def classify_data(input: str, structured_data,max_retries=3, retry_delay=1.0) -> str: + import base64 + import os + from google import genai + from google.genai import types + client = genai.Client( + api_key="AIzaSyA4eTxl-suN_D8COMiRA5UmrF-6vW7etys", + ) + + model = "gemini-2.5-flash-preview-05-20" + prompt = f""" +Bạn là một chuyên gia xử lý ngôn ngữ, có nhiệm vụ xác định **ý định** của người dùng đối với các sản phẩm (thêm, cập nhật hoặc xóa) và trích xuất chính xác thông tin liên quan. +- Bên này chỉ được hỏi các sản phẩm liên quan đến đồ uống, nc ngọt, không đc phép thêm các sản phẩm thể loại khác nhu sách, phim, movie +### Dữ liệu đầu vào: +- Câu query mới: "{input}" +- Dữ liệu sản phẩm cũ: {structured_data} +--- + +### Yêu cầu xử lý: + +1. So sánh **câu query mới** và **Dữ liệu sản phẩm cũ** để xác định **ý định chính** của người dùng là: + - Xác định đúng tên sản phẩm, size được đề cập đến và nếu có hành động xóa thì phải gán thêm action đúng chỗ không phải tạo ra đối tượng khác. + - `add`: thêm sản phẩm mới. + - `update`: cập nhật thông tin sản phẩm cũ (tên, size, số lượng). + - `delete`: xóa sản phẩm (có thể là một hoặc nhiều). +2. Trích xuất dữ liệu dạng JSON list gồm các sản phẩm bị ảnh hưởng trong câu mới. +2. Nếu là hành động thêm cần xác định rõ tên sản phẩm, size (nếu có) và số lượng (nếu có). Và ý nghĩa câu {input} ví dụ họ cũng thêm vào sản phẩm đã tồn tại đó thì quantity phải cộng lên hoặc giảm xuống.(Quan trọng) +3. Mỗi sản phẩm trong JSON phải có các trường: + - `name`: tên sản phẩm. + - `size`: nếu có, ghi size. Nếu không có ghi "None". Phân biệt rõ size này là của sản phẩm nào. + - `quantity`: nếu có, ghi số lượng. Nếu không có ghi là 1. Nếu không có số lượng thì ghi là 1. Số lượng không bao giờ được phép ấm nếu trừ hay giảm mà âm hoặc bằng 0 thì phải gán là 0. + - `action`: hành động tương ứng (add / update / delete). + - size_old: Nếu là hành động `update`, cần xác định rõ **size cũ** của sản phẩm để cập nhật.(Chỉ khi nào có yêu cầu thay thế size(thay thế size mà không đổi số lượng thì số lượng phải giữ nguyên), kích cỡ) +4. Nếu là hành động `delete`, cần xác định rõ **tên và size** của sản phẩm cần xóa. +6. Nếu có hành động thay sản phẩm này bằng sản phâm khác thì cần xác định rõ tên sản phẩm mới và size mới, khi đó sản phẩm mới size mới là add còn cái bị thay thế sẽ thành deleted. +5. Nếu sản phẩm không đủ thông tin (thiếu tên), thì **bỏ qua** không thêm vào JSON. +6. Nếu người dùng nói xóa toàn bộ sản phẩm, thì liệt kê tất cả sản phẩm hiện có trong structured_data với action là `delete`. +7. Nếu không xác định được hành động nào, trả về `"None"`. +8. Nếu đã có sản phẩm trong structured_data nhưng không được đề cập trong câu mới, thì giữ nguyên thông tin của sản phẩm đó. Nếu có đề cập thì xác định rõ ý định của người dùng +**Yêu cầu phản hồi phải tuân theo: + - Vui lòng trả về danh sách toàn bộ các sản phẩm gồm có các dữ liệu cũ và dữ liệu update, luôn thay dữ liệu cu c bằng dữ liệu mới nếu có update,delete nếu không có update,delete thì giữ nguyên dữ liệu cũ. + - Dữ liệu insert,add cũng phải được thêm vào danh sách này, không được tạo ra một đối tượng khác mà phải là một chuỗi JSON nối tiếp với các sản phẩm khác. + - Không được phép viết hoa tên sản phẩm, chỉ viết thường trừ khi bản gốc đã viết hoa. Không thêm dấu câu, không xuống dòng. + - Luôn trả về đầy đủ gồm các sản phẩm size delete, update, add, insert trong một chuỗi JSON nối tiếp. +--- +### ✅ Output mẫu (phải đúng định dạng JSON array, không xuống dòng, không giải thích): + +```json +{example_json} +``` +""" + contents = [ + types.Content( + role="user", + parts=[ + types.Part.from_text(text=f"""{prompt}"""), + ], + ), + ] + generate_content_config = types.GenerateContentConfig( + response_mime_type="application/json", + ) + + result = "" + + + for attempt in range(1, max_retries + 1): + try: + result = "" + for chunk in client.models.generate_content_stream( + model=model, + contents=contents, + config=generate_content_config, + ): + if chunk.text: + result += chunk.text + return result.strip() + except Exception as e: + if attempt == max_retries: + raise RuntimeError(f"[Gemini classify_data failed] After {max_retries} attempts: {str(e)}") + else: + import time + print(f"[Retry {attempt}/{max_retries}] Error: {e}. Retrying in {retry_delay} seconds...") + time.sleep(retry_delay) + + +def update_data(input: str, input_old, structured_data, max_retries: int = 2) -> str: + import base64 + import os + import time + from google import genai + from google.genai import types + + client = genai.Client( + api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac", + ) + + model = "gemini-2.5-flash-preview-05-20" + + prompt = f""" +Bạn là một chuyên gia trong việc cập nhật lại sản phẩm từ yêu cầu mới của người dùng dựa trên yêu cầu trước đó. + +- Câu query mới: {input} +- Câu query cũ: {input_old} +- Dữ liệu sản phẩm ban đầu được trích xuất từ câu cũ: +{structured_data} + +**Yêu cầu xử lý:** + +1. So sánh giữa câu query mới và câu query cũ để xác định người dùng đã thay đổi gì: về tên sản phẩm, size, số lượng hoặc loại bỏ/thêm sản phẩm. +2. Dựa vào structured_data, xác định các sản phẩm trong câu mới để cập nhật tương ứng. +3. Nếu người dùng đổi tên sản phẩm, thì thay thế sản phẩm cũ gần nhất (vị trí hoặc tên) bằng sản phẩm mới đó. +4. Nếu người dùng cập nhật size cho sản phẩm, thì ghi đè size mới. +5. Nếu người dùng không nói rõ size, nhưng sản phẩm đó đã tồn tại trong structured_data, thì giữ nguyên size cũ. +6. Nếu người dùng cập nhật số lượng, thì ghi đè lại số lượng theo câu mới. +7. Nếu có sản phẩm mới hoàn toàn chưa có trong structured_data và đủ thông tin (tên, số lượng, size), thì thêm vào danh sách. +8. Nếu sản phẩm mới không có đủ thông tin (thiếu tên, size hoặc số lượng), thì bỏ qua không thêm. +9. Nếu một sản phẩm trong structured_data không bị thay đổi hoặc nhắc đến trong câu mới, thì giữ nguyên thông tin sản phẩm đó. +10. Nếu không thể xác định được bất kỳ sản phẩm nào trong câu mới, thì trả về "None" +11. Nếu người dùng có đề cập giữ lại thì xác định xem có sản phẩm nào trong structured_data không bị thay đổi thì giữ lại sản phẩm đó. +12. Nếu người dùng nói mua thêm hay thêm sản phẩm(các từ có ngữ ý là mua sản phẩm khác) mà không có trong structured_data thì bạn sẽ thêm sản phẩm mới vào danh sách +**Phản hồi chỉ gồm chuỗi JSON không giải thích gì thêm. Mỗi sản phẩm là một object JSON trong chuỗi nối tiếp, ví dụ:** + +**Tuyệt đối không viết hoa tên sản phẩm trừ khi bản gốc đã viết hoa. Không thêm dấu câu, không xuống dòng.** + +**Văn bản đầu vào mới cần xử lý:** +"{input}" +""" + + contents = [ + types.Content( + role="user", + parts=[types.Part.from_text(prompt)], + ), + ] + generate_content_config = types.GenerateContentConfig( + response_mime_type="application/json", + ) + + for attempt in range(max_retries + 1): + result = "" + try: + for chunk in client.models.generate_content_stream( + model=model, + contents=contents, + config=generate_content_config, + ): + if chunk.text: + result += chunk.text + + result = result.strip() + if result: + return result + + except Exception as e: + print(f"[Lần {attempt + 1}] Lỗi khi gọi Gemini: {e}") + time.sleep(1) + + raise RuntimeError("Không thể tạo dữ liệu cập nhật sau nhiều lần thử.") + + +# def update_data(input: str, input_old, structured_data) -> str: +# import base64 +# import os +# from google import genai +# from google.genai import types +# client = genai.Client( +# api_key="AIzaSyBka2z9sIE-BIHXb9o2Z-hoZ6TPzXCDnac", +# ) + +# model = "gemini-2.5-flash-preview-05-20" +# prompt = f""" +# Bạn là một chuyên gia trong việc cập nhật lại sản phẩm từ yêu cầu mới của người dùng dựa trên yêu cầu trước đó. + +# - Câu query mới: {input} +# - Câu query cũ: {input_old} +# - Dữ liệu sản phẩm ban đầu được trích xuất từ câu cũ: +# {structured_data} + +# **Yêu cầu xử lý:** + +# 1. So sánh giữa câu query mới và câu query cũ để xác định người dùng đã thay đổi gì: về tên sản phẩm, size, số lượng hoặc loại bỏ/thêm sản phẩm. +# 2. Dựa vào structured_data, xác định các sản phẩm trong câu mới để cập nhật tương ứng. +# 3. Nếu người dùng đổi tên sản phẩm, thì thay thế sản phẩm cũ gần nhất (vị trí hoặc tên) bằng sản phẩm mới đó. +# 4. Nếu người dùng cập nhật size cho sản phẩm, thì ghi đè size mới. +# 5. Nếu người dùng không nói rõ size, nhưng sản phẩm đó đã tồn tại trong structured_data, thì giữ nguyên size cũ. +# 6. Nếu người dùng cập nhật số lượng, thì ghi đè lại số lượng theo câu mới. +# 7. Nếu có sản phẩm mới hoàn toàn chưa có trong structured_data và đủ thông tin (tên, số lượng, size), thì thêm vào danh sách. +# 8. Nếu sản phẩm mới không có đủ thông tin (thiếu tên, size hoặc số lượng), thì bỏ qua không thêm. +# 9. Nếu một sản phẩm trong structured_data không bị thay đổi hoặc nhắc đến trong câu mới, thì giữ nguyên thông tin sản phẩm đó. +# 10. Nếu không thể xác định được bất kỳ sản phẩm nào trong câu mới, thì trả về "None" +# 11. Nếu người dùng có đề cập giữ lại thì xác định xem có sản phẩm nào trong structured_data không bị thay đổi thì giữ lại sản phẩm đó. +# 12. Nếu người dùng nói mua thêm hay thêm sản phẩm(các từ có ngữ ý là mua sản phẩm khác) mà không có trong structured_data thì bạn sẽ thêm sản phẩm mới vào danh sách +# **Phản hồi chỉ gồm chuỗi JSON không giải thích gì thêm. Mỗi sản phẩm là một object JSON trong chuỗi nối tiếp, ví dụ:** + + +# **Tuyệt đối không viết hoa tên sản phẩm trừ khi bản gốc đã viết hoa. Không thêm dấu câu, không xuống dòng.** + +# **Văn bản đầu vào mới cần xử lý:** +# "{input}" + +# """ +# contents = [ +# types.Content( +# role="user", +# parts=[ +# types.Part.from_text(text=f"""{prompt}"""), +# ], +# ), +# ] +# generate_content_config = types.GenerateContentConfig( +# response_mime_type="application/json", +# ) + +# result = "" + +# for chunk in client.models.generate_content_stream( +# model=model, +# contents=contents, +# config=generate_content_config, +# ): +# result += chunk.text + +# result = result.strip() +# return result + + +def validate_products(data_list, server_java): + validated_data = [] + + for product in data_list: + product_name = product.get("name") + product_size = product.get("size") + + if product_name not in [None, "", "None"] and product_size not in [None, "", "None"]: + check = server_java.search_product(keyword=product_name, size=product_size) + + if check is None: + # Không thêm sản phẩm này vào danh sách kết quả + print(f"❌ Không tồn tại sản phẩm '{product_name}' với size '{product_size}'") + continue # bỏ qua sản phẩm này + + # Nếu tồn tại nhưng không có size + if isinstance(check, tuple) and (len(check) > 2 and check[2] in [None, "", "None"]): + product["check"] = f"⚠️ Sản phẩm '{product_name}' không có size '{product_size}'" + else: + product["check"] = f"✅ Sản phẩm '{product_name}' với size '{product_size}' tồn tại" + + validated_data.append(product) + + else: + product["check"] = "⚠️ Thiếu thông tin sản phẩm (name hoặc size)" + validated_data.append(product) + + return validated_data + + +def validate_product_list(data_list, server_java): + print(data_list) + annotated_list = [] + clean_list = [] + full_test = "" + + for product in data_list: + product_copy = product.copy() + product_name = product_copy.get("name") + product_size = product_copy.get("size") + + if product_name not in [None, "", "None"]: + check = server_java.search_product(keyword=product_name, size=product_size) + + if check is None: + full_test += f".Không tồn tại sản phẩm '{product_name}' với size '{product_size} \n" + product_copy["check"] = f"❌ Không tồn tại sản phẩm '{product_name}' với size '{product_size}'" + product_copy["deleted"] = True + elif isinstance(check, tuple) and (len(check) > 2 and check[2] in [None, "", "None"]): + product_copy["check"] = f"⚠️ Sản phẩm '{product_name}' không có size '{product_size}'" + full_test += f".Không tồn tại sản phẩm '{product_name}' không có size '{product_size}" + product_copy["deleted"] = True + else: + product_copy["check"] = f"✅ Sản phẩm '{product_name}' với size '{product_size}' tồn tại" + if int(check[3]) == 0: + full_test += f".Sản phẩm '{product_name}' size '{product_size} hiện tại không còn hàng" + product_copy["deleted"] = True + if product['quantity'] in [None, "", "None"]: + full_test += f".Sản phẩm '{product_name}' size '{product_size} chưa nhập số lượng" + elif int(check[3]) < int(product['quantity']): + full_test += f".Sản phẩm '{product_name}' size '{product_size} hiện tại không có đủ số lượng, số lượng còn lại chỉ là tầm {int(check[3])}" + else: + product_copy["check"] = "⚠️ Thiếu thông tin sản phẩm (name hoặc size)" + + annotated_list.append(product_copy) + + if not product_copy.get("deleted", False): + # Tạo một bản sao sạch chỉ chứa các trường cần thiết + clean_item = { + "name": product_copy.get("name"), + "size": product_copy.get("size"), + "quantity": product_copy.get("quantity"), + "check": product_copy.get("check") + } + clean_list.append(clean_item) + + return annotated_list, clean_list, full_test + + + + + diff --git a/function/advance_shopping/prompt/__pycache__/context_prompt.cpython-311.pyc b/function/advance_shopping/prompt/__pycache__/context_prompt.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c83282e34efb9b79cb78a019a31415d918a732be Binary files /dev/null and b/function/advance_shopping/prompt/__pycache__/context_prompt.cpython-311.pyc differ diff --git a/function/advance_shopping/prompt/__pycache__/multitool_prompt.cpython-311.pyc b/function/advance_shopping/prompt/__pycache__/multitool_prompt.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b18f515e49c1b82b2a8e00da049cab9cc1ca331 Binary files /dev/null and b/function/advance_shopping/prompt/__pycache__/multitool_prompt.cpython-311.pyc differ diff --git a/function/advance_shopping/prompt/__pycache__/user_intent.cpython-311.pyc b/function/advance_shopping/prompt/__pycache__/user_intent.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7936fb4595f9f0380c2de6aeaf8f3e2e0096cee Binary files /dev/null and b/function/advance_shopping/prompt/__pycache__/user_intent.cpython-311.pyc differ diff --git a/function/advance_shopping/prompt/context_prompt.py b/function/advance_shopping/prompt/context_prompt.py new file mode 100644 index 0000000000000000000000000000000000000000..76546db88acb7b35341e53e90fcfa35de2de22ec --- /dev/null +++ b/function/advance_shopping/prompt/context_prompt.py @@ -0,0 +1,38 @@ +def build_context_prompt(chat_history: str, cart_exists: str) -> str: + """ + Tạo prompt đầu vào cho LLM dựa trên đoạn hội thoại và trạng thái giỏ hàng. + + Parameters: + - chat_history (str): Đoạn hội thoại giữa chatbot và người dùng. + - cart_exists (str): "Yes" nếu còn giỏ hàng, "No" nếu không. + + Returns: + - str: Prompt hoàn chỉnh để gửi cho LLM. + """ + return f""" +Bạn là một chuyên gia trong việc xử lý ngữ cảnh hội thoại để hỗ trợ mua sắm. + +Tôi sẽ cung cấp cho bạn: +- Một đoạn hội thoại giữa chatbot và người dùng (chat_history). +- Trạng thái giỏ hàng (cart_exists), giá trị là "Yes" nếu người dùng đang còn giỏ hàng, "No" nếu không. + +Yêu cầu: +- Nếu cart_exists là "Yes", bạn phải đưa ra **một câu hỏi duy nhất** để hướng người dùng quay lại việc mua sản phẩm, dựa vào đoạn hội thoại trước. +- Nếu cart_exists là "No", chỉ trả về "None", không thêm bất kỳ nội dung nào khác. +- Không tóm tắt, không giải thích, chỉ trả về câu hỏi hoặc "None". + +Nếu câu trả lời bạn tạo ra có ý hỏi người dùng về việc thêm, xóa, sửa sản phẩm vào giỏ hay tạo thanh toán thì hãy trả thêm một biến gọi là is_question_orders và gán là True nếu không có hãy gán là False. +Hãy trả về format Json gồm có hai biến là question và is_question_orders + + +Dữ liệu đầu vào: +chat_history: +{chat_history} + +cart_exists: {cart_exists} + +Trả lời: +""".strip() + + + diff --git a/function/advance_shopping/prompt/multitool_prompt.py b/function/advance_shopping/prompt/multitool_prompt.py new file mode 100644 index 0000000000000000000000000000000000000000..1698c89107a865a06c365616d40aa8cdf1364dd6 --- /dev/null +++ b/function/advance_shopping/prompt/multitool_prompt.py @@ -0,0 +1,83 @@ +example_json_multi_prompt = { + "question": "Thêm sản phẩm nước ép xoài size H vào giỏ hàng, xóa sản phẩm trà chanh khỏi giỏ hàng.", + "question_normal": { + "question_normal1": "Xem danh sách các bài viết mới." + }, + "confirm_order": False +} + + +def build_multi_tool_prompt(question: str, user_answer: str, tools: list) -> str: + tool_list = ", ".join(tools) + prompt = f"""Bạn là một chuyên gia trong việc phân tích ý định người dùng để xác định **tất cả hành động** cần thực hiện trong hệ thống bán hàng. + +### Dữ liệu đầu vào: +- Một câu hỏi từ hệ thống gửi tới người dùng: +question: "{question}" + +- Một câu trả lời từ người dùng: +user_answer: "{user_answer}" + +- Danh sách các tool khả dụng: +{tool_list} + +--- + +### Danh sách tool và chức năng: +- `insert_cart`: Thêm sản phẩm vào giỏ hàng +- `update_cart`: Cập nhật thông tin sản phẩm đã có trong giỏ hàng (ví dụ: size, số lượng) +- `delete_item_cart`: Xoá một sản phẩm cụ thể khỏi giỏ hàng +- `deleted all cart`: Xoá toàn bộ giỏ hàng +- `confirm_order`: Xác nhận đơn hàng hiện tại + +--- + +### Yêu cầu: +1. Phân tích kỹ câu trả lời của người dùng. +2. Xác định **mọi hành động** mà người dùng mong muốn thực hiện. +3. Gán đúng với một trong các tool trong danh sách. +4. Với các tool `insert_cart`, `update_cart`, `delete_item_cart`, cần có các tham số: + - `product_name`: tên sản phẩm (bắt buộc) + - `size`: kích cỡ (nếu có) + - `quantity`: số lượng (nếu có) +5. Các tool `confirm_order` và `deleted all cart` không cần tham số. +7. Yêu cầu đặt hàng mà vẫn có các thông tin về tên sản phẩm hoặc size hay cả hai đều phải giữ lại trong question +6. Các câu nói về xác nhận thanh toán, xem giỏ hàng đều không cần để question + +--- + +### Định dạng đầu ra: +ạn chỉ cần viết lại một câu duy nhất mô tả lại các hành động bằng ngôn ngữ tự nhiên và xác định rõ tên sản phẩm mà họ đề cập. Vui lòng viết rõ ràng chính xác để có thể hiểu bạn nói gì nghĩa là dùng , cho các hành động khác. Nếu có yêu cầu xác nhận thanh toán thì thêm một trường là confirm_orders và câu hỏi sẽ đc đưa vào question. Sau đó hãy trả về Json +Bạn sẽ nhận được một câu mô tả hành động từ người dùng. +Hãy đọc kỹ và phân loại nội dung theo 3 yêu cầu sau: +1. question: +- Viết lại duy nhất một câu mô tả đầy đủ các hành động liên quan đến việc mua hàng, bao gồm: + + Thêm/xóa sản phẩm khỏi giỏ hàng + + Chọn size, loại sản phẩm + + Đặt hàng, xác nhận đơn hàng + + Các thao tác với đơn hàng, giỏ hàng, thanh toán + + Hành động phải nối bằng dấu phẩy “,”, và ghi rõ tên sản phẩm, size nếu có. + + Các câu nói về xác nhận thanh toán, xem giỏ hàng, xóa vật phẩm trong giỏ hàng đều không cần để question +2. question_normal: +- Tách các câu hỏi hoặc hành động không liên quan đến việc mua hàng, như: + + Xem bài viết, tra cứu thông tin, hỏi khuyến mãi + + Xem bài đăng, thời gian mở cửa, v.v. + + Trả về dưới dạng dictionary với các khóa: question_normal1, question_normal2, ... + + Nhưng nếu câu hỏi đó liên quan đến việc xem sản phẩm trong giỏ hàng, đơn hàng hay các hành động liện quan có giỏ hàng, đơn hàng thì vui lòng không bỏ vào question_normal +3. confirm_order: + + Gán giá trị true nếu câu chứa hành động xác nhận đơn hàng hoặc thanh toán (ví dụ: “xác nhận đơn hàng”, “đặt mua”, “tôi muốn thanh toán”,...) + + Gán false nếu không có hành động đó. +. Trả về một JSON gồm 3 trường: +Ví dụ: +"question": theo mô tả của minh ỏ trên. Các câu nói về xác nhận thanh toán, xem giỏ hàng, xóa vật phẩm trong giỏ hàng đều không cần để question chỉ cần để. Yêu cầu đặt hàng mà vẫn có các thông tin về tên sản phẩm hoặc size hay cả hai đều phải giữ lại question "" +"question chỉ trống khi câu đó riêng lẻ là xác nhận mua hàng, tôi muốn mua hàng, đặt hàng, tiến hành thanh toán,..." +"question_normal": theo mô tả trên +"confirm_order": true hoặc false + + +Ví dụ trả về: +{example_json_multi_prompt} + +Bắt đầu phân tích dữ liệu sau đây.""" + + return prompt \ No newline at end of file diff --git a/function/advance_shopping/prompt/user_intent.py b/function/advance_shopping/prompt/user_intent.py new file mode 100644 index 0000000000000000000000000000000000000000..c592a98260c324f85e0e1114f4cbcbef2d29612a --- /dev/null +++ b/function/advance_shopping/prompt/user_intent.py @@ -0,0 +1,127 @@ +json_example = { + "type": """ "shopping_question" | "normal_question" | "unclear" """, + "clarification_question_if_unclear": """ "string | null" """, + "is_confirm_cart": """ Yes | No""", + "is_deleted_cart": """ Yes | No""", + "is_confirm_order": """Yes | No """, + "is_deleted_order": """ Yes| No""", + "is_view_cart": """ Yes| No""", +} + + + + +def build_detailed_user_intent_prompt(cart_status: str, chat_history: str, user_question: str) -> str: + prompt = f""" +Bạn là một hệ thống phân tích hội thoại thông minh trong lĩnh vực thương mại điện tử, có nhiệm vụ xác định ý định của người dùng dựa trên hội thoại với chatbot và trạng thái giỏ hàng hiện tại. + +### Mục tiêu: +Phân loại **câu hỏi mới nhất của người dùng** vào một trong ba nhóm ý định sau: + +--- + +### 1. `shopping_question` – Câu hỏi mang ý định mua sắm + +**Định nghĩa**: Là câu hỏi thể hiện mong muốn của người dùng muốn thực hiện một hành động mua sắm online hoặc tương tác với các chức năng thương mại điện tử. + +**Các hành động phổ biến cần nhận diện:** +- Thêm sản phẩm vào giỏ hàng → `add_to_cart` +- Xác nhận đơn hàng → `confirm_order` +- Tiến hành thanh toán → `checkout` +- Kiểm tra giỏ hàng hiện tại → `view_cart` +- Xóa sản phẩm khỏi giỏ hàng → `remove_item` +- Đặt hàng → `place_order` + +** Các câu hỏi về sản phẩm, xem sản phẩm không liên quan tới loại này +** Các từ hỏi về thông tin đơn hàng, tình trạng giao đơn hay hỏi về quá trình đơn không liên quan đến này + +**Từ khóa thường gặp:** +- **Động từ**: mua, buy, đặt, order, xác nhận, thanh toán, kiểm tra +- **Đối tượng**: sản phẩm, món, đồ, item, giỏ hàng, đơn hàng, order(Vui lòng kèm theo động từ ở trên) + +**Phân loại là `shopping_question` nếu**: +- Có ý định thực hiện hành động thương mại rõ ràng(tôi muốn mua, thêm sản phẩm, xóa sản phẩm, loại bỏ,..) +- Nhắc đến giỏ hàng +- Có động từ hành động và danh từ liên quan mua sắm +- **Các hành động liên quan đến việc xem thông tin, dữ liệu sản phẩm(xem sản phẩm trà chanh, thông tin về cà phê,...) không được tính là shopping_question`**. Quan trọng + +--- + +### 2. `normal_question` – Câu hỏi thông thường không liên quan đến giao dịch + +**Ví dụ**: +- Hỏi thông tin về thương hiệu, xuất xứ +- Hỏi về khuyến mãi, giờ mở cửa +- Tư vấn không đi kèm hành động mua cụ thể + +--- + +### 3. `unclear` – Không rõ ý định + +**Khi nào áp dụng?** +- Câu nói mơ hồ, không đủ thông tin để xác định +- Có thể là ý định mua nhưng chưa rõ ràng + +**Yêu cầu bổ sung nếu chọn `unclear`:** +- Đề xuất một câu hỏi gợi mở giúp làm rõ thêm ý định người dùng + *(Ví dụ: "Bạn muốn mình thêm món đó vào giỏ luôn không ạ?")* + +--- +##is_confirm_cart: Yes | No +Mô tả: Xác định xem người dùng có yêu cầu xác nhận hoặc đồng ý với nội dung giỏ hàng hiện tại hay không (ví dụ: "giỏ hàng của tôi đã đúng chưa?", "tôi xác nhận giỏ hàng này"). +Yes: Người dùng muốn xác nhận nội dung giỏ hàng. +No: Người dùng không xác nhận giỏ hàng (mà có thể thêm/xóa sản phẩm, xem giỏ, v.v.). +###is_deleted_cart: Yes | No +Mô tả: Xác định xem người dùng có yêu cầu xóa toàn bộ giỏ hàng hiện tại hay không, xóa toàn bộ sản phẩm đã thêm vào hay không +Yes: Người dùng muốn xóa giỏ hàng. +No: Người dùng không muốn xóa giỏ hàng. +###is_confirm_order: Yes | No +Mô tả: Xác định xem người dùng có ý định xác nhận, hoàn tất, hoặc tiến hành đặt/mua ngay lập tức một đơn hàng hoặc sản phẩm hay không. Đây là điểm mà bạn đã góp ý. +Yes: Nếu người dùng sử dụng các từ ngữ như "xác nhận đơn hàng", "đặt hàng", "mua luôn", "thanh toán", "quyết định mua", hoặc các câu lệnh rõ ràng thể hiện mong muốn hoàn tất giao dịch (chứ không chỉ dừng lại ở việc thêm vào giỏ hàng). Kể cả khi có yêu cầu "thêm vào giỏ" kèm theo một ý định mua sắm mạnh mẽ và hoàn tất (ví dụ: "thêm vào giỏ và mua luôn", "thêm vào giỏ, tôi quyết định mua sản phẩm này"), thì giá trị này vẫn là Yes. +No: Nếu người dùng chỉ muốn thêm sản phẩm vào giỏ hàng, xem giỏ hàng, xóa sản phẩm khỏi giỏ hàng, hoặc các hành động khác mà không đi kèm ý định hoàn tất giao dịch ngay lập tức. +###is_deleted_order: Yes| No +Mô tả: Xác định xem người dùng có yêu cầu hủy bỏ một đơn hàng đã được đặt hoặc đang trong quá trình xử lý hay không. +Yes: Người dùng muốn hủy đơn hàng. +No: Người dùng không muốn hủy đơn hàng. +###is_view_cart: Yes| No +Mô tả: Xác định xem người dùng có yêu cầu muốn xem thông tin về giỏ hàng, sản phẩm trong giỏ(Chỉ kèm theo khi có đề cập giỏ hàng, đơn hàng), sản phẫm nãy giờ nguời dùng đã thêm cập nhật vào giỏ +Yes: Người dùng muốn xem giỏ hàng, đơn hàng, danh sách sản phẩm nãy giờ mà người dùng đã thêm(Phải kiểm tra xem có kèm theo giỏ hàng, đơn hàng đồ không) . +No: Người dùng không muốn xem. + +### Phân tích bổ sung: +Ngoài phân loại chính, bạn cũng cần **trích xuất xem câu nói có chứa hành động nào sau đây không**: +- Tạo giỏ hàng mới +- Xóa giỏ hàng +- Xác nhận tạo đơn hàng +- Hủy đơn hàng +- Xem đơn hàng +- Nếu câu hỏi người dùng kết hợp nhiều ý ví dụ xem xong rồi xác nhận mua, xem xong rồi xóa thì hãy vui lòng đánh dấu thêm thứ tự thực hiện trong đó. + +--- + +### DỮ LIỆU ĐẦU VÀO + +**Trạng thái giỏ hàng hiện tại:** +{cart_status} + +**Lịch sử hội thoại gần nhất:** +\"\"\"\n{chat_history.strip()}\n\"\"\" + +**Câu hỏi hiện tại của người dùng:** +\"{user_question.strip()}\" + +--- + +### ĐỊNH DẠNG ĐẦU RA (JSON) + +```json +{json_example} +Hãy phân tích cẩn thận dựa trên ngữ cảnh trước khi đưa ra kết luận. +Luôn Đảm bảo trong câu trả lời luôn đảm bảo trong json trả về có 'question'. Tuyệt đối không được phép trả dữ liệu mà không có trường question. +Hãy xem xét kĩ xem câu hỏi người dùng có phải mua sắm hay không hay chỉ đơn thuần là câu hỏi khác nếu có hãy tách chúng ra. +""" + return prompt.strip() + +# data = build_detailed_user_intent_prompt("No","","Xem giỏ hàng của tôi") +# with open("user_intent_prompt.txt", "w", encoding="utf-8") as f: +# f.write(data) \ No newline at end of file diff --git a/function/advance_shopping/server_java/__pycache__/server_java.cpython-311.pyc b/function/advance_shopping/server_java/__pycache__/server_java.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80be4cac0257b9f91f6d5c4c8fa4a32b3edc8381 Binary files /dev/null and b/function/advance_shopping/server_java/__pycache__/server_java.cpython-311.pyc differ diff --git a/function/advance_shopping/server_java/config.yaml b/function/advance_shopping/server_java/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a1f4b69cb21c4a28042929664ce3a77f053af72f --- /dev/null +++ b/function/advance_shopping/server_java/config.yaml @@ -0,0 +1,53 @@ +frontend: + web_payment_base: "http://localhost:5173" + base: "http://localhost:5173" + web_redirect_paths: + zalo: "payment-online-status" + momo: "payment-online-status-momo" + payos: "payment-online-status-payos" + vnpay: "payment-online-status-vnpay" + android_redirect_base: "myapp://open/order-complete" + android_group_redirect_base: "myapp://open/order-group-complete" + + +callback_urls: + base: http://localhost:1010 + zalo: + single: + web: + callback: api/payment/zalo/callback + android: + callback: api/payment/zalo/callback + group: + android: + callback: api/payment-group/zalo/callback + + momo: + single: + web: + callback: api/payment/momo/callback + android: + callback: api/payment/momo/callback + group: + android: + callback: api/payment-group/momo/callback + + payos: + single: + web: + callback: api/payment/payOS/callback + android: + callback: api/payment/payOS/callback + group: + android: + callback: api/payment-group/payOS/callback + + vnpay: + single: + web: + callback: api/payment/vnpay_ipn + android: + callback: api/payment/vnpay_ipn + group: + android: + callback: api/payment-group/vnpay_ipn diff --git a/function/advance_shopping/server_java/server_java.py b/function/advance_shopping/server_java/server_java.py new file mode 100644 index 0000000000000000000000000000000000000000..3e595d3b47b0ac73c7520c16b757ab0084d2ef94 --- /dev/null +++ b/function/advance_shopping/server_java/server_java.py @@ -0,0 +1,338 @@ +import requests +import urllib.parse +import yaml +with open("config.yaml", "r") as f: + config = yaml.safe_load(f) +base_backend_java = config["callback_urls"]["base"] +base_frontend = config["frontend"]["base"] +api = base_backend_java +def search_product(keyword, page=1, limit=5, language="VN", size=None): + encoded_keyword = urllib.parse.quote(keyword) + print(api) + url = f"{api}/api/product/search?keyword={encoded_keyword}&page={page}&limit={limit}&language={language}" + print(url) + try: + response = requests.get(url) + response.raise_for_status() + data = response.json() + + # Lấy danh sách sản phẩm + products = data.get("productResponseList", []) + if not products: + print("⚠️ Không tìm thấy sản phẩm.") + return None + + # Lấy sản phẩm đầu tiên + first_product = products[0] + pro_id = first_product.get("proId") + pro_name = first_product.get("proName") + variants = first_product.get("listProductVariants", []) + + + + size_check = "" + stock_check = 0 + price_check = 0 + for v in variants: + if v['size'] == size: + size_check = v['size'] + stock_check = v['stock'] + price_check = v['price'] + break + # print(f" + Size: {v['size']}, Giá: {v['price']} VND, Tồn kho: {v['stock']}") + + return pro_id, pro_name, size_check, stock_check, price_check + + except requests.RequestException as e: + print(f"❌ Lỗi kết nối: {e}") + except ValueError: + print("❌ Lỗi parse JSON.") + + +def insert_cartItem(userId: int, cart_id: int, proId:int, size, quantity, token:str = "", language="VN"): + url = f"{api}/api/cart-item/insert" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + body = { + "userId": userId, + "cartId": cart_id, + "proId": proId, + "size": size, + "quantity": quantity, + "language": language + } + + try: + response = requests.post(url, json=body, headers=headers) + # response.raise_for_status() + if not response.ok: + print("❌ Lỗi HTTP", response.status_code) + try: + print("📦 Nội dung lỗi JSON:", response.json()) + except Exception: + print("📜 Nội dung lỗi (text):", response.text) + return None + return response.json() + + except requests.exceptions.RequestException as e: + print("Lỗi khi gọi API:", e) + return None + + +def update_cartItem_quantity(userId: int, cartItemId: int, quantity, token): + url = f"{api}/api/cart-item/update" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + body = { + "userId": userId, + "cartItemId": cartItemId, + "quantity": quantity, + } + response = requests.put(url, json=body, headers=headers) + try: + + # response.raise_for_status() + if not response.ok: + print("❌ Lỗi HTTP", response.status_code) + try: + print("📦 Nội dung lỗi JSON:", response.json()) + except Exception: + print("📜 Nội dung lỗi (text):", response.text) + return None + return response.json() + except requests.exceptions.RequestException as e: + print("Lỗi khi gọi API:", e) + return response.content + + +def update_cartItem_size(userId: int, cartItemId: int, size, token): + url = f"{api}/api/cart-item/change-size" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + body = { + "userId": userId, + "cartItemId": cartItemId, + "size": size, + } + try: + response = requests.put(url, json=body, headers=headers) + + if not response.ok: + print("❌ Lỗi HTTP", response.status_code) + try: + + return response.json() + except Exception: + return response.text + return response.json() + except requests.exceptions.RequestException as e: + print("Lỗi khi gọi API:", e) + + +def remove_one_cartItem( userId, cartItemId,token): + url = f"{api}/api/cart-item/delete/{cartItemId}" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + body = { + "userId": userId, + "cartItemId": cartItemId, + } + + try: + response = requests.delete(url, json=body, headers=headers) + response.raise_for_status() + if not response.ok: + print("❌ Lỗi HTTP", response.status_code) + try: + print("📦 Nội dung lỗi JSON:", response.json()) + return response.json() + except Exception: + + print("📜 Nội dung lỗi (text):", response.text) + return response.text + return None + return response.json() + except requests.exceptions.RequestException as e: + print("Lỗi khi gọi API:", e) + return None + + +def remove_all_cartItem(userId, cartId,token): + url = f"{api}/api/cart/delete-allItem/{cartId}" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + body = { + "userId": userId, + "cartId": cartId, + } + + try: + response = requests.delete(url, json=body, headers=headers) + if not response.ok: + print("❌ Lỗi HTTP", response.status_code) + try: + print("📦 Nội dung lỗi JSON:", response.json()) + return response.json() + except Exception: + + print("📜 Nội dung lỗi (text):", response.text) + return response.text + return response.json() + except requests.exceptions.RequestException as e: + print("Lỗi khi gọi API:", e) + return None + + +def create_orders(user_id, cartId, token, language="VN"): + url = f"{api}/api/orders/create" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + body = { + + "userId": user_id, + "cartId": cartId, + "voucherId": "string", + "pointCoinUse": 0, + "note": "string", + "language": language + + } + + try: + response = requests.post(url, json=body, headers=headers) + if not response.ok: + print("❌ Lỗi HTTP", response.status_code) + try: + print("📦 Nội dung lỗi JSON:", response.json()) + return response.json() + except Exception: + print("📜 Nội dung lỗi (text):", response.text) + return response.text + return response.json() + except requests.exceptions.RequestException as e: + print("Lỗi khi gọi API:", e) + return None + + +def confirm_orders_AI(user_id,token, orderId): + url = f"{api}/api/orders/confirm" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + body = { + + "userId": user_id, + "orderId": orderId, + + } + + try: + response = requests.post(url, json=body, headers=headers) + if not response.ok: + print("❌ Lỗi HTTP", response.status_code) + try: + print("📦 Nội dung lỗi JSON:", response.json()) + return response.json() + except Exception: + print("📜 Nội dung lỗi (text):", response.text) + return response.text + return response.json() + except requests.exceptions.RequestException as e: + print("Lỗi khi gọi API:", e) + return None + + +def confirm_cancel_AI(user_id,token, orderId): + url = f"{api}/api/orders/confirm-cancel-ai" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + body = { + + "userId": user_id, + "orderId": orderId, + + } + + try: + response = requests.post(url, json=body, headers=headers) + if not response.ok: + print("❌ Lỗi HTTP", response.status_code) + try: + print("📦 Nội dung lỗi JSON:", response.json()) + return response.json() + except Exception: + print("📜 Nội dung lỗi (text):", response.text) + return response.text + return response.json() + except requests.exceptions.RequestException as e: + print("Lỗi khi gọi API:", e) + return None + + +def create_cart(user_id, token): + print(token) + print(user_id) + url = f"{api}/api/cart/createAI" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + body = { + "userId": user_id + } + try: + response = requests.post(url, json=body, headers=headers) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + print("Lỗi khi gọi API:", e) + return None + + +# print(search_product(keyword="trà gừng",size="L")) + +# data = create_cart(4, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDg1NDE4ODgsImV4cCI6MTc0OTQ0MTg4OH0.6pEg1hdSDRvfAQK5YYJsQY_OfZPV9_EqqVvTGbHftvc") +# print(data) + +# data1 = insert_cartItem(4, 775, 38, "L", 2, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ") +# print(data1) + +# data2 = update_cartItem_quantity(4, 1226, 5, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ") +# print(data2) + +# data3 = update_cartItem_size(4, 1226, "K", "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ") +# print(data3) + +# data4 = remove_one_cartItem(4, 1226, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ") +# print(data4) + +# data5 = remove_all_cartItem(4, 773, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDgwNzAxNTgsImV4cCI6MTc0ODk3MDE1OH0.nGHuJ-eNocxtAhIFk-GD4nehKnQH8uSjyGsnqgVT3eQ") +# print(data5) + +# data6 = create_orders(4, 787, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDg1NDE4ODgsImV4cCI6MTc0OTQ0MTg4OH0.6pEg1hdSDRvfAQK5YYJsQY_OfZPV9_EqqVvTGbHftvc") +# print(data6) + +# data7 = confirm_orders_AI(4, "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDg1NDE4ODgsImV4cCI6MTc0OTQ0MTg4OH0.6pEg1hdSDRvfAQK5YYJsQY_OfZPV9_EqqVvTGbHftvc", 652) +# print(data7) \ No newline at end of file diff --git a/function/agent/__init__.py b/function/agent/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/function/agent/function_call.py b/function/agent/function_call.py new file mode 100644 index 0000000000000000000000000000000000000000..6229cf9029215620964788c06fc1d155e53048c1 --- /dev/null +++ b/function/agent/function_call.py @@ -0,0 +1,190 @@ +from langchain_core.tools import tool +from langchain_openai import ChatOpenAI +import os +import os,sys +BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) +sys.path.insert(0, BASE_DIR) +import function.gemini_response.filter_query_internal as filter_query + + +#Test OpenAI +os.environ["OPENAI_API_KEY"] = "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" +llm = ChatOpenAI(model="gpt-4.1") + +#Test google +os.environ["GOOGLE_API_KEY"] = "AIzaSyB_7ahCuAOZU0UKcON_A00ya5breHTEgQM" +from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI +llm1 = ChatGoogleGenerativeAI(model='gemini-2.5-flash-preview-05-20',temperature=0.6) + +from langchain_core.messages import HumanMessage, ToolMessage + + +@tool +def question_information(query: str) -> str: + """ + Trả lời các câu hỏi về thông tin liên hệ của cửa hàng, thông tin về sinh viên, tên đề tài các nội dung liên quan mà không phải sql + + Hàm này chỉ phục vụ mục đích cung cấp thông tin liên lạc của cửa hàng, bao gồm: + - Số điện thoại liên hệ. + - Địa chỉ cửa hàng. + - Giờ làm việc. + - Email hỗ trợ. + - Các kênh liên hệ khác như Facebook, Zalo, v.v. + + ⚠️ Lưu ý: + - Hàm này KHÔNG trả lời các câu hỏi khác như: gợi ý món ăn, tư vấn sản phẩm, giá cả, chương trình khuyến mãi, cách pha chế đồ uống, v.v. + - Nếu truy vấn không liên quan đến thông tin cửa hàng, hàm sẽ từ chối trả lời. + + Args: + query (str): Câu hỏi của người dùng liên quan đến thông tin liên hệ của cửa hàng. bao gồm: + - Số điện thoại liên hệ. + - Địa chỉ cửa hàng. + - Giờ làm việc. + - Email hỗ trợ. + - Các kênh liên hệ khác như Facebook, Zalo, v.v. + + Returns: + str: Thông tin liên hệ của cửa hàng hoặc thông báo từ chối nếu câu hỏi không phù hợp. Xác định rõ câu hỏi không viết gọn + """ + return query + + +@tool(description="Vui lòng giữ đúng truy vấn người dùng không tạo ra câu truy vấn khác ý nghĩa câu hỏi người dùng định hỏi, (**yêu cầu phải bám sát không được tự sinh câu hỏi)") +def question_sql(query: str)->str: + """ + Hỗ trợ truy xuất schema của database để giúp tạo truy vấn SQL cho các bảng sau: + + - **Giỏ hàng (cart, cart_item)** + - **Danh mục sản phẩm(tên sản phẩm) (category, category_translation)** + - **Liên hệ và yêu thích (contact, favourite, favourite_item)** + - **Thông báo (notification)** + - **Đơn hàng (orders, order_item, shipment)** + - **Thanh toán (otp, payments)** + - **Bài viết & tin tức (post, post_translation)** + - **Lịch sử giá (price_history)** + - **Sản phẩm (product, product_translation, product_variants)** + - **Đánh giá sản phẩm (review)** + - **Người dùng (user, user_coin, token)** + - **Mã giảm giá & phiếu giảm giá (voucher, user_voucher)** + - **Xác nhận về mua/xem, xóa giỏ hàng, đơn hàng**(Hãy lưu ý những câu hỏi về này) + + ⚠ **Lưu ý**: + - Chỉ hỗ trợ truy vấn liên quan đến các bảng trong database. + - Không trả lời các câu hỏi về thông tin cửa hàng như số điện thoại, email, giờ làm việc. + + Args: + query (str): Câu hỏi liên quan đến việc truy vấn dữ liệu từ database. + + Returns: + str: Mô tả bảng tương ứng hoặc thông báo từ chối nếu câu hỏi không phù hợp. + """ + return query + + +@tool +def question_general(query: str) -> str: + """ + Xử lý các câu hỏi không liên quan đến các công cụ có sẵn. + + Hàm này nhận vào một câu hỏi dưới dạng chuỗi và trả về câu hỏi đó + mà không thực hiện bất kỳ xử lý nào khác. Nó có thể được sử dụng như + một phương án dự phòng khi không có tool nào phù hợp để xử lý câu hỏi. + + Args: + query (str): Câu hỏi đầu vào từ người dùng. + + Returns: + str: Trả về chính câu hỏi mà không thay đổi. + """ + return query + + + + +@tool(description="""ác định xem truy vấn có phải là câu chào hỏi hay không. + Câu chào hỏi bao gồm các từ hoặc cụm từ thể hiện sự chào đón, bắt đầu cuộc trò chuyện, chẳng hạn như: + - "Xin chào", "Chào bạn", "Hello", "Hi", "Hey", "Good morning", "Good evening", ... + - Cụm từ mang ý nghĩa xã giao: "Rất vui được gặp bạn", "Hôm nay bạn thế nào?", "Chúc một ngày tốt lành", ... + + Lưu ý: + - Chỉ xử lý các câu hỏi thuộc nhóm chào hỏi. + - Không liên quan đến **question_sql** hoặc các câu hỏi về dữ liệu. + + Args: + query (str): Câu hỏi đầu vào của người dùng. + + Returns: + str: Trả về câu hỏi nếu nó thuộc nhóm chào hỏi, ngược lại có thể xử lý theo yêu cầu cụ thể.""") +def question_hello(query: str)->str: + """ + Xác định xem truy vấn có phải là câu chào hỏi hay không. + Câu chào hỏi bao gồm các từ hoặc cụm từ thể hiện sự chào đón, bắt đầu cuộc trò chuyện, chẳng hạn như: + - "Xin chào", "Chào bạn", "Hello", "Hi", "Hey", "Good morning", "Good evening", ... + - Cụm từ mang ý nghĩa xã giao: "Rất vui được gặp bạn", "Hôm nay bạn thế nào?", "Chúc một ngày tốt lành", ... + + Lưu ý: + - Chỉ xử lý các câu hỏi thuộc nhóm chào hỏi. + - Không liên quan đến **question_sql** hoặc các câu hỏi về dữ liệu. + + Args: + query (str): Câu hỏi đầu vào của người dùng. + + Returns: + str: Trả về câu hỏi nếu nó thuộc nhóm chào hỏi, ngược lại có thể xử lý theo yêu cầu cụ thể. + """ + return query + + +import asyncio +import importlib +import function.chat as chat_module +async def update_tool_calls(tool_calls, chat_id,user_id,user_input): + + updated_calls = [] + + for tool in tool_calls: + query = tool['args']['query'] + list_history = await chat_module.get_chat_details_text(chat_id, user_id) + new_question = await filter_query.response_rename_question(user_input, list_history, query) + tool['args']['query'] = new_question + new_name = await filter_query.response_general(new_question, list_history) + if new_name is not None and new_name == "question_sql": + tool['name'] = new_name.strip() + updated_calls.append(tool) + if new_name == "question_general": + tool['name'] = new_name.strip() + updated_calls.append(tool) + return updated_calls + else: + return tool_calls + + return updated_calls + + +async def process_user_query(user_input, user_id, role, language,chat_id): + always_call_tool_llm = llm.bind_tools([question_information, question_sql, question_hello], tool_choice=True,strict=True) + tool_calls = always_call_tool_llm.invoke(f"{user_input}").tool_calls + + for tc in tool_calls: + tc["user_id"] = user_id + tc["role"] = role + tc["language"] = language + print("Tool gốc:",tool_calls) + data = await update_tool_calls(tool_calls,chat_id,user_id,user_input) + + for item in data: + item['chat_id'] = chat_id + print("Tool update: ", data) + return data + + +# import asyncio + +async def main(): + await process_user_query("Tôi muốn thêm trà chanh size L vào giỏ hàng", 4, "CUSTOMER", "VN", "67d2fc6607b51a01e3beb501") + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(main()) + loop.close() \ No newline at end of file diff --git a/function/agent/pipeline_agent.py b/function/agent/pipeline_agent.py new file mode 100644 index 0000000000000000000000000000000000000000..1d466b8c60ac616d1a78952b273eee42d0580f03 --- /dev/null +++ b/function/agent/pipeline_agent.py @@ -0,0 +1,136 @@ +import os,sys +BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) +sys.path.insert(0, BASE_DIR) +from function.file import extract_file as extract_file +import function.agent.function_call as fc +import function.function_agent.multi_agent as multi_agent +import function.function_agent.map_toolcall as map_toolcall +import function.gemini_response.response_all as response_all +from models.Database_Entity import StopSignal +from advance_shopping.server_java import server_java +async def check_should_stop(chat_id: str, stop_event: object = None): + # Trường hợp dừng qua RAM (in-memory) + await asyncio.sleep(0.1) + if stop_event and stop_event.is_set(): + print("🛑 Dừng qua stop_event.") + return {"status": "cancelled"} + + # Trường hợp dừng qua MongoDB + await asyncio.sleep(0.1) + if StopSignal.objects(chat_history=chat_id, is_stopped=True).first(): + print("🛑 Dừng vì có StopSignal trong DB.") + return {"status": "cancelled"} + + return None # Không bị dừng + + +async def rollback_cart_if_stopped(stop_event, result_multi_task, chat_id: str, user_id: int, token: str): + if stop_event and stop_event.is_set(): + for task_id, task_result in result_multi_task.items(): + if task_result.get("is_shopping"): + print(f"INFO: Bắt đầu rollback task mua sắm: {task_id}") + chat_cart = ChatCart() + latest_unconfirmed_cart = ChatCart.objects(chat_history=chat_id).order_by('-created_at').first() + if latest_unconfirmed_cart: + chat_cart = latest_unconfirmed_cart + + # Khôi phục trạng thái backup + chat_cart.cart_products = chat_cart.backup_cart_products + chat_cart.status = chat_cart.backup_status + chat_cart.confirmed_order = chat_cart.backup_confirmed_order + + chat_cart.save() + cart_id = chat_cart.cart_id + order_id = chat_cart.order_id + + if order_id: + try: + server_java.confirm_cancel_AI(int(user_id), token, order_id) + print(f"✅ Đã huỷ đơn hàng: {order_id}") + except Exception as e: + print(f"ERROR khi huỷ đơn hàng {order_id}: {e}") + if cart_id: + try: + server_java.remove_all_cartItem(int(user_id), cart_id, token) + print(f"✅ Đã xoá item trong giỏ hàng: {cart_id}") + except Exception as e: + print(f"ERROR khi xoá cart item {cart_id}: {e}") + + # Reset các ID để tránh nhầm lẫn về sau + chat_cart.order_id = None + chat_cart.cart_id = None + chat_cart.save() + + else: + print(f"Task {task_id} không phải mua sắm, không cần rollback.") + + +from typing import Optional +import asyncio +from models.Database_Entity import StopSignal,ChatCart +async def multi_query_user( + user_input: str, + user_id: int, + role, + languages: str, + chat_id, + token, + stop_event: Optional[asyncio.Event] = None +) -> str: + + chat_cart = ChatCart() + latest_unconfirmed_cart = ChatCart.objects(chat_history=chat_id,status ="pending").order_by('-created_at').first() + if latest_unconfirmed_cart: + chat_cart = latest_unconfirmed_cart + chat_cart.backup_cart_products = chat_cart.cart_products + chat_cart.backup_status = chat_cart.status + chat_cart.backup_confirmed_order = chat_cart.confirmed_order + chat_cart.save() + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + multi_tasks = await fc.process_user_query(user_input, user_id, role, languages, chat_id) + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + result_multi_task = await multi_agent.process_toolcalls_with_order(multi_tasks, token, stop_event,chat_id) + chat_cart = ChatCart() + if stop_event and stop_event.is_set(): + await rollback_cart_if_stopped(stop_event, result_multi_task, chat_id, user_id, token) + + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + await rollback_cart_if_stopped(stop_event, result_multi_task, chat_id, user_id, token) + return result_check + + map_tool_result = map_toolcall.map_toolcalls_with_results(multi_tasks, result_multi_task) + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + await rollback_cart_if_stopped(stop_event, result_multi_task, chat_id, user_id, token) + return result_check + + result = await response_all.response_all(user_input, map_tool_result) + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + await rollback_cart_if_stopped(stop_event, result_multi_task, chat_id, user_id, token) + return result_check + + return result if result is not None else {"status": "empty"} + + + +# import asyncio +# # if __name__ == "__main__": +# # token = "eyJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOiI0IiwiUm9sZXMiOiJDVVNUT01FUiIsInNlc3Npb25JZCI6ImJkM2M5MmNkLTczMzgtNDlmNS04NzIyLTUwMDU0Zjk5MjRhZSIsInN1YiI6ImxpbmgiLCJpYXQiOjE3NDg3NjcwNDUsImV4cCI6MTc0OTY2NzA0NX0.X4d4GcstDhaVC1nbPWWkN3sUEHNUltDtD5KgP_mzPrM" +# # result = asyncio.run(multi_query_user("Tôi muốn thêm sản phẩm Yogurt Đào size S vào giỏ hàng và tôi quyết định mua sản phẩm này", 4 , "CUSTOMER", "VN","67d009fee0a638763885c195",token=token)) +# # print(result) +# if __name__ == "__main__": +# result = asyncio.run(multi_query_user("Tôi muốn thêm trà chanh size L vào giỏ", 4, "CUSTOMER", "ADMIN","67d2fc6607b51a01e3beb501","")) +# print(result) \ No newline at end of file diff --git a/function/analyze/__init__.py b/function/analyze/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/function/analyze/__pycache__/__init__.cpython-311.pyc b/function/analyze/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a285bc8af88bcc820442afea4454498e3e684a9 Binary files /dev/null and b/function/analyze/__pycache__/__init__.cpython-311.pyc differ diff --git a/function/analyze/__pycache__/execute_query.cpython-311.pyc b/function/analyze/__pycache__/execute_query.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71a19a7ebd5cc52d15a2d52e606eed202c4b878c Binary files /dev/null and b/function/analyze/__pycache__/execute_query.cpython-311.pyc differ diff --git a/function/analyze/__pycache__/main.cpython-311.pyc b/function/analyze/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cc4ca525b067fd5a768466a2b45f5dec20c86fc Binary files /dev/null and b/function/analyze/__pycache__/main.cpython-311.pyc differ diff --git a/function/analyze/__pycache__/merge_output.cpython-311.pyc b/function/analyze/__pycache__/merge_output.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3138355bf0bd3dd02794dfc4edf92d346db7adf Binary files /dev/null and b/function/analyze/__pycache__/merge_output.cpython-311.pyc differ diff --git a/function/analyze/__pycache__/pipeline.cpython-311.pyc b/function/analyze/__pycache__/pipeline.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a86c57981dd6043cb3883e09da46134dc767c8bf Binary files /dev/null and b/function/analyze/__pycache__/pipeline.cpython-311.pyc differ diff --git a/function/analyze/__pycache__/prompt_databse.cpython-311.pyc b/function/analyze/__pycache__/prompt_databse.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f540f89986efc5fee22214210a585708124faf2 Binary files /dev/null and b/function/analyze/__pycache__/prompt_databse.cpython-311.pyc differ diff --git a/function/analyze/__pycache__/prompt_query.cpython-311.pyc b/function/analyze/__pycache__/prompt_query.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7976224663079c25b366059cdef2d7154df0813c Binary files /dev/null and b/function/analyze/__pycache__/prompt_query.cpython-311.pyc differ diff --git a/function/analyze/__pycache__/return_code_python.cpython-311.pyc b/function/analyze/__pycache__/return_code_python.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..018d052204cc0246bd7e467728ccbeb82a657817 Binary files /dev/null and b/function/analyze/__pycache__/return_code_python.cpython-311.pyc differ diff --git a/function/analyze/execute_query.py b/function/analyze/execute_query.py new file mode 100644 index 0000000000000000000000000000000000000000..f76688d97d1d90eeac565b2436e567fc09f8e3bb --- /dev/null +++ b/function/analyze/execute_query.py @@ -0,0 +1,394 @@ +import sys +import os + +# Thêm thư mục gốc (chứa thư mục "function") vào sys.path +sys.path.append(os.path.abspath(os.path.join(os.getcwd(), ".."))) +import os +from dotenv import load_dotenv + +# Load biến môi trường từ file .env +load_dotenv() + +DB_HOST = os.getenv("DB_HOST") +DB_USER = os.getenv("DB_USER") +DB_PASSWORD = os.getenv("DB_PASSWORD") +DB_NAME = os.getenv("DB_NAME") +DB_PORT = os.getenv("DB_PORT") + +import os +from urllib.parse import quote + +password = os.getenv("DB_PASSWORD") # VD: 'Yahana0509@' +DB_PASSWORD = quote(password) +# Tạo connection string +connection_uri = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" +from langchain_community.utilities.sql_database import SQLDatabase +from langchain_experimental.sql import SQLDatabaseChain +import sys +import os +import pymysql +from fastapi import HTTPException +import sys +import os + + +from fastapi.encoders import jsonable_encoder +import re +from function.prompt import prompt_main as prompt +import function.prompt.prompt_custom as prompt_cus +os.environ["GOOGLE_API_KEY"] = "AIzaSyCO-RlqYewC4e9BEPoC8m-AxHUY7J3_o2E" +from bson import ObjectId +db = SQLDatabase.from_uri(connection_uri) +from dotenv import load_dotenv +import filter.filter_role as filter_role_1 +import filter.filter_sql_injection as filter_sql_injection_1 +import filter.result as query_result_1 +import response.ResponseChat as res_chat +from datetime import datetime +import pytz +from mongoengine import connect +import os +import nltk +import function.agent.pipeline_agent as pipeline_agent +nltk.download('punkt') +from models.Database_Entity import User, ChatHistory, DetailChat +from dotenv import load_dotenv +load_dotenv() +MONGO_URI = os.getenv("MONGO_URI", "") +connect("chatbot_hmdrinks", host=MONGO_URI) +import re + +def contains_delete(sql: str) -> bool: + return bool(re.search(r'\bdelete\b', sql, re.IGNORECASE)) +load_dotenv() + +#setup model +from bson import ObjectId +import random +from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI +llm1 = ChatGoogleGenerativeAI(model='gemini-2.5-flash-preview-05-20',temperature=0.5) + +#setup kết nối database +db = SQLDatabase.from_uri(connection_uri) +db_chain = SQLDatabaseChain.from_llm(llm=llm1,db=db,prompt= prompt.PROMPT) + +from prompt.prompt_syntax_insert import is_insert_related_to_product_category_variant, filter_syntax_sql +import sqlparse + + +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.getcwd(), '..'))) +from prompt import prompt_detail_table + +schema_mapping = { + "payments": prompt_detail_table.prompt_payments, + "user": prompt_detail_table.prompt_users, + "user_voucher": prompt_detail_table.prompt_user_voucher, + "category": prompt_detail_table.prompt_categort, + "category_translation": prompt_detail_table.prompt_category_translation, + "cart": prompt_detail_table.prompt_cart, + "cart_item": prompt_detail_table.prompt_cart_item, + "orders":prompt_detail_table.prompt_orders, + "order_item": prompt_detail_table.prompt_order_item, + "payment": prompt_detail_table.prompt_payments, + "favourite": prompt_detail_table.prompt_favourite, + "favourite_item": prompt_detail_table.prompt_fav_item, + "post": prompt_detail_table.prompt_post, + "post_translation": prompt_detail_table.prompt_post_translation, + "product": prompt_detail_table.prompt_product, + "product_translation": prompt_detail_table.prompt_product_translation, + "shipment": prompt_detail_table.prompt_shipment, + "product_variants": prompt_detail_table.prompt_product_variants, + "review": prompt_detail_table.prompt_review, + "user_coin": prompt_detail_table.prompt_user_coin, + "absence": prompt_detail_table.prompt_absence, + "cart_group": prompt_detail_table.prompt_cart_group, + "cart_item_group": prompt_detail_table.prompt_cartitem_group, + "group_orders": prompt_detail_table.prompt_group_orders, + "payments_group": prompt_detail_table.prompt_payments_group, + "group_order_members":prompt_detail_table.prompt_group_orders_member, + "shipment_group":prompt_detail_table.prompt_shipment_group, + "shipper_attendance":prompt_detail_table.prompt_shipper_attendance, + "shipper_commission_detail":prompt_detail_table.prompt_shipper_commission_detail, + "shipper_salary_summary":prompt_detail_table.prompt_shipper_salary_summary, + "voucher": prompt_detail_table.prompt_voucher + } + + +def get_schemas_from_sql(sql_query: str, schema_mapping: dict): + import sqlglot + print("SQL query:", sql_query) + parsed_query = sqlglot.parse_one(sql_query, read="mysql") + + # Lấy danh sách bảng duy nhất trong query + table_names = list({t.name for t in parsed_query.find_all(sqlglot.exp.Table)}) + + schemas_used = {} + for table in table_names: + if table in schema_mapping: + schemas_used[table] = schema_mapping[table] + else: + print(f"⚠️ Warning: Table '{table}' not found in schema_mapping") + + # Gom toàn bộ schema thành 1 chuỗi duy nhất + all_schemas = "\n\n".join( + [f"Schema for table '{table}':\n{schemas_used[table]}" for table in schemas_used] + ) + + return all_schemas + + +def build_sql_fix_prompt(schemas_result: dict, sql: str,user_id: int) -> str: + + prompt = f""" +Bạn là một chuyên gia cơ sở dữ liệu. + +Dưới đây là mô tả schema chi tiết của các bảng có trong hệ thống: + +{schemas_result} + +--- + +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: + +```sql +{sql.strip()} +Yêu cầu của bạn là: +User Id: {user_id} +Dựa trên các schema ở trên, hãy kiểm tra và chỉnh sửa câu SQL sao cho: +Tên bảng, tên cột phải chính xác theo schema. +Logic và mục đích của truy vấn được giữ nguyên. +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). +Loại bỏ các câu comment chỉ trả lại câu SQL chính xác sau khi bạn đã chỉnh sửa. +Vui lòng chỉnh sửa một cách chính xác nhất. +Trả lời dưới dạng một truy vấn SQL duy nhất. +** Tham khảo thêm các lỗi sau để tránh: + - "- Tránh các lỗi như :\n" + " (1054, \"Unknown column 'oi.pro_id' in 'field list'\")\n . Luôn đảm bảo bạn không bao giờ bị lỗi này" + " (1054, \"Unknown column 'oi.note' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này\n" + " (1054, \"Unknown column 'oi.size' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này \n" + " (1054, \"Unknown column 'c.is_deleted' in 'on clause'\"). Luôn đảm bảo bạn không bao giờ bị lỗi này\n" +""" + return prompt + + + + +async def execute_query_user(user_input: str, user_id: int, languages: str, role: str): + PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_input) + check_insert = is_insert_related_to_product_category_variant(user_input) + + db_config = { + "host": os.getenv("DB_HOST"), + "user": os.getenv("DB_USER"), + "database": os.getenv("DB_NAME"), + "password": os.getenv("DB_PASSWORD"), + "port": int(os.getenv("DB_PORT", 3306)), + "charset": "utf8mb4", + "cursorclass": pymysql.cursors.DictCursor, + } + + + def regenerate_sql_until_safe(): + max_retry = 5 + retry_count = 0 + + while retry_count < max_retry: + try: + regenerated_data = db_chain.run(f""" + Role: {text_role} + Language: {languages} + Question: {user_input}. + """) + regenerated_sql = extract_sql_from_response(regenerated_data) + if regenerated_sql: + regenerated_sql = clean_sql(regenerated_sql) + if not re.search(r"%{1,2}s", regenerated_sql): # đã sạch + return regenerated_sql + retry_count += 1 + except Exception as e: + return f"❌ Lỗi khi tạo lại truy vấn lần {retry_count + 1}: {str(e)}" + + return "❌ Lỗi: Không thể tạo được truy vấn an toàn sau nhiều lần thử." + + + def execute_query_with_pymysql(query, multi=False): + connection = pymysql.connect(**db_config) + try: + with connection.cursor() as cursor: + results = [] + if multi: + # Dùng sqlparse để chia câu truy vấn cho an toàn + statements = sqlparse.split(query) + for stmt in statements: + stmt = stmt.strip() + if stmt: + cursor.execute(stmt) + results.append(cursor.fetchall()) + else: + cursor.execute(query) + results = cursor.fetchall() + connection.commit() + return results + except pymysql.MySQLError as e: + return str(e) + finally: + connection.close() + + def clean_sql(sql) -> str: + print("Clean sql1:", sql) + if isinstance(sql, dict) and sql: + first_value = next(iter(sql.values())) + sql = first_value + sql = re.sub(r"```sql", "", sql, flags=re.IGNORECASE) + sql = sql.replace("```sql", "") + sql = re.sub(r"```", "", sql) + return sql.strip() + # sql = re.sub(r"```sql", "", sql, flags=re.IGNORECASE) + # sql = sql.replace("```sql", "") + # # sql = re.sub(r'%%s', r'%s', sql) + # sql = re.sub(r"```", "", sql) + # return sql.strip() + + def extract_sql_from_response(data): + match = re.search(r"\[SQL:\s*```sql\s*(.*?)\s*```]", data, re.DOTALL) + if match: + return clean_sql(match.group(1)) + match = re.search(r"SQLQuery:\s*(.*)", data, re.DOTALL) + if match: + return clean_sql(match.group(1)) + + return None + + def extract_sql_from_error(error_msg): + # Case 1: [SQL: ```sql ... ```] + match = re.search(r"\[SQL:\s*```sql\s*(.*?)\s*```]", error_msg, re.DOTALL) + if match: + return clean_sql(match.group(1)) + + + match = re.search(r"```(?:sql)?\s*\r?\n(.*?)```", error_msg, re.DOTALL) + if match: + return clean_sql(match.group(1)) + + return None + + def process_and_execute_sql(sql): + print("NHận sql: ",sql ) + data = sql + if isinstance(data, dict) and data: + print("Là dict") + first_key, first_value = next(iter(data.items())) + sql = first_value + elif isinstance(data, list) and data: + sql = "\n\n".join(data) + + sql_clean = clean_sql(sql) + print("SQL Clean: ", sql_clean) + if re.search(r"%{1,2}s", sql_clean): + regenerated_sql = regenerate_sql_until_safe() + sql_clean = clean_sql(regenerated_sql) + result = get_schemas_from_sql(sql_clean, schema_mapping) + prompt = build_sql_fix_prompt(schemas_result=result,sql = str(sql_clean),user_id =user_id) + from advance_shopping.call_gemini import tool_call + data = tool_call.generate(prompt = prompt) + sql_clean = clean_sql(data) + print("SQL step2: ", sql_clean) + if contains_delete(sql_clean): + 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." + + if re.search(r"\bIF\b.*\bTHEN\b", sql_clean, re.IGNORECASE): + return "❌ Lỗi: Không được dùng IF...THEN trong SQL. Vui lòng chia nhỏ truy vấn." + + if check_insert: + check_syntax = filter_syntax_sql(sql_clean, PROMPT_CUSTOM, user_input) + if check_syntax is True: + # Tách từng câu và thực thi tuần tự + try: + connection = pymysql.connect(**db_config) + with connection.cursor() as cursor: + statements = sqlparse.split(sql_clean) + results = [] + for stmt in statements: + stmt = stmt.strip() + if stmt: + cursor.execute(stmt) + try: + results.append(cursor.fetchall()) + except: + results.append("✅ OK") # Có thể là câu SET hoặc INSERT + connection.commit() + return results + except Exception as e: + return f"❌ Lỗi khi thực thi từng truy vấn: {str(e)}" + finally: + connection.close() + else: + try: + regenerated_data = db_chain.run(f""" + Role: {text_role} + Language: {languages} + Question: {user_input}. + """) + regenerated_sql = extract_sql_from_response(regenerated_data) + if regenerated_sql: + return process_and_execute_sql(regenerated_sql) + else: + return "❌ Lỗi: Không thể tạo lại truy vấn hợp lệ." + except Exception as regen_error: + return f"❌ Lỗi khi tạo lại truy vấn: {str(regen_error)}" + else: + return execute_query_with_pymysql(sql_clean, multi=True) + + + + PROMPT_CUSTOM.template += ( + "\n\n⚠️ Lưu ý: Do NOT use IF...THEN...ELSE...END in SQL. " + "Only use plain SELECT, INSERT, UPDATE, JOIN, SET statements. " + "Tuyệt đối cấm dùng %%s hoặc %s để lấy giá trị từ biến. " + "SQL phải chứa giá trị cố định, không dùng placeholder hay biến bind." + ) + + db_chain = SQLDatabaseChain.from_llm(llm=llm1, db=db, prompt=PROMPT_CUSTOM) + text_role = f"{role} (userId = {user_id})" if role == "ADMIN" else f"{role} (userId = {user_id}), not role ADMIN" + + try: + data = db_chain.run(f""" + Role: {text_role} + Language: {languages} + Question: {user_input}. + """) + extracted_sql = extract_sql_from_response(data) + if extracted_sql: + return process_and_execute_sql(extracted_sql) + else: + return data + + except Exception as e: + error_message = str(e) + extracted_sql = extract_sql_from_error(error_message) + + if extracted_sql: + fix_sql = re.sub(r"```sql", "", extracted_sql) + fix_sql = extracted_sql.replace("```", "") + fix_sql = extracted_sql.replace("```sql", "") + # fix_sql = re.sub(r'%%s', r'%s', fix_sql) + if contains_delete(fix_sql): + 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." + return process_and_execute_sql(fix_sql) + else: + # Không trích xuất được SQL -> Thử tạo lại truy vấn từ đầu + try: + regenerated_data = db_chain.run(f""" + Role: {text_role} + Language: {languages} + Question: {user_input}. + """) + regenerated_sql = extract_sql_from_response(regenerated_data) + if regenerated_sql: + return process_and_execute_sql(regenerated_sql) + else: + return "❌ Lỗi: Không thể tạo lại truy vấn hợp lệ sau lỗi." + except Exception as regen_error: + return f"❌ Lỗi khi tạo lại truy vấn từ lỗi: {str(regen_error)}" diff --git a/function/analyze/gemini/__init__.py b/function/analyze/gemini/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/function/analyze/gemini/__pycache__/__init__.cpython-311.pyc b/function/analyze/gemini/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..712c8c0dc90517fff6452730f7605dba09442cd7 Binary files /dev/null and b/function/analyze/gemini/__pycache__/__init__.cpython-311.pyc differ diff --git a/function/analyze/gemini/__pycache__/result_analyze.cpython-311.pyc b/function/analyze/gemini/__pycache__/result_analyze.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92e8e6a4d813b6fd9d080ab7f2cbd24696d397cd Binary files /dev/null and b/function/analyze/gemini/__pycache__/result_analyze.cpython-311.pyc differ diff --git a/function/analyze/gemini/__pycache__/result_sql.cpython-311.pyc b/function/analyze/gemini/__pycache__/result_sql.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89b8c61c49f2dce147a8d16226d14647b197e318 Binary files /dev/null and b/function/analyze/gemini/__pycache__/result_sql.cpython-311.pyc differ diff --git a/function/analyze/gemini/result_analyze.py b/function/analyze/gemini/result_analyze.py new file mode 100644 index 0000000000000000000000000000000000000000..11447a3b895de5df7e7caa8fd29c71d30bbe9c2a --- /dev/null +++ b/function/analyze/gemini/result_analyze.py @@ -0,0 +1,48 @@ +import os +from google import genai +from google.genai import types + +def generate(image_folder, question): + client = genai.Client(api_key="AIzaSyBoPdVLTJNxfDg9wxWDpY4QJezHiyjKbTE") + + # Lọc các file ảnh + image_files = [f for f in os.listdir(image_folder) if f.lower().endswith((".png", ".jpg", ".jpeg"))] + files = [] + for filename in image_files: + full_path = os.path.join(image_folder, filename) + uploaded_file = client.files.upload(file=full_path) + files.append(uploaded_file) + + # Tạo danh sách các Part từ ảnh + image_parts = [ + types.Part.from_uri(file_uri=f.uri, mime_type=f.mime_type) for f in files + ] + + # Prompt hướng dẫn rõ ràng hơn + prompt_text = ( + "Bạn là một chuyên gia phân tích dữ liệu có 5 năm kinh nghiệm và có khả năng quan sát, diễn giải dữ liệu dưới dạng biểu đồ một cách chính xác, sắc sảo và có chiều sâu. " + "Hãy phân tích kỹ lưỡng **từng biểu đồ** dưới đây như thể đang viết một báo cáo phân tích chuyên nghiệp cho khách hàng/ban lãnh đạo. " + "Tuyệt đối **không bỏ qua biểu đồ nào**, và **không được viện lý do thiếu dữ liệu** – mỗi hình ảnh đều mang thông tin và cần được khai thác tối đa. " + "Phân tích phải rõ ràng, mạch lạc, giải thích được **xu hướng chính, điểm bất thường, mối quan hệ giữa các danh mục**, và đưa ra nhận định hoặc giả thuyết hợp lý cho từng hiện tượng được thấy từ dữ liệu. " + "Nếu có nhiều biểu đồ thể hiện cùng một khía cạnh, hãy so sánh chúng để rút ra kết luận sâu hơn. " + f"Yêu cầu phân tích: {question}" + ) + + image_parts.append(types.Part.from_text(text=prompt_text)) + + contents = [ + types.Content(role="user", parts=image_parts), + types.Content(role="user", parts=[ + types.Part.from_text(text="Hãy thực hiện phân tích theo yêu cầu trên một cách đầy đủ, rõ ràng và có cấu trúc.") + ]), + ] + + config = types.GenerateContentConfig(response_mime_type="text/plain") + + response = client.models.generate_content( + model="gemini-2.5-flash-lite-preview-06-17", + contents=contents, + config=config, + ) + + return response.text diff --git a/function/analyze/gemini/result_sql.py b/function/analyze/gemini/result_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..ddbe9b8b3ebfbe971ca3085dacaa3d1581064d96 --- /dev/null +++ b/function/analyze/gemini/result_sql.py @@ -0,0 +1,33 @@ +import os + +import google.generativeai as genai +genai.configure(api_key="AIzaSyD2hh16UhUIX7AbtW52_7o4VP7x5ieh99U") +def generate_step( query): + generation_config = { + "temperature": 0.7, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 12000, + "response_mime_type": "text/plain", + } + + model = genai.GenerativeModel( + model_name="gemini-2.0-flash-thinking-exp-01-21", + generation_config=generation_config, + ) + + chat_session = model.start_chat( + history=[ + { + "role": "user", + "parts": [ + f""" +{query} +""" + ], + } + ] + ) + + response = chat_session.send_message("Hãy thực hiện theo đúng yêu cầu của mình. Trình bày rõ ràng các yêu cầu lấy dữ liệu để phân tích trong thẻ và các bước thực hiện trong thẻ . Hãy trình bày rõ ràng các yêu cầu lấy dữ liệu theo cấu trúc mẫu sau:") + return response.text.strip() diff --git a/function/analyze/main.py b/function/analyze/main.py new file mode 100644 index 0000000000000000000000000000000000000000..c4266bf331f50424266a1a430962002278b95850 --- /dev/null +++ b/function/analyze/main.py @@ -0,0 +1,103 @@ +import asyncio +import os +import uuid +import py_compile + +from .execute_query import execute_query_user +from .pipeline import run_pipeline +from .merge_output import parse_step_data_blocks +from .prompt_databse import prompt_user as prompt_user1 +from .prompt_query import query as prompt_query1 +from .return_code_python import return_code_python +import google.generativeai as genai +from .gemini.result_sql import generate_step +from models.Database_Entity import StopSignal + +async def check_should_stop(chat_id: str, stop_event: object = None): + await asyncio.sleep(0.1) + if stop_event and stop_event.is_set(): + print("🛑 Dừng qua stop_event.") + return {"status": "cancelled"} + await asyncio.sleep(0.1) + if StopSignal.objects(chat_history=chat_id, is_stopped=True).first(): + print("🛑 Dừng vì có StopSignal trong DB.") + return {"status": "cancelled"} + return None + +async def analyze(query: str, user_id, languages, role, chat_id: str = "", stop_event: object = None): + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + data2 = generate_step(prompt_query1.format(question=query, prompt_user=prompt_user1)) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + data_test3 = await run_pipeline(data2, execute_query_user, user_id, role, languages) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + data_final = data2 + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + parsed_steps = parse_step_data_blocks(data_final) + output = "" + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + for step in parsed_steps: + step_id = step["id"] + output += f"Yêu cầu {step_id} : {step['content']}\n" + if step_id in data_test3: + data = data_test3[step_id][0] + result_str = "\n".join([f"{item}" for item in data]) + output += f"Kết quả yêu cầu {step_id} :\n{result_str}\n" + else: + output += "Kết quả:\nKhông có dữ liệu tương ứng.\n" + output += "-"*50 + "\n\n" + + folder = "test5" + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + # Gọi sinh code + code_python = return_code_python(output, folder=folder) + code_clean = code_python.strip() + if code_clean.startswith("```python"): + code_clean = code_clean[9:].strip() + if code_clean.endswith("```"): + code_clean = code_clean[:-3].strip() + code_clean = code_clean.replace("os_path:", "os.path").strip() + encoding_fix = 'import sys\nsys.stdout.reconfigure(encoding="utf-8")\n\n' + encoding_fix1= 'import numpy as np\n\n' + code_clean = encoding_fix + encoding_fix1 + code_clean + + + code_python=code_clean + + # Ghi ra file tạm để kiểm tra syntax + temp_file_name = f"{uuid.uuid4().hex[:8]}.py" + os.makedirs(folder, exist_ok=True) + temp_path = os.path.join(folder, temp_file_name) + + with open(temp_path, "w", encoding="utf-8") as f: + f.write(code_python) + + # Kiểm tra cú pháp + try: + py_compile.compile(temp_path, doraise=True) + except py_compile.PyCompileError as e: + print(f"❌ Lỗi cú pháp, tạo lại code: {e}") + code_python = return_code_python(output, folder=folder) + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + return code_python, folder diff --git a/function/analyze/merge_output.py b/function/analyze/merge_output.py new file mode 100644 index 0000000000000000000000000000000000000000..898d744fb53b4c9348873605fbfdc32d71108418 --- /dev/null +++ b/function/analyze/merge_output.py @@ -0,0 +1,16 @@ +import re + +# Hàm parse_step_data_blocks để trích xuất các yêu cầu từ văn bản +def parse_step_data_blocks(text): + blocks = re.findall(r"(.*?)", text, re.DOTALL) + parsed_steps = [] + for block in blocks: + match = re.match(r"Yêu cầu\s+(\d+):", block.strip()) + if match: + step_id = int(match.group(1)) + parsed_steps.append({ + "id": step_id, + "content": block.strip(), + "dependencies": set(), + }) + return parsed_steps \ No newline at end of file diff --git a/function/analyze/pipeline.py b/function/analyze/pipeline.py new file mode 100644 index 0000000000000000000000000000000000000000..e5fa4832b747a4425f77eb76c9d9bd160dc8edee --- /dev/null +++ b/function/analyze/pipeline.py @@ -0,0 +1,239 @@ +# import re +# from collections import defaultdict, deque + +# def parse_step_data_blocks(text): +# blocks = re.findall(r"(.*?)", text, re.DOTALL) +# parsed_steps = [] + +# for block in blocks: +# match = re.search(r"Yêu cầu\s+(\d+):", block.strip()) +# if match: +# step_id = int(match.group(1)) +# parsed_steps.append({ +# "id": step_id, +# "content": block.strip(), +# "dependencies": set(), +# }) +# else: +# pass +# return parsed_steps + + +# # ==== Phân tích phụ thuộc ==== +# def extract_dependencies(steps): +# for step in steps: +# deps = re.findall(r"Yêu cầu\s+(\d+)", step["content"]) +# step["dependencies"] = {int(d) for d in deps if int(d) != step["id"]} + +# # ==== Xây đồ thị phụ thuộc ==== +# def build_dependency_graph(steps): +# graph = defaultdict(list) +# for step in steps: +# for dep in step["dependencies"]: +# graph[dep].append(step["id"]) +# return graph + +# # ==== Sắp xếp topo ==== +# def topological_sort(steps, graph): +# indegree = {step["id"]: 0 for step in steps} +# for step in steps: +# for dep in step["dependencies"]: +# indegree[step["id"]] += 1 + +# queue = deque([sid for sid, deg in indegree.items() if deg == 0]) +# sorted_order = [] + +# while queue: +# current = queue.popleft() +# sorted_order.append(current) +# for neighbor in graph[current]: +# indegree[neighbor] -= 1 +# if indegree[neighbor] == 0: +# queue.append(neighbor) + +# return sorted_order + +# async def execute_steps(steps, execution_order, query_func,user_id, role,languages): +# results = {} +# step_map = {s["id"]: s for s in steps} +# for step_id in execution_order: +# step = step_map[step_id] + +# merged_prompt = "" +# for dep in step["dependencies"]: +# merged_prompt += f"[Yêu cầu {dep}]\n{step_map[dep]['content']}\n" +# merged_prompt += f"[Kết quả {dep}]\n{results[dep]}\n" +# sql_warning = ( +# "\n\n⚠️ **Lưu ý khi viết SQL**:\n" +# "- Tránh lỗi như `(1054, \"Unknown column 'oi.pro_id','oi.size' in 'field list'\")`\n" +# "- Vui lòng **kiểm tra kỹ tên cột** trong bảng thực tế.\n" +# "\n\n⚠️ **Lưu ý khi viết SQL** :\n" +# "- Tránh các lỗi như :\n" +# " Tuyệt đối cấm dùng các kí hiệu %, %%s để tránh bị báo lỗi" +# " (1054, \"Unknown column 'oi.pro_id' in 'field list'\")\n . Luôn đảm bảo bạn không bao giờ bị lỗi này" +# " (1054, \"Unknown column 'oi.note' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này\n" +# " (1054, \"Unknown column 'oi.size' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này \n" +# " (1054, \"Unknown column 'c.is_deleted' in 'on clause'\"). Luôn đảm bảo bạn không bao giờ bị lỗi này\n" +# "- 🔍 Quan trọng(Luôn luôn ghi nhớ): Bảng `order_item` **không có** cột `pro_id`, chỉ gồm các cột: `order_item_id`, `order_id`, `quantity`, `total_price`, `cart_id`,. để lấy chi tiết các item hãy nối qua cart tù cart nối cart_item\n" +# """- Tránh lỗi:(1054, "Unknown column 'c.is_deleted' in 'where clause'") vì cart không có thuộc tính is_deleted, cart_item không có thuộc tính user_id""" +# "- Quan trọng: Không cần thêm các điều kiện `is_deleted = 0` như:\n" +# " AND oi.is_deleted = 0\n" +# " AND ci.is_deleted = 0\n" + +# " AND pv.is_deleted = 0\n" +# " AND p.is_deleted = 0\n" +# " AND cat.is_deleted = 0\n" +# " AND c.is_deleted = 0\n" +# "- ✅ Các câu SQL đã được kiểm tra và chạy thành công trên hệ thống – vui lòng không tự động thêm các điều kiện lọc `is_deleted`.\n" +# "Khi lấy chi tiết tiết các item thì cần lấy pro_name, size và, price" +# "Vui lòng luu ý rang order_item không có cột pro_id,size chỉ có các cột order_item_id, order_id, quantity, total_pric \n" +# "- Không cần thêm điều kiện `is_deleted = 0` trong các bảng như `oi`, `ci`, `pv`, `p`, `cat`, `c`.\n" +# ) + +# # Ghép prompt chính để gửi đi +# question_prompt = merged_prompt + f"[ Vui long thục hiện Yêu cầu {step_id}]\n{step['content']}" + sql_warning + +# print(f"\n=== 🧩 Thực thi Yêu cầu {step_id} ===") + +# # Gọi hàm async thực thi câu hỏi +# result = await query_func(question_prompt, user_id, languages, role) + +# results[step_id] = result +# print(f"\n✅ 📤 Kết quả Yêu cầu {step_id}: {result}") + +# return results + +# async def run_pipeline(text, query_func,user_id,role,languages): +# steps = parse_step_data_blocks(text) +# extract_dependencies(steps) +# graph = build_dependency_graph(steps) +# order = topological_sort(steps, graph) +# print("\n🔗 Thứ tự thực thi hợp lệ theo phụ thuộc:", order) +# data = await execute_steps(steps, order, query_func,user_id,role,languages) +# return data + +import re +from collections import defaultdict, deque +import asyncio + +def parse_step_data_blocks(text): + blocks = re.findall(r"(.*?)", text, re.DOTALL) + parsed_steps = [] + + for block in blocks: + match = re.search(r"Yêu cầu\s+(\d+):", block.strip()) + if match: + step_id = int(match.group(1)) + parsed_steps.append({ + "id": step_id, + "content": block.strip(), + "dependencies": set(), + }) + return parsed_steps + + +def extract_dependencies(steps): + for step in steps: + deps = re.findall(r"Yêu cầu\s+(\d+)", step["content"]) + step["dependencies"] = {int(d) for d in deps if int(d) != step["id"]} + + +def build_dependency_graph(steps): + graph = defaultdict(list) + for step in steps: + for dep in step["dependencies"]: + graph[dep].append(step["id"]) + return graph + + +def topological_sort(steps, graph): + indegree = {step["id"]: 0 for step in steps} + for step in steps: + for dep in step["dependencies"]: + indegree[step["id"]] += 1 + + queue = deque([sid for sid, deg in indegree.items() if deg == 0]) + sorted_order = [] + + while queue: + current = queue.popleft() + sorted_order.append(current) + for neighbor in graph[current]: + indegree[neighbor] -= 1 + if indegree[neighbor] == 0: + queue.append(neighbor) + + return sorted_order + +async def execute_steps_parallel(steps, graph, query_func, user_id, role, languages): + results = {} + step_map = {s["id"]: s for s in steps} + indegree = {s["id"]: 0 for s in steps} + + for step in steps: + for dep in step["dependencies"]: + indegree[step["id"]] += 1 + + queue = deque([sid for sid, deg in indegree.items() if deg == 0]) + + async def run_step(step_id): + step = step_map[step_id] + + merged_prompt = "" + for dep in step["dependencies"]: + merged_prompt += f"[Yêu cầu {dep}]\n{step_map[dep]['content']}\n" + merged_prompt += f"[Kết quả {dep}]\n{results[dep]}\n" + + sql_warning = ( + "\n\n⚠️ **Lưu ý khi viết SQL**:\n" + "- Tránh lỗi như `(1054, \"Unknown column 'oi.pro_id','oi.size' in 'field list'\")`\n" + "- Vui lòng **kiểm tra kỹ tên cột** trong bảng thực tế.\n" + "\n\n⚠️ **Lưu ý khi viết SQL** :\n" + "- Tránh các lỗi như :\n" + " ###Tuyệt đối cấm dùng các kí hiệu %, %%s để tránh bị báo lỗi" + " (1054, \"Unknown column 'oi.pro_id' in 'field list'\")\n . Luôn đảm bảo bạn không bao giờ bị lỗi này" + " (1054, \"Unknown column 'oi.note' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này\n" + " (1054, \"Unknown column 'oi.size' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này \n" + " (1054, \"Unknown column 'c.is_deleted' in 'on clause'\"). Luôn đảm bảo bạn không bao giờ bị lỗi này\n" + "- 🔍 Quan trọng(Luôn luôn ghi nhớ): Bảng `order_item` **không có** cột `pro_id`, chỉ gồm các cột: `order_item_id`, `order_id`, `quantity`, `total_price`, `cart_id`,. để lấy chi tiết các item hãy nối qua cart tù cart nối cart_item\n" + """- Tránh lỗi:(1054, "Unknown column 'c.is_deleted' in 'where clause'") vì cart không có thuộc tính is_deleted, cart_item không có thuộc tính user_id""" + "- Quan trọng: Không cần thêm các điều kiện `is_deleted = 0` như:\n" + " AND oi.is_deleted = 0\n" + " AND ci.is_deleted = 0\n" + + " AND pv.is_deleted = 0\n" + " AND p.is_deleted = 0\n" + " AND cat.is_deleted = 0\n" + " AND c.is_deleted = 0\n" + "- ✅ Các câu SQL đã được kiểm tra và chạy thành công trên hệ thống – vui lòng không tự động thêm các điều kiện lọc `is_deleted`.\n" + "Khi lấy chi tiết tiết các item thì cần lấy pro_name, size và, price" + "Vui lòng luu ý rang order_item không có cột pro_id,size chỉ có các cột order_item_id, order_id, quantity, total_pric \n" + "- Không cần thêm điều kiện `is_deleted = 0` trong các bảng như `oi`, `ci`, `pv`, `p`, `cat`, `c`.\n" + ) + question_prompt = merged_prompt + f"[ Vui lòng thực hiện Yêu cầu {step_id}]\n{step['content']}" + sql_warning + + print(f"\n=== 🧩 Thực thi Yêu cầu {step_id} ===") + result = await query_func(question_prompt, user_id, languages, role) + results[step_id] = result + print(f"\n✅ 📤 Kết quả Yêu cầu {step_id}: {result}") + + for neighbor in graph[step_id]: + indegree[neighbor] -= 1 + if indegree[neighbor] == 0: + queue.append(neighbor) + + while queue: + current_batch = list(queue) + queue.clear() + await asyncio.gather(*(run_step(sid) for sid in current_batch)) + + return results + +async def run_pipeline(text, query_func, user_id, role, languages): + steps = parse_step_data_blocks(text) + extract_dependencies(steps) + graph = build_dependency_graph(steps) + order = topological_sort(steps, graph) + print("\n🔗 Thứ tự thực thi hợp lệ theo phụ thuộc:", order) + data = await execute_steps_parallel(steps, graph, query_func, user_id, role, languages) + return data diff --git a/function/analyze/prompt_databse.py b/function/analyze/prompt_databse.py new file mode 100644 index 0000000000000000000000000000000000000000..fce8581475be7ba17d75e8dea5d9ee56d824661a --- /dev/null +++ b/function/analyze/prompt_databse.py @@ -0,0 +1,644 @@ +prompt_user = """ +Đây là các bảng dũ liệu vui lòng đọc kỹ càng, chính xác để xác định. + -voucher +Đây là bảng chứa các mã khuyến mãi của từng người dùng. +- Gồm có các thuộc tính: + + voucher_id: mã Id của bảng voucher. Kiểu Integer + + key_voucher: mã khóa định danh cho từng voucher riêng biệt. + + number: số lượng voucher hiện có. Kiểu Interger + + start_date: Ngày bắt đầu của mã khuyến mãi. Kiểu dateTime + + end_date: Ngày kết thúc của mã khuyến mãi. + + status: Trạng thái của mã khuyến mãi(Là giá trị Enum gồm có: ACTIVE, EXPIRED, ) + + is_deleted: Kiểm tra xem voucher có bị xóa hay không. Kiểu giá Boolean + + date_deleted: Ngày xóa voucher. + + discount: Giá trị của khuyến mãi. Kiểu Double + + post_id: bài viết mà voucher đó liên quan +- Các mối quan hệ: + + Khóa ngoại liên kết với bảng orders. + + Khóa ngoại liên kết với bảng user_voucher. + +-user_voucher +Đây là bảng thể hiện mối liên hệ giữa người dùng và voucher mà họ sở hữu +-Gồm có các thuộc tính: + + user_voucher_id: Mã Id của bảng user_voucher. Kiểu Integer + + status: trạng thái của voucher mà người dùng sở hữu. Kiểu Enum gồm có: INACTIVE, USED + + user_id: mã Id của người dùng sở hữu voucher. Kiểu Integer + + voucher_id: mã voucher của cửa hàng mà người dùng đã thu thập. Kiểu Integer. +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng voucher + +-cart +Đây là bảng chứa các giỏ hàng thuộc về từng người dùng cụ thể. +-Gồm có các thuộc tính: ++ cart_id: mã Id của định danh cho giỏ hàng. Kiểu Integer ++ status: trạng thái của giỏ hàng (Là giá trị Enum gồm có: COMPLETED, NEW, RESTORE ++ total_price: tổng tiền các sản phẩm có trong giỏ hàng. Kiểu Double ++ total_product: tổng số lượng sản phẩm có trong giỏ hàng. Kiểu Integer ++ user_id: mã Id của người dùng đang sở hữu giỏ hàng. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +-cart_item +Đây là bảng chứa các sản phẩm thuộc nằm trong một giỏ hàng cụ thể của người dùng +-Gồm có các thuộc tính: + + cart_item_id: mã Id của sản phẩm ở trong giỏ hàng. Kiểu Integer + + quantity: số lượng của sản phẩm đó trong giỏ hàng. Kiểu Integer + + total_price: tổng tiền của sản phẩm. Kiểu Double + + cart_id: mã Id của giỏ hàng mà sản phẩm đang thuộc về. Kiểu Integer + + pro_id: mã Id của sản phẩm trong cửa hàng. Kiểu Integer + + size: kích cỡ của sản phẩm (Là giá trị Enum gồm có: S,M,L) + + date_deleted: ngày sản phẩm bị xóa khỏi giỏ hàng. Kiểu dateTime + + is_delete: Kiểm tra xem sản phẩm có bị xóa khỏi giỏ hàng hay không. Kiểu giá trị Boolean + + note: Ghi chú. Kiểu String +-Các mối liên hệ: + + cart_id: Khóa ngoại liên kết với bảng cart + + pro_Id: Khóa ngoại liên kết với bảng product + +-category +Đây là bảng chứa các danh mục đồ uống có trong cửa hàng. +-Gồm có các thuộc tính: + + cate_id: Mã Id của danh mục đồ uống. Kiểu Integer + + cate_img: url hình ảnh tượng trưng cho danh mục. Kiểu String + + cate_name: tên danh mục đồ uống. Kiểu String + + date_created: ngày danh mục được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày danh mục bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật danh mục. Kiểu dateTime + + is_deleted: Kiểm tra xem danh mục có bị xóa hay không. Kiểu giá trị Boolean + +-category_translation +Đây là bảng chứa các danh mục đồ uống đã được translate qua tiếng Anh. +-Gồm có các thuộc tính: + + cate_trans_id: Mã Id của danh mục đồ uống đã được translate. Kiểu Integer + + cate_name: tên danh mục đã được translate. Kiểu String + + date_created: ngày danh mục được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày danh mục bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật danh mục. Kiểu dateTime + + is_deleted: Kiểm tra xem danh mục có bị xóa hay không. Kiểu giá trị Boolean + + language_code: ngôn ngữ dùng để translate. Kiểu Enum gồm có: EN, VN + + cate_id: mã Id của danh mục ở bảng danh mục chưa translate. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng category + +-contact +Đây là bảng chứa các thư liên hệ mà người dùng gửi về cửa hàng +-Gồm có các thuộc tính: + + contact_id: mã Id của thư liên hệ. Kiểu Integer + + create_date: ngày người dùng gửi liên hệ. Kiểu dateTime + + date_deleted: ngày liên hệ bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật liên hệ. Kiểu dateTime + + is_deleted: Kiểm tra xem liên hệ có bị xóa hay không. Kiểu giá trị Boolean + + description: nội dung thư liên hệ. Kiểu String + + email: email của người dùng gửi liên hệ. Kiểu String + + full_name: tên của người dùng gửi liên hệ. Kiểu String + + phone_number: số điện thoại của người dùng gửi liên hệ. Kiểu String + + status: trạng thái của liên hệ. Kiểu Enum gồm có: COMPLETED, WAITING + +-favourite +Đây là bảng chứa danh sách yêu thích tương ứng với từng người dùng +-Gồm có các thuộc tính: + + fav_id: Mã Id của danh sách yêu thích + + date_created: ngày người dùng tạo danh sách yêu thích. Kiểu dateTime + + date_deleted: ngày danh sách yêu thích bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật danh sách yêu thích. Kiểu dateTime + + is_deleted: Kiểm tra xem danh sách yêu thích có bị xóa hay không. Kiểu giá trị Boolean + + user_id: mã Id của người dùng sở hữu danh sách yêu thích. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng user + +-favourite_item: +Đây là bảng chứa các sản phẩm yêu thích nằm trong danh sách yêu thích của ngưởi dùng. +-Gồm có các thuộc tính: + + fav_item_id: mã Id sản phẩm được yêu thích. Kiểu Integer + + fav_id: mã Id danh sách yêu thích tương ứng chứa sản phẩm được yêu thích. Kiểu Integer + + pro_id: mã Id của sản phẩm tương ứng với sản phẩm đã được yêu thích. Kiểu Integer + + size: kích cỡ của sản phẩm được yêu thích. Kiểu Enum gồm có:S,M,L + + date_deleted: ngày sản phẩm được yêu thích bị xóa. Kiểu dateTime + + is_deleted: Kiểm tra xem sản phẩm được yêu thích có bị xóa hay không. Kiểu giá trị Boolean +-Các mối liên hệ: ++ Khóa ngooại liên kết tới bảng favourite + + Khóa ngoại liên kết tới bảng product + +-map_directions +Đây là bảng chứa thông tin về tuyến đường giao hàng +-Gồm có các thuộc tính: + + map_direction_id: mã Id của bảng map_directions. Kiểu Long + + created_at: thời gian tạo thông tin tuyến đường. Kiểu dateTime ++ deleted_at: thời gian xóa thông tin tuyến đường. Kiểu dateTime ++ is_deleted: Kiểm tra xem thông tin tuyến đường có bị xóa hay không. Kiểu giá trị Boolean + + latitude_end: vĩ độ điểm đến. Kiểu Double. + + latitude_start: vĩ độ điểm đi. Kiểu Double. + + longitude_end: kinh độ điểm đến. Kiểu Double + + longitude_start: kinh độ điểm đi. Kiểu Double + + overview_polyline: dữ liệu mã hóa của tuyến đường. Kiểu Text + + shipment_id: mã Id của đơn hàng vận chuyển. Kiểu Integer +-Các mối liên hệ: ++ Khóa ngoại liên kết với bảng shipment + +-notification +Đây là bảng chứa thông báo mà người dùng nhận được +-Gồm có các thuộc tính: + + notifi_id: mã Id của thông báo. Kiểu Integer. + + is_read: kiểm tra xem thông báo đã được đọc hay chưa. Kiểu Boolean + + message: nội dung thông báo. Kiểu String + + shipment_id: mã Id của đơn hàng vận chuyển. Kiểu Integer + + time: thời gian tạo thông báo. Kiểu dateTime + + user_id: mã Id người dùng nhận thông báo. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng user +-orders +Đây là bảng chứa các đơn hàng của người dùng +-Gồm có các thuộc tính: + + order_id: Mã Id của đơn hàng trong bảng orders. Kiểu Integer. + + address: địa chỉ người đặt. Kiểu String. + + date_created: ngày đơn hàng được tạo. Kiểu dateTime + + date_deleted: ngày đơn hàng bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật đơn hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + delivery_date: thời gian giao hàng dự kiến. Kiểu dateTime + + delivery_fee: phí vận chuyển. Kiểu Double + + discount_price: số tiền được giảm giá. Kiểu Double + + note: ghi chú của đơn hàng. Kiểu Text + + order_date: ngày đặt đơn. Kiểu dateTime + + phone_number: số điện thoại nhận hàng. Kiểu String + + status: trạng thái của đơn hàng. Kiểu Enum gồm có: CANCELLED, CONFIRMED , WAITING + + total_price: tổng tiền các sản phẩm có trong đơn hàng. Kiểu Double + + user_id: mã Id của người dùng đặt đơn hàng. Kiểu Integer. + + voucher_id: mã Id voucher được áp dụng cho đơn hàng. Kiểu Integer + + cancel_reason: lí do hủy đơn. Kiểu Enum gồm có CHANGED_MY_MIND, DELIVERY_TOO_SLOW, FOUND_CHEAPER_ELSEWHERE + + date_canceled: ngày hủy đơn. Kiểu dateTime + + is_cancel_reason: kiểm tra xem đơn hàng có bị hủy với lí do nào chưa. Kiểu Boolean. + + point_coin_use: số coin mà người dùng áp vào đơn hàng. Kiểu Float +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng voucher + +-order_item +Đây là bảng chứa các sản phẩm có trong đơn hàng của người dùng +-Gồm có các thuộc tính: + + order_item_id: mã Id của sản phẩm trong bảng orders có trong đơn hàng. Kiểu Integer + + date_created: ngày sản phẩm được thêm vào đơn hàng. Kiểu dateTime + + date_deleted: ngày sản phẩm bị xóa khỏi đơn hàng . Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong đơn hàng. Kiểu dateTime + + is_deleted: Kiểm tra sản phẩm trong đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + quantity: số lượng của sản phẩm trong đơn hàng. Kiểu Integer + + total_price: tổng tiền của sản phẩm. Kiểu Double + + cart_id: mã Id của giỏ hàng chứa các sản phẩm. Kiểu Integer + + order_id: mã Id của đơn hàng chứa sản phẩm. Kiểu Integer. + + user_id: mã Id của người dùng đặt đơn. Kiểu Integer. +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng cart + + Khóa ngoại liên kết với bảng orders + + Khóa ngoại liên kết với bảng user + +-payments + Đây là bảng chứa các thanh toán cho các đơn hàng của người dùng. +-Gồm có các thuộc tính: + + payment_id: mã Id thanh toán của bảng payments. Kiểu Integer. + + amount: tổng tiền cần thanh toán cho đơn hàng. Kiểu Double + + date_created: ngày tạo thanh toán cho đơn hàng. Kiểu dateTime + + date_deleted: ngày xóa thanh toán đơn hàng . Kiểu dateTime + + is_deleted: Kiểm tra thanh toán của đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + order_id_payment: mã Id của thanh toán khi sử dụng các cổng thanh toán bên thứ ba. Kiểu String + + payment_method: phương thức thanh toán đã lựa chọn cho đơn hàng. Kiểu Enum gồm có: CASH, CREDIT + + status: trạng thái thanh toán đơn hàng. Kiểu Enum gồm có: COMPLETED, FAILED , PENDING , REFUND + + order_id: mã Id của đơn hàng đang thanh toán. Kiểu Integer + + is_refunded: kiểm tra xem đơn hàng đã được hoàn tiền chưa. Kiểu Boolean + + date_refunded: ngày hoàn tiền cho đơn hàng. Kiểu dateTime + + link: url trang thanh toán bên thứ ba. Kiểu Text +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng orders + +-post +Đây là bảng chứa các bài viết quảng cáo của cửa hàng +-Gồm có các thuộc tính: + + post_id: Mã Id của bài viết trong bảng post. Kiểu Integer + + banner_url: url đường dẫn hình ảnh của bài viết. Kiểu String + + date_create: ngày tạo bài viết. Kiểu dateTime + + date_deleted: ngày bài viết bị xóa. Kiểu dateTime + + description: nội dung bài viết. Kiểu Text + + is_deleted: Kiểm tra bài viết có bị xóa hay không. Kiểu giá trị Boolean + + short_des: Mô tả ngắn cho bài viết. Kiểu String + + title: tiêu đề của bài viết. Kiểu String + + type: loại bài viết. Kiểu Enum gồm có: DISCOUNT, EVENT, NEW + + user_id: mã Id người tạo bài viết. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +-post_translation +Đây là bảng chứa các bài viết quảng cáo của cửa hàng đã được translate qua ngôn ngữ khác. +-Gồm có các thuộc tính: ++ post_trans_id: Mã Id của bài viết đã được translate trong bảng post_translation. Kiểu Integer ++ date_create: ngày tạo bài viết translate. Kiểu dateTime + + date_deleted: ngày bài viết translate bị xóa. Kiểu dateTime + + description: nội dung bài viết đã được translate . Kiểu Text + + is_deleted: Kiểm tra bài viết đã được translate có bị xóa hay không. Kiểu giá trị Boolean + + language_code: ngôn ngữ dùng để translate. Kiểu Enum gồm có: EN, VN + + short_des: Mô tả ngắn đã được translate cho bài viết. Kiểu String + + title: tiêu đề đã được translate của bài viết. Kiểu String + + post_id: Mã Id của bài viết khi chưa translate ở bảng post. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +-price_history +Đây là bảng chứa lịch sử thay đổi giá của sản phâmr trong cửa hàng. +-Gồm có các thuộc tính: + + history_id: mã Id của bảng price_history. Kiểu Integer + + chang_reason: lí do thay đổi giá. Kiểu String + + date_changed: ngày thay đổi giá. Kiểu dateTime + + new_price: giá mới sau khi đổi. Kiểu Double + + ole_price: giá cũ khi chưa đổi. Kiểu Double + + var_id: mã Id biến thể của sản phẩm đã được thay đổi giá. Kiểu Integer +-Các mối liện hệ: + + Khóa ngoại liên kết với bảng product_variants + +-product +Đây là bảng chứa các sản phẩm có trong cửa hàng +-Gồm có các thuộc tính: + + pro_id: mã Id của sản phẩm. Kiểu Integer + + date_created: ngày sản phẩm được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày sản phẩm bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong cửa hàng. Kiểu dateTime + + description: mô tả sản phẩm. Kiểu Text + + is_deleted: Kiểm tra sản phẩm trong cửa hàng có bị xóa hay không. Kiểu giá trị Boolean + + list_pro_img: danh sách url hình ảnh của sản phẩm. Kiểu Text + + pro_name: tên sản phẩm. Kiểu String + + category_id: mã Id của danh mục chứa sản phẩm. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng category + +-product_translation +Đây là bảng chứa các sản phẩm có trong cửa hàng đã được translate +-Gồm có các thuộc tính: + + pro_trans_id: mã Id của sản phẩm đã được translate trong bảng product_translation. Kiểu Integer + + date_created: ngày sản phẩm được translate. Kiểu dateTime + + date_deleted: ngày sản phẩm được translate bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm được translate. Kiểu dateTime + + description: mô tả sản phẩm đã được translate. Kiểu Text + + is_deleted: Kiểm tra sản phẩm được translate có bị xóa hay không. Kiểu giá trị Boolean + + language_ code: ngôn ngữ dùng để translate. Kiểu Enum gồm có: EN, VN + + pro_name: tên sản phẩm được translate. Kiểu String + + pro_id: mã Id của sản phẩm trong bảng product. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng product + +-product_variants +Đây là bảng chứa các biến thể của sản phẩm có trong cửa hàng +-Gồm có các thuộc tính: + + var_id: mã Id của biến thể. Kiểu Integer + + date_created: ngày biến thể của sản phẩm được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày biến thể của sản phẩm bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật biến thể của sản phẩm trong cửa hàng. Kiểu dateTime + + is_deleted: Kiểm tra biến thể của sản phẩm trong cửa hàng có bị xóa hay không. Kiểu giá trị Boolean + + price: giá biến thể của sản phẩm. Kiểu Double + + size: kích cỡ của biến thể sản phẩm. Kiểu Enum gồm có: S,M,L + + stock: số lượng tồn kho của biến thể sản phẩm. Kiểu Integer + + pro_id: mã Id của sản phẩm. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng product + +-review +Đây là bảng chứa các đánh giá về sản phẩm có trong cửa hàng +-Gồm có các thuộc tính: + + review_id: mã Id của đánh giá. Kiểu Integer + + content: nội dung đánh giá. Kiểu Text + + date_created: ngày tạo đánh giá. Kiểu dateTime + + date_deleted: ngày đánh giá bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật đánh giá. Kiểu dateTime + + is_deleted: Kiểm tra đánh giá có bị xóa hay không. Kiểu giá trị Boolean + + rating_star: số lượng sao đánh giá. Kiểu Integer + + pro_id: mã Id sản phẩm được đánh giá. Kiểu Integer + + user_id: mã Id của người dùng viết đánh giá sản phẩm. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng product + + Khóa ngoại liên kết với bảng user + +-shipment +Đây là bảng chứa các vận đơn +-Gồm có các thuộc tính: + + shipment_id: mã Id vận đơn. Kiểu Integer + + date_created: ngày tạo vận đơn. Kiểu dateTime + + date_deleted: ngày vận đơn bị xóa. Kiểu dateTime + + date_delivered: thời gian giao hàng dự kiến cho vận đơn. Kiểu dateTime + + date_shipped: thời gian giao hàng thành công. Kiểu dateTime + + date_canceled: thời gian hủy vận đơn. Kiểu dateTime + + is_deleted: Kiểm tra vận đơn có bị xóa hay không. Kiểu giá trị Boolean + + status: trạng thái vận đơn. Kiểu Enum gồm có: CANCELLED, SHIPPING, SUCCESS, WAITING + + payment_id: mã thanh toán của đơn hàng. Kiểu Integer + + user_id: mã Id của nhân viên giao hàng. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng payments + + Khóa ngoại liên kết với bảng user + +-step_details +Đây là bảng chứa các mô tả chi tiết về tuyến đường giao hàng +-Gồm có các thuộc tính: + + step_id: mã Id của bước đi trong bảng step_details. Kiểu Long + + distance_text: độ dài của quãng đường cần đi trong bước này. Kiểu String + + duration_text: thời gian của quãng đường cần đi trong bước này. Kiểu String + + instruction: hướng dẫn của quãng đường cần đi trong bước này. Kiểu Text + + latitude: vĩ độ điểm. Kiểu Double. + + longitude: kinh độ điểm. Kiểu Double. + + map_direction_id: mã Id của bảng map_directions. Kiểu Long +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng map_directions + +-user +Đây là bảng chứa các thông tin người dùng trong cửa hàng +-Gồm có các thuộc tính: + + user_id: mã Id của sản phẩm. Kiểu Integer + + date_created: ngày đăng ký tài khoản. Kiểu dateTime + + date_deleted: ngày tài khoản bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật thông tin tài khoản. Kiểu dateTime + + avatar: url hình ảnh đại diện của tài khoản. Kiểu String + + is_deleted: Kiểm tra tài khoản người dùng có bị xóa hay không. Kiểu giá trị Boolean + + birthdate: ngày tháng năm sinh của người dùng. Kiểu dateTime + + city: địa chỉ thành phố của người dùng. Kiểu String + + district: địa chỉ quận/huyện của người dùng. Kiểu String + + email: địa chỉ email của người dùng. Kiểu String + + full_name: tên đầy đủ người dùng. Kiểu String + + password: mật khẩu tài khoản của người dùng. Kiểu String + + phone_number: số điện thoại của người dùng. Kiểu String + + role: vai trò của tài khoản. Kiểu Enum gồm có: ADMIN, CUSTOMER, SHIPPER + + sex: giới tính của người dùng. Kiểu Enum gồm có: FEMALE, MALE, OTHER + + street: địa chỉ đường của người dùng. Kiểu String + + type: loại đăng nhập của tài khoản, Kiểu Enum gồm có: BASIC, BOTH, EMAIL + + username: tên đăng nhập của tài khoản. Kiểu String + + ward: địa chỉ xã/phường của người dùng. Kiểu String + +-user_chat +Đây là bảng chứa các đoạn chat của người dùng với chatbot của cửa hàng +-Gồm có các thuộc tính: + + user_chat_id: mã Id của đoạn chat. Kiểu Integer + + date_created: ngày tạo đoạn chat. Kiểu dateTime + + date_deleted: ngày đoạn chat bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật đoạn chat. Kiểu dateTime + + chat_name: tên đoạn chat. Kiểu String + + is_deleted: Kiểm tra đoạn chat có bị xóa hay không. Kiểu giá trị Boolean + + id_mongo_db: mã Id đoạn chat được lưu trên mogodb. Kiểu String + + user_id: mã Id của người dùng tạo đoạn chat. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +-user_coin +Đây là chứa các coin mà người dùng tích lũy được +-Gồm có các thuộc tính: + +_user_coin_id: mã Id của túi coin của người dùng. Kiểu Integer + + point_coin: số soin mà người dùng tích được. Kiểu Float + + user_id: mã Id của người dùng sở hữu coin. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + + +- absence_request +Đây là bảng chứa các đơn xin nghỉ của shipper +- Gồm có các thuộc tính: ++ request_id: mã Id của bảng absence_request. Kiểu Integer ++ end_date: ngày kết thúc nghỉ phép. Kiểu dateTime ++ reason: lí do xin nghỉ. Kiểu Text ++ start_date: ngày bắt đầu nghỉ phép. Kiểu dateTime ++ status: trạng thái đơn xin nghỉ phép. Kiểu Enum gồm có APPROVED, REJECTED, WAITING ++ user_id: mã id của nhân viên xin nghỉ. Kiểu Integer +- Các mối quan hệ: + + Khóa ngoại liên kết với bảng user. + +- cart_group +Đây là bảng chứa các giỏ hàng phụ thuộc về từng người dùng cụ thể trong 1 đơn đặt han. +-Gồm có các thuộc tính: ++ cart_id: mã Id của định danh cho giỏ hàng. Kiểu Integer ++ total_price: tổng tiền các sản phẩm có trong giỏ hàng. Kiểu Double ++ total_product: tổng số lượng sản phẩm có trong giỏ hàng. Kiểu Integer ++ user_id: mã Id của người dùng đang là trưởng nhóm. Kiểu Integer ++ member_id: mã id của người dùng sở hữu giỏ hàng. Kiểu Integer ++ date_created: ngày tạo giỏ hàng. Kiểu dateTime + + date_deleted: ngày giỏ hàng bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật giỏ hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem giỏ hàng có bị xóa hay không. Kiểu giá trị Boolean +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user ++ Khóa ngoại liên kết với bảng group_order_members + +- cart_item_group +Đây là bảng chứa các sản phẩm thuộc nằm trong một giỏ hàng cụ thể của người dùng trong một đơn hàng nhóm +-Gồm có các thuộc tính: + + cart_item_id: mã Id của sản phẩm ở trong giỏ hàng. Kiểu Integer + + quantity: số lượng của sản phẩm đó trong giỏ hàng. Kiểu Integer + + total_price: tổng tiền của sản phẩm. Kiểu Double + + cart_id: mã Id của giỏ hàng trong đơn nhóm mà sản phẩm đang thuộc về. Kiểu Integer + + pro_id: mã Id của sản phẩm trong cửa hàng. Kiểu Integer + + size: kích cỡ của sản phẩm (Là giá trị Enum gồm có: S,M,L) + + date_created: ngày thêm sản phẩm vào giỏ hàng. Kiểu dateTime + + date_deleted: ngày sản phẩm trong giỏ hàng bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong giỏ hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem sản phẩm trong giỏ hàng có bị xóa hay không. Kiểu giá trị Boolean ++ item_price: giá của sản phẩm. Kiểu Double + + note: Ghi chú. Kiểu String +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng cart_group + + Khóa ngoại liên kết với bảng product_variant + +- group_order_members +Đây là bảng chứa các thành viên trong một nhóm đặt hàng +-Gồm có các thuộc tính: + + member_id: mã Id của thành viên ở trong nhóm. Kiểu Integer + + amount: tổng tiền sản phẩm thành viên đã thêm vào đơn nhóm. Kiểu Double + + quantity: số lượng của sản phẩm thành viên đã thêm vào đơn nhóm. Kiểu Integer + + status: trạng thái của nhóm đặt hàng. Kiểu Enum gồm có: CANCELED, CHECKOUT, COMPLETED, CREATED, SHOPPING + + type_payment: hình thức thanh toán của thành viên trong nhóm đặt hàng. Kiểu Enum gồm có: CASH, MOMO, NONE, PAYOS, VNPAY, ZALO + + cart_id: mã Id của giỏ hàng trong đơn nhóm mà sản phẩm đang thuộc về. Kiểu Integer + + group_order_id: mã Id của nhóm đặt hàng đang tham gia. Kiểu Integer + + user_id: mã Id của người dùng. Kiểu Integer + + date_created: ngày tham gia nhóm đặt hành. Kiểu dateTime + + date_deleted: ngày bị xóa khỏi nhóm đặt hàng. Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong nhóm đặt hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem thành viên trong nhóm có bị xóa hay không. Kiểu giá trị Boolean + + is_leader: Kiểm tra xem thành viên có phải trưởng nhóm không. Kiểu Boolean + + is_paid: kiểm tả xem thành viên đã thanh toán chưa. Kiểu Boolean + + note: Ghi chú. Kiểu Text +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng group_orders + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng cart_group + + +- group_orders +Đây là bảng chứa các thông tin về các nhóm đặt hàng +-Gồm có các thuộc tính: + + group_order_id: mã Id của nhóm đặt hàng. Kiểu Integer + + address: địa chỉ giao hàng cho đơn nhóm. Kiểu String + + code: mã tham giao vào đơn nhóm. Kiểu Integer ++ date_created: ngày tạo nhóm đặt hàng. Kiểu dateTime + + date_deleted: ngày xóa nhóm đặt hàng. Kiểu dateTime + + date_updated: ngày cập nhật thông tin trong nhóm đặt hàng. Kiểu dateTime + + deadline_payment: giới hạn thời gian tồn tại của nhóm đặt hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem nhóm có bị xóa hay không. Kiểu giá trị Boolean + + is_flexible_payment: Kiểm tra xem có thể thanh toán nhiều hình thức không. Kiểu Boolean + + link: đường dẫn để tham gia nhóm đặt hàng. Kiểu String ++ note: Ghi chú. Kiểu Text ++ order_date: ngày đặt hàng. Kiểu dateTime + + name_group: tên nhóm đặt hàng. Kiểu String + + status: trạng thái của nhóm đặt hàng. Kiểu Enum gồm có: CANCELED, CHECKOUT, COMPLETED, CREATED, SHOPPING + + total_price: tổng tiền của nhóm đặt hàng. Kiểu Double + + total_quantity: tổng số lượng sản phẩm có trong nhóm. Kiểu Integer + + type_bill: loại thanh toán cho nhóm đặt hàng. Kiểu Enum gồm có: PAY_FOR_ALL, SPLIT_BILL_WITH_ALL + + type_payment: hình thức thanh toán của nhóm đặt hàng. Kiểu Enum gồm có: CASH, MOMO, NONE, PAYOS, VNPAY, ZALO ++ user_id: mã Id của trưởng nhóm. Kiểu Integer +-Các mối liên hệ: ++ Khóa ngoại liên kết với bảng user + + + + + + +- +- voucher +Đây là bảng chứa các mã khuyến mãi của từng người dùng. ++ voucher_id: mã Id của bảng voucher. Kiểu Integer + + key_voucher: mã khóa định danh cho từng voucher riêng biệt. + + number: số lượng voucher hiện có. Kiểu Interger + + start_date: Ngày bắt đầu của mã khuyến mãi. Kiểu dateTime + + end_date: Ngày kết thúc của mã khuyến mãi. + + status: Trạng thái của mã khuyến mãi(Là giá trị Enum gồm có: ACTIVE, EXPIRED, ) + + is_deleted: Kiểm tra xem voucher có bị xóa hay không. Kiểu giá Boolean + + date_deleted: Ngày xóa voucher. + + discount: Giá trị của khuyến mãi. Kiểu Double + + post_id: bài viết mà voucher đó liên quan +- Các mối quan hệ: + + Khóa ngoại liên kết với bảng post. + +- user_voucher +Đây là bảng thể hiện mối liên hệ giữa người dùng và voucher mà họ sở hữu +-Gồm có các thuộc tính: + + user_voucher_id: Mã Id của bảng user_voucher. Kiểu Integer + + status: trạng thái của voucher mà người dùng sở hữu. Kiểu Enum gồm có: INACTIVE, USED + + user_id: mã Id của người dùng sở hữu voucher. Kiểu Integer + + voucher_id: mã voucher của cửa hàng mà người dùng đã thu thập. Kiểu Integer. +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng voucher + +- notification +Đây là bảng chứa thông báo mà người dùng nhận được +-Gồm có các thuộc tính: + + notifi_id: mã Id của thông báo. Kiểu Integer. + + is_read: kiểm tra xem thông báo đã được đọc hay chưa. Kiểu Boolean + + message: nội dung thông báo. Kiểu String + + shipment_id: mã Id của đơn hàng vận chuyển. Kiểu Integer + + group_order_id: mã Id của nhóm đặt hàng. Kiểu Integer + + time: thời gian tạo thông báo. Kiểu dateTime + + user_id: mã Id người dùng nhận thông báo. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng user + +- payments_group + Đây là bảng chứa các thanh toán cho các đơn hàng nhóm. +-Gồm có các thuộc tính: + + payment_id: mã Id thanh toán của bảng payments_group. Kiểu Integer. + + amount: tổng tiền cần thanh toán cho đơn hàng nhóm. Kiểu Double + + date_created: ngày tạo thanh toán cho đơn hàng nhóm. Kiểu dateTime + + date_deleted: ngày xóa thanh toán đơn hàng nhóm . Kiểu dateTime + + date_refunded: ngày hoàn tiền cho đơn hàng nhóm . Kiểu dateTime + + discount_percent: phần trăm giảm giá cho đơn hàng nhóm. Kiểu Double + + is_deleted: Kiểm tra thanh toán của đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + is_refunded: kiểm tra xem đơn hàng đã được hoàn tiền chưa. Kiểu Boolean + + link: url trang thanh toán bên thứ ba. Kiểu Text + + order_id_payment: mã Id của thanh toán khi sử dụng các cổng thanh toán bên thứ ba. Kiểu String + + payment_method: phương thức thanh toán đã lựa chọn cho đơn hàng. Kiểu Enum gồm có: CASH, CREDIT + + status: trạng thái thanh toán đơn hàng. Kiểu Enum gồm có: COMPLETED, FAILED , PENDING , REFUND + + group_order_id: mã Id của đơn hàng nhóm đang thanh toán. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng group_orders thông qua group_order_id + + + +- shipment +Đây là bảng chứa các vận đơn +-Gồm có các thuộc tính: + + shipment_id: mã Id vận đơn. Kiểu Integer + + date_created: ngày tạo vận đơn. Kiểu dateTime + + date_deleted: ngày vận đơn bị xóa. Kiểu dateTime + + date_delivered: thời gian giao hàng dự kiến cho vận đơn. Kiểu dateTime + + date_shipped: thời gian giao hàng thành công. Kiểu dateTime + + date_canceled: thời gian hủy vận đơn. Kiểu dateTime + + is_deleted: Kiểm tra vận đơn có bị xóa hay không. Kiểu giá trị Boolean + + status: trạng thái vận đơn. Kiểu Enum gồm có: CANCELLED, SHIPPING, SUCCESS, WAITING + + payment_id: mã thanh toán của đơn hàng. Kiểu Integer + + user_id: mã Id của nhân viên giao hàng. Kiểu Integer + + distance: khoảng cách giao hàng. Kiểu Double + + note: ghi chú cho đơn hàng khi giao hàng. Kiểu String +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng payments thông qua payment_id + + Khóa ngoại liên kết với bảng user thông qua user_id + +- shipment_group +Đây là bảng chứa các vận đơn +-Gồm có các thuộc tính: + + shipment_id: mã Id vận đơn của đơn hàng nhóm. Kiểu Integer + + date_canceled: thời gian hủy vận đơn. Kiểu dateTime + + date_created: ngày tạo vận đơn. Kiểu dateTime + + date_deleted: ngày vận đơn bị xóa. Kiểu dateTime + + date_delivered: thời gian giao hàng dự kiến cho vận đơn. Kiểu dateTime + + date_shipped: thời gian giao hàng thành công. Kiểu dateTime + + distance: khoảng cách giao hàng. Kiểu Double + + is_deleted: Kiểm tra vận đơn có bị xóa hay không. Kiểu giá trị Boolean + + note: ghi chú cho đơn hàng khi giao hàng. Kiểu String + + status: trạng thái vận đơn. Kiểu Enum gồm có: CANCELLED, SHIPPING, SUCCESS, WAITING + + payment_id: mã thanh toán của đơn hàng nhóm. Kiểu Integer + + user_id: mã Id của nhân viên giao hàng. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng payments_group + + Khóa ngoại liên kết với bảng user thông qua user_id + +- shipper_attendance +Đây là bảng quản lý chấm công của shipper +-Gồm có các thuộc tính: + + id: mã định danh chấm công. Kiểu Integer + + attendance_date: ngày chấm công. Kiểu Date + + check_in_time: thời gian check-in của shipper. Kiểu dateTime + + created_at: thời điểm chấm công. Kiểu dateTime + + is_present: kiểm tra có mặt hay không. Kiểu Boolean + + note: ghi chú liên quan đến chấm công. Kiểu Text + + status: trạng thái chấm công. Kiểu Enum gồm các giá trị: ABSENT, LATE, NONE, ON_LEAVE, ON_TIME + + updated_at: thời điểm cập nhật gần nhất. Kiểu DateTime(6) + + user_id: mã ID của shipper. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user thông qua user_id + +- shipper_commission_detail +Đây là bảng chứa chi tiết hoa hồng hàng ngày của shipper +-Gồm có các thuộc tính: + + id: mã định danh hoa hồng. Kiểu Integer + + bonus: tiền thưởng thêm. Kiểu Double + + commission_date: ngày tính hoa hồng. Kiểu Date + + daily_commission: hoa hồng trong ngày. Kiểu Decimal(12,2) + + note: ghi chú. Kiểu Text + + order_count: số đơn hàng giao trong ngày. Kiểu Integer + + user_id: mã ID của shipper. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +- shipper_salary_summary +Đây là bảng tổng kết lương hàng tháng của shipper +-Gồm có các thuộc tính: + + id: mã tổng kết lương. Kiểu Integer + + approved_leave_days: số ngày nghỉ được duyệt. Kiểu Integer + + base_salary: lương cơ bản. Kiểu Decimal(12,2) + + commission: tổng hoa hồng. Kiểu Decimal(12,2) + + created_at: thời điểm tạo bản ghi. Kiểu DateTime(6) + + date_deleted: thời điểm xóa bản ghi. Kiểu DateTime + + is_deleted: cờ xác định đã xóa hay chưa. Kiểu Boolean + + month: tháng tính lương. Kiểu Integer + + note: ghi chú. Kiểu Text + + total_orders: tổng số đơn hàng. Kiểu Integer + + total_salary: tổng thu nhập. Kiểu Decimal(12,2) + + updated_at: thời điểm cập nhật. Kiểu DateTime(6) + + working_days: số ngày làm việc. Kiểu Integer + + year: năm tính lương. Kiểu Integer + + user_id: mã ID của shipper. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + +""" \ No newline at end of file diff --git a/function/analyze/prompt_query.py b/function/analyze/prompt_query.py new file mode 100644 index 0000000000000000000000000000000000000000..586bf481955879b73a23079a0c85ab4c05682a44 --- /dev/null +++ b/function/analyze/prompt_query.py @@ -0,0 +1,93 @@ +query = """ +Bảng dữ liệu của người dùng: +{prompt_user} \n . +Hãy trình bày các yêu cầu lấy dữ liệu theo cấu trúc mẫu sau: + Yêu cầu: Lấy thông tin chi tiết về các voucher đã được áp dụng cho các đơn hàng đã thanh toán thành công (nếu có). + Mục đích: Phân tích việc sử dụng khuyến mãi từ voucher. + Nguồn: Các đơn hàng đã thanh toán thành công (từ Yêu cầu 2, đặc biệt là cột voucher_id) và bảng voucher. + Kết nối/Lọc: Kết nối các đơn hàng từ Yêu cầu 2 (mà có voucher_id IS NOT NULL) với bảng voucher thông qua voucher_id. + Dữ liệu cần lấy: order_id, key_voucher, discount (giá trị của voucher), start_date, end_date, status (trạng thái của voucher). + +*** Đây là mẫu example toàn diện hãy tham khảo và tùy tình huống mà bạn sẽ thay đổi cách tiếp cận của các yêu cầu lấy data: + +Yêu cầu 1: Lấy danh sách tất cả các đơn hàng của bạn đã được tạo trong tháng 4 năm [CURRENT_YEAR]. +Mục đích: Tạo tập hợp ban đầu các đơn hàng có tiềm năng cho phân tích. +Nguồn: Bảng orders. +Lọc: user_id = [ID người dùng của bạn] + Năm của order_date hoặc date_created là [CURRENT_YEAR] (Tôi sẽ ưu tiên order_date nếu có dữ liệu thực tế, nếu không thì dùng date_created). + Tháng của order_date hoặc date_created là 4. + is_deleted = FALSE (Không lấy các đơn hàng đã bị xóa logic). +Dữ liệu cần lấy: order_id, order_date (hoặc date_created), total_price (tổng tiền sản phẩm trước phí ship/giảm giá), delivery_fee, discount_price (giảm giá từ voucher/khác), point_coin_use, voucher_id, status (trạng thái đơn hàng). + + +Yêu cầu 2 : Từ danh sách đơn hàng ở Yêu cầu 1, xác định những đơn hàng nào đã được thanh toán thành công. +Mục đích: Chỉ phân tích các giao dịch thực sự đã hoàn thành và chi tiền. +Nguồn: Kết quả từ Yêu cầu 1 và bảng payments. +Kết nối/Lọc: Kết nối kết quả từ Yêu cầu 1 với bảng payments thông qua order_id. + Chỉ giữ lại các bản ghi từ bảng payments có status = 'COMPLETED'. + Chỉ giữ lại các bản ghi từ Yêu cầu 1 mà có bản ghi payments tương ứng với status = 'COMPLETED'. +Dữ liệu cần lấy: Tất cả các cột từ Yêu cầu 1 cho các đơn hàng đã thanh toán thành công, cộng thêm payment_id, amount (số tiền thanh toán cuối cùng), payment_method, status (của thanh toán) từ bảng payments. +Kết quả quan trọng: Một danh sách cuối cùng các order_id của các đơn hàng đã thanh toán thành công trong tháng 4. Đây là tập dữ liệu chính cho các phân tích sâu hơn. + + +Yêu cầu 3: Lấy chi tiết các sản phẩm (item) có trong các đơn hàng đã thanh toán thành công (đã xác định ở Yêu cầu 2). +Mục đích: Biết bạn đã mua những gì, số lượng bao nhiêu, giá tiền của từng mặt hàng. +Nguồn: Kết quả từ Yêu cầu 2 và bảng order_item. +Kết nối/Lọc: Kết nối danh sách order_id từ Yêu cầu 2 với bảng order_item thông qua order_id. + Chỉ giữ lại các bản ghi từ order_item có is_deleted = FALSE. +Dữ liệu cần lấy: order_item_id, order_id (để liên kết lại đơn hàng), pro_id, quantity, total_price (tổng tiền cho item này), size, note. + + +Yêu cầu 4 : Bổ sung thông tin chi tiết về sản phẩm và danh mục cho các item đã lấy ở Yêu cầu 3.(Yêu cầu này luôn luôn có khi thực hiện) +Mục đích: Có thể phân tích dựa trên tên sản phẩm và tên danh mục (thay vì chỉ ID). +Nguồn: Kết quả từ Yêu cầu 3, bảng product, và bảng category. +Kết nối: Kết nối kết quả từ Yêu cầu 3 với bảng product thông qua pro_id (từ order_item và product). + Kết nối kết quả đó với bảng category thông qua category_id (từ product) và cate_id (từ category). +Dữ liệu cần lấy: Tất cả các cột từ Yêu cầu 3, cộng thêm pro_name (từ product), cate_id, cate_name (từ category). (Có thể cần cân nhắc bảng product_translation/category_translation nếu cần tên theo ngôn ngữ cụ thể). + + +Yêu cầu 5: Lấy thông tin chi tiết về các voucher đã được áp dụng cho các đơn hàng đã thanh toán thành công (nếu có). +Mục đích: Phân tích việc sử dụng khuyến mãi từ voucher. +Nguồn: Các đơn hàng đã thanh toán thành công (từ Yêu cầu 2, đặc biệt là cột voucher_id) và bảng voucher. +Kết nối/Lọc: Kết nối các đơn hàng từ Yêu cầu 2 (mà có voucher_id IS NOT NULL) với bảng voucher thông qua voucher_id. + Dữ liệu cần lấy: order_id, key_voucher, discount (giá trị của voucher), start_date, end_date, status (trạng thái của voucher). + + +Yêu cầu 6 : Lấy thông tin về phương thức thanh toán đã dùng. +Mục đích: Hiểu cách bạn thanh toán cho các đơn hàng. +Nguồn: Các bản ghi thanh toán đã xác định ở Yêu cầu 2 (từ bảng payments). +Dữ liệu cần lấy: order_id, payment_method. + +*** +## Hãy tham khảo mẫu trên và tùy tình huống hãy làm tuong tự như vậy để lấy ra các yêu cầu lấy data. +### Hãy gom các yêu cầu lấy data sao cho hợp lý. chỉ tầm 5-6 yêu cầu là đủ. +### Hãy nhớ là nếu phân tính mua sắm trong tháng, năm mà không đề cập so sánh gì cả thì mặc định bạn sẽ so sánh so với tháng trước đó. +### Các yêu cầu lấy dữ liệu cần được trình bày rõ ràng. +Hãy giả sử bạn là một chuyên gia trong lĩnh vực Data Analyst. Bây giờ tôi muốn bạn thực hiện câu hỏi là: {question}. +**Chỉ tạo tầm 5-6 bước(step) không dduocjwj phép tạo nhiều step quá 6**. + + +Vậy thì bạn sẽ làm thế nào để từ việc lấy dữ liệu tới phân tích hãy trình bày rõ và ngoài ra trong bước lấy dữ liệu bạn sẽ có những câu hỏi nào để lấy dữ liệu trong MySQL. Nếu viết thành yêu cầu và đánh số bạn sẽ viết nó thành yêu cầu nhu thế nào. Trình bày rõ ràng các bước và lưu ý mỗi bước bạn nhóm vào tag để đánh dấu cho mình. +Quan trong các bước để lấy data đều nằm trong . +Hãy chú ý các tham số sau: nếu không có tháng, năm thì bạn sẽ lấy tất cả các tháng, năm trong bảng dữ liệu. +Khi phân tích mua sắm luôn có yêu cầu lấy chi tiết các sản phẩm họ đã mua. +Nếu không có ngày thì bạn sẽ lấy tất cả các ngày trong tháng. +### Hãy lưu ý các điều sau đây: +** Nếu bạn làm theo câu sql trên bạn sẽ nhận lỗi vì order_item không có pro_id mà chỉ có order_item_id, order_id, quantity, total_price, size, note. +**Tránh các lỗi sau đây: ** Khi xây d +(1054, "Unknown column 'oi.pro_id' in 'field list'") +(1054, "Unknown column 'oi.pro_id' in 'field list'") +(1054, "Unknown column 'oi.note' in 'field list'") +(1054, "Unknown column 'oi.size' in 'field list'") +(1054, "Unknown column 'c.is_deleted' in 'on clause'") +** +**Vui lòng không check lại các câu sql này vì nó đã được kiểm tra và chạy thành công trên hệ thống của tôi. Tức là bạn không cần cái check is_deleted chỗ này + AND oi.is_deleted = 0 + AND ci.is_deleted = 0 + AND pv.is_deleted = 0 + AND p.is_deleted = 0 + AND cat.is_deleted = 0; + c.is_deleted = 0 + ** + +""" diff --git a/function/analyze/return_code_python.py b/function/analyze/return_code_python.py new file mode 100644 index 0000000000000000000000000000000000000000..3e19b286ad0b0a9827f302dcf1eb0830fe2abed0 --- /dev/null +++ b/function/analyze/return_code_python.py @@ -0,0 +1,98 @@ +import os +import time +import google.generativeai as genai + +genai.configure(api_key="AIzaSyBOnrOqFe1xEkUxPiN9wmAIg2ccW2AICeM") + +def return_code_python(query, folder, max_retries=3): + generation_config = { + "temperature": 0.7, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 64000, + "response_mime_type": "text/plain", + } + + model = genai.GenerativeModel( + model_name="gemini-2.5-flash-preview-05-20", + generation_config=generation_config, + ) + + # Prompt nội dung sinh mã + prompt = f""" +Bạn là một chuyên gia trong lĩnh vực Trực quan hóa dữ liệu và Phân tích dữ liệu. +Bạn nhận được một yêu cầu step và nhận lại được kết quả như dưới: +{query}. \n +Viết đoạn mã Python để trực quan hóa dữ liệu (**do tôi cung cấp, không được phép tự sinh dữ liệu**) bằng nhiều loại biểu đồ khác nhau phù hợp với từng đặc điểm của dữ liệu. Cụ thể: +Không sử dụng dữ liệu giả lập — chỉ dùng đúng dữ liệu do tôi đưa. +Vui lòng sử dụng chính xác tên biến, cấm sai sót. + +**Các biểu đồ cần triển khai gồm**: + + Biểu đồ cột (Bar Chart, Column chart) để so sánh chi tiêu giữa các danh mục. + + Biểu đồ đường (Line Chart) để theo dõi xu hướng chi tiêu theo thời gian (nếu có cột thời gian). + + Biểu đồ cột nhóm (Grouped Bar Chart) để so sánh chi tiêu giữa các tháng theo danh mục. + + Biểu đồ cột xếp chồng (Stacked Bar Chart) để tổng hợp chi tiêu các danh mục theo tháng. + + Biểu đồ diện tích (Area Chart) nếu có dữ liệu thời gian cần theo dõi biến động. + + Vẽ biểu đồ Donut hai lớp (Nested Donut Chart) trực quan, chuyên nghiệp: + + Biểu đồ donut pie chart: + - Ở giữa vòng donut, bạn có thể chèn một con số tổng quát (ví dụ: tổng số đơn hàng, % trung bình, doanh thu…) + - Chèn mini-legend bên phải: + + Hiện tại mỗi lát đã có label, nhưng bạn có thể: + + Gom các danh mục lại bên phải dạng bảng nhỏ để dễ nhìn tổng thể hơn. + - Tăng khoảng cách hiển thị nhãn bằng cách điều chỉnh pctdistance (nếu dùng matplotlib). + - Giảm kích thước font chữ cho các nhãn phần trăm. + - Tăng kích thước biểu đồ tổng thể để có thêm không gian hiển thị. + + Biểu đồ Area graph. + + Biểu đồ cột ngang (Horizontal Bar Chart) nếu tên danh mục quá dài. + + Biểu đồ tròn (Pie Chart) để thể hiện: + - % tỷ lệ chi tiêu theo danh mục (bao gồm cả chung và tháng riêng lẻ). + - % tỷ lệ số lượng theo danh mục (bao gồm cả chung và tháng riêng lẻ). + +**Sau khi vẽ xong**: + + Lưu tất cả hình ảnh vào thư mục: {folder} (tạo nếu chưa tồn tại). + + Phân tích và đưa ra nhận xét trực tiếp trong code Python (dưới mỗi biểu đồ hoặc cuối cùng). + +**Yêu cầu**: + + Không xuất biểu đồ ra web, chỉ vẽ hình cục bộ (matplotlib, seaborn...). + + Hình ảnh trực quan, màu sắc đẹp mắt, dễ nhìn (ưu tiên dùng màu sáng pha các màu đẹp, không bị chói mắt). Pie chart dùng màu sáng không dùng các màu tối. + + Code Python phải gọn gàng, tối ưu, dễ đọc và tránh lỗi. + + Sử dụng tất cả dữ liệu mình cung cấp. + + Không được sử dụng sai các trường trong Python. + + Nếu xác định có dùng "autotext" thì phải thêm: autopct='%1.1f%%' + + Check lỗi toàn bộ trước khi trả về. + + Không trả lời thêm thông tin nào khác ngoài code Python. + + Không để tên các loại chart trên hình (ví dụ: Line Chart). + + Chỉ được phép sử dụng dữ liệu mình cung cấp. + + Vui lòng thực hiện đầy đủ các biểu đồ mà mình yêu cầu. +""" + + retries = 0 + while retries <= max_retries: + try: + chat_session = model.start_chat(history=[ + { + "role": "user", + "parts": [prompt] + } + ]) + + response = chat_session.send_message( + "Vui lòng trả lời bằng đoạn code Python. Chỉ được phép sử dụng dữ liệu mình đưa ra cấm tự sinh dữ liệu. Vui lòng gửi toàn bộ đoạn code đầy đủ không thiếu dòng nào." + ) + + # Trả về text (sẽ lỗi nếu Gemini không trả về nội dung hợp lệ) + result = response.text + return result + + except ValueError as ve: + print(f"[Gemini Warning] ❌ Không có response.text hợp lệ (retry {retries+1}/{max_retries}) ➜ {ve}") + retries += 1 + time.sleep(1) + + except Exception as e: + print(f"[Gemini Error] ❌ Lỗi khác xảy ra (retry {retries+1}/{max_retries}) ➜ {e}") + retries += 1 + time.sleep(1) + + print("❌ Đã retry quá số lần cho phép. Trả về chuỗi rỗng.") + return "" diff --git a/function/chat.py b/function/chat.py new file mode 100644 index 0000000000000000000000000000000000000000..a4e8c271cc0c68a2c9a1a28a14e1cc4fca766fd4 --- /dev/null +++ b/function/chat.py @@ -0,0 +1,860 @@ +from langchain_community.utilities.sql_database import SQLDatabase +from langchain_experimental.sql import SQLDatabaseChain +import sys +import os +import pymysql +from fastapi import HTTPException +from fastapi.encoders import jsonable_encoder +import re +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "."))) +import prompt.prompt_main as prompt +import prompt.prompt_custom as prompt_cus +os.environ["GOOGLE_API_KEY"] = "AIzaSyBoPdVLTJNxfDg9wxWDpY4QJezHiyjKbTE" +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +from bson import ObjectId +import os +from dotenv import load_dotenv +import os +from dotenv import load_dotenv +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(), override=True) + + + +DB_HOST = os.getenv("DB_HOST") +DB_USER = os.getenv("DB_USER") +DB_PASSWORD = os.getenv("DB_PASSWORD") +DB_NAME = os.getenv("DB_NAME") +DB_PORT = os.getenv("DB_PORT") +# Tạo connection string +import os +from urllib.parse import quote + +password = os.getenv("DB_PASSWORD") # VD: 'Yahana0509@' +DB_PASSWORD = quote(password) +connection_uri = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" +db = SQLDatabase.from_uri(connection_uri) +from dotenv import load_dotenv +import filter.filter_role as filter_role_1 +import filter.filter_sql_injection as filter_sql_injection_1 +import filter.result as query_result_1 +import support.get_key as get_key +import response.ResponseChat as res_chat +from datetime import datetime +import pytz +from mongoengine import connect +import sys +import os +import nltk +import function.agent.pipeline_agent as pipeline_agent +nltk.download('punkt') +from models.Database_Entity import User, ChatHistory, DetailChat +from dotenv import load_dotenv +load_dotenv() +MONGO_URI = os.getenv("MONGO_URI", "") +connect("chatbot_hmdrinks", host=MONGO_URI) + +load_dotenv() + +#setup model +from bson import ObjectId +import random +from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI + + + +BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) +sys.path.insert(0, BASE_DIR) +from repository.MySQL import UserRepository +from prompt.prompt_syntax_insert import is_insert_related_to_product_category_variant, filter_syntax_sql +import sqlparse +import re +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.getcwd(), '..'))) +from prompt import prompt_detail_table + +schema_mapping = { + "user": prompt_detail_table.prompt_users, + "user_voucher": prompt_detail_table.prompt_user_voucher, + "category": prompt_detail_table.prompt_categort, + "category_translation": prompt_detail_table.prompt_category_translation, + "cart": prompt_detail_table.prompt_cart, + "cart_item": prompt_detail_table.prompt_cart_item, + "orders":prompt_detail_table.prompt_orders, + "order_item": prompt_detail_table.prompt_order_item, + "payment": prompt_detail_table.prompt_payments, + "payments": prompt_detail_table.prompt_payments, + "favourite": prompt_detail_table.prompt_favourite, + "favourite_item": prompt_detail_table.prompt_fav_item, + "post": prompt_detail_table.prompt_post, + "post_translation": prompt_detail_table.prompt_post_translation, + "product": prompt_detail_table.prompt_product, + "product_translation": prompt_detail_table.prompt_product_translation, + "shipment": prompt_detail_table.prompt_shipment, + "product_variants": prompt_detail_table.prompt_product_variants, + "review": prompt_detail_table.prompt_review, + "user_coin": prompt_detail_table.prompt_user_coin, + "absence": prompt_detail_table.prompt_absence, + "cart_group": prompt_detail_table.prompt_cart_group, + "cart_item_group": prompt_detail_table.prompt_cartitem_group, + "group_orders": prompt_detail_table.prompt_group_orders, + "payments_group": prompt_detail_table.prompt_payments_group, + "group_order_members":prompt_detail_table.prompt_group_orders_member, + "shipment_group":prompt_detail_table.prompt_shipment_group, + "shipper_attendance":prompt_detail_table.prompt_shipper_attendance, + "shipper_commission_detail":prompt_detail_table.prompt_shipper_commission_detail, + "shipper_salary_summary":prompt_detail_table.prompt_shipper_salary_summary, + "voucher": prompt_detail_table.prompt_voucher + } + + +def get_schemas_from_sql(sql_query: str, schema_mapping: dict): + import sqlglot + + parsed_query = sqlglot.parse_one(sql_query, read="mysql") + + # Lấy danh sách bảng duy nhất trong query + table_names = list({t.name for t in parsed_query.find_all(sqlglot.exp.Table)}) + + schemas_used = {} + for table in table_names: + if table in schema_mapping: + schemas_used[table] = schema_mapping[table] + else: + print(f"⚠️ Warning: Table '{table}' not found in schema_mapping") + + # Gom toàn bộ schema thành 1 chuỗi duy nhất + all_schemas = "\n\n".join( + [f"Schema for table '{table}':\n{schemas_used[table]}" for table in schemas_used] + ) + + return all_schemas + + +def build_sql_fix_prompt(schemas_result: dict, sql: str) -> str: + + prompt = f""" +Bạn là một chuyên gia cơ sở dữ liệu. + +Dưới đây là mô tả schema chi tiết của các bảng có trong hệ thống: +Đọc rõ và ghi nhớ tùng thuộc tính của mỗi bảng mà bạn truy vấn: +{schemas_result} + +--- + +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: + +```sql +{sql.strip()} +Yêu cầu của bạn là: + +Dựa trên các schema ở trên, hãy kiểm tra và chỉnh sửa câu SQL sao cho: +Tên bảng, tên cột(thuộc tính) phải chính xác theo trình bày bên trong schema. +Nếu tên cột trong bảng đó có mô tả trong schema mà trình bày khác thì phải thay đổi sao cho cú pháp sql chính xác. +Logic và mục đích của truy vấn được giữ nguyên. +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). +Trả lời dưới dạng một truy vấn SQL duy nhất. +Không dùng pt.`language_ code` = 'EN +Với các câu hỏi liên quan đến product luôn trả về kèm theo pro_id cho mình. +- "- Tránh các lỗi như :\n" + " (1054, \"Unknown column 'oi.pro_id' in 'field list'\")\n . Luôn đảm bảo bạn không bao giờ bị lỗi này" + " (1054, \"Unknown column 'oi.note' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này\n" + " (1054, \"Unknown column 'oi.size' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này \n" + " (1054, \"Unknown column 'c.is_deleted' in 'on clause'\"). Luôn đảm bảo bạn không bao giờ bị lỗi này\n" +""".strip() + return prompt + + +def contains_delete(sql: str) -> bool: + return bool(re.search(r'\bdelete\b', sql, re.IGNORECASE)) + +async def execute_query_user(user_input: str, user_id: int, languages: str, role: str): + api_key = get_key.get_random_api_key() + os.environ["GOOGLE_API_KEY"] = api_key + llm1 = ChatGoogleGenerativeAI(model='gemini-2.0-flash-thinking-exp-01-21',temperature=0.2) + db = SQLDatabase.from_uri(connection_uri) + PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_input) + check_insert = is_insert_related_to_product_category_variant(user_input) + + db_config = { + "host": os.getenv("DB_HOST"), + "user": os.getenv("DB_USER"), + "database": os.getenv("DB_NAME"), + "password": os.getenv("DB_PASSWORD"), + "port": int(os.getenv("DB_PORT", 3306)), + "charset": "utf8mb4", + "cursorclass": pymysql.cursors.DictCursor, + } + + def regenerate_sql_until_safe(): + max_retry = 5 + retry_count = 0 + + while retry_count < max_retry: + try: + regenerated_data = db_chain.run(f""" + Role: {text_role} + Language: {languages} + Question: {user_input}. + """) + regenerated_sql = extract_sql_from_response(regenerated_data) + if regenerated_sql: + regenerated_sql = clean_sql(regenerated_sql) + if not re.search(r"%{1,2}s", regenerated_sql): # đã sạch + return regenerated_sql + retry_count += 1 + except Exception as e: + return f"❌ Lỗi khi tạo lại truy vấn lần {retry_count + 1}: {str(e)}" + return "❌ Lỗi: Không thể tạo được truy vấn an toàn sau nhiều lần thử." + def execute_query_with_pymysql(query, multi=False): + connection = pymysql.connect(**db_config) + try: + with connection.cursor() as cursor: + results = [] + if multi: + statements = sqlparse.split(query) + for stmt in statements: + stmt = stmt.strip() + if stmt: + try: + cursor.execute(stmt) + try: + results.append(cursor.fetchall()) + except pymysql.ProgrammingError: + results.append("✅ Executed") + except Exception as e: + results.append(f"❌ Error in query: {stmt}\n{str(e)}") + else: + try: + cursor.execute(query) + results = cursor.fetchall() + except Exception as e: + return f"❌ Error executing query: {str(e)}" + connection.commit() + return results + except pymysql.MySQLError as e: + return f"❌ MySQL Error: {str(e)}" + finally: + connection.close() + + def clean_sql(sql) -> str: + if isinstance(sql, dict) and sql: + first_value = next(iter(sql.values())) + sql = first_value + sql = re.sub(r"```sql", "", sql, flags=re.IGNORECASE) + sql = sql.replace("```sql", "") + sql = re.sub(r"```", "", sql) + return sql.strip() + + def extract_sql_from_response(data): + # Định dạng markdown block [SQL: ```sql ... ```] + match = re.search(r"\[SQL:\s*```sql\s*(.*?)\s*```]", data, re.DOTALL) + if match: + return clean_sql(match.group(1)) + + # Định dạng đơn giản SQLQuery: ... + match = re.search(r"SQLQuery:\s*(.*)", data, re.DOTALL) + if match: + return clean_sql(match.group(1)) + + return None + + def extract_sql_from_error(error_msg): + # Case 1: [SQL: ```sql ... ```] + match = re.search(r"\[SQL:\s*```sql\s*(.*?)\s*```]", error_msg, re.DOTALL) + if match: + return clean_sql(match.group(1)) + match = re.search(r"```(?:sql)?\s*\r?\n(.*?)```", error_msg, re.DOTALL) + if match: + return clean_sql(match.group(1)) + + return None + + def process_and_execute_sql(sql): + data = sql + if isinstance(data, dict) and data: + first_key, first_value = next(iter(data.items())) + sql = first_value + elif isinstance(data, list) and data: + sql = "\n\n".join(data) + sql_clean = clean_sql(sql) + if re.search(r"%{1,2}s", sql_clean): + regenerated_sql = regenerate_sql_until_safe() + sql_clean = clean_sql(regenerated_sql) + result = get_schemas_from_sql(sql_clean, schema_mapping) + print("result",result) + prompt = build_sql_fix_prompt(result,sql =sql_clean) + from advance_shopping.call_gemini import tool_call + data = tool_call.generate(prompt = prompt) + sql_clean = clean_sql(data) + print("SQL step2: ", sql_clean) + if sql_clean == None: + return "❌ Lỗi không thể thực thi truy vấn: Không tìm thấy SQL trong phản hồi." + if contains_delete(sql_clean): + 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." + if re.search(r"\\bIF\\b.*\\bTHEN\\b", sql_clean, re.IGNORECASE): + return "❌ Lỗi: Không được dùng IF...THEN trong SQL. Vui lòng chia nhỏ truy vấn." + + if check_insert: + check_syntax = filter_syntax_sql(sql_clean, PROMPT_CUSTOM, user_input) + + if check_syntax is True: + try: + connection = pymysql.connect(**db_config) + with connection.cursor() as cursor: + statements = sqlparse.split(sql_clean) + results = [] + for stmt in statements: + stmt = stmt.strip() + if stmt: + try: + cursor.execute(stmt) + try: + results.append(cursor.fetchall()) + except: + results.append("✅ OK") + except Exception as e: + return f"❌ Lỗi tại truy vấn: `{stmt}`\nChi tiết: {str(e)}" + connection.commit() + return results + except Exception as e: + return f"❌ Lỗi khi thực thi từng truy vấn: {str(e)}" + finally: + connection.close() + else: + try: + regenerated_data = db_chain.run(f""" + Role: {text_role} + Language: {languages} + Question: {user_input}. + """) + regenerated_sql = extract_sql_from_response(regenerated_data) + if regenerated_sql: + return process_and_execute_sql(regenerated_sql) + else: + return "❌ Lỗi: Không thể tạo lại truy vấn hợp lệ." + except Exception as regen_error: + return f"❌ Lỗi khi tạo lại truy vấn: {str(regen_error)}" + else: + return execute_query_with_pymysql(sql_clean, multi=True) + + if "Do not use IF...THEN" not in PROMPT_CUSTOM.template: + PROMPT_CUSTOM.template += ( + "\n\n⚠️ Note: Do NOT use IF...THEN...ELSE...END in SQL. " + "Only use plain SELECT, INSERT, UPDATE, DELETE, SET statements." + ) + + db_chain = SQLDatabaseChain.from_llm(llm=llm1, db=db, prompt=PROMPT_CUSTOM) + text_role = f"{role} (userId = {user_id})" if role == "ADMIN" else f"{role} (userId = {user_id}), not role ADMIN" + try: + data = db_chain.run(f""" + Role: {text_role} + Language: {languages} + Question: {user_input}. + """) + extracted_sql = extract_sql_from_response(data) + if extracted_sql: + print("SQL:",extracted_sql) + print(extracted_sql) + return process_and_execute_sql(extracted_sql) + else: + return data + + except Exception as e: + error_message = str(e) + print("Lỗi: ",error_message) + extracted_sql = extract_sql_from_error(error_message) + print("SQL lỗi: ",extracted_sql) + if extracted_sql == None: + return "❌ Lỗi không thể thực thi truy vấn: Không tìm thấy SQL trong phản hồi." + fix_sql = re.sub(r"```sql", "", extracted_sql, flags=re.IGNORECASE) + fix_sql = re.sub(r"```", "", fix_sql) + fix_sql = re.sub(r'%%s', r'%s', fix_sql) + print(fix_sql) + print("SQL fix:",fix_sql) + if contains_delete(fix_sql): + 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." + if extracted_sql: + return process_and_execute_sql(fix_sql) + else: + return f"❌ Lỗi không thể thực thi truy vấn: {error_message}" + + +async def create_new_chat_history(user_id: int) -> str: + """ + Tạo một đoạn chat mới cho user_id và trả về ObjectId của đoạn chat. + """ + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=404, detail="User not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id).first() + if not user: + user = User(id=ObjectId(), user_id=user_id, user_name=f"User_{user_id}") + user.save() + random_name_chat = str(random.randint(10**14, 10**15 - 1)) + name_chat = f"Chat_{random_name_chat}" + new_chat = ChatHistory(id=ObjectId(), user=user, name_chat=name_chat) + new_chat.save() + + return res_chat.CreateNewChat(idMongo=str(new_chat.id), chat_name=name_chat) + + +async def update_chat_name(chat_id: str, new_name: str,user_id:int) -> str: + """ + Cập nhật name_chat của một ChatHistory dựa trên chat_id. + """ + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=404, detail="User not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + + chat = ChatHistory.objects(_id=ObjectId(chat_id)).first() + + if not chat: + raise HTTPException(status_code=404, detail="Chat not found in MongoDB") + + chat.name_chat = new_name + chat.save() + + return f"Updated chat name to {new_name}" + + +async def soft_delete_chat(chat_id: str,user_id:int): + """ + Cập nhật `is_deleted=True` và `date_deleted` cho `ChatHistory` và các `DetailChat` liên quan. + """ + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + + check_history_id = UserRepository.getChatHistory(user_id,chat_id) + if check_history_id is None: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + + chat = ChatHistory.objects(_id=ObjectId(chat_id)).first() + + if not chat: + raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MongoDB") + chat.is_deleted = True + chat.date_deleted = datetime.now(pytz.UTC) + chat.save() + + DetailChat.objects(chat_history=chat).update( + set__is_deleted=True, + set__date_deleted=datetime.now(pytz.UTC) + ) + + return {"message": "Chat and related details marked as deleted"} + + +from datetime import datetime, timedelta +async def chat_with_user( + user_input: str, + user_id: int, + languages: str, + role: str, + token: str, + chat_history_id: str = None, + +) -> str: + """ + 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. + """ + + if role not in ["ADMIN", "CUSTOMER", "SHIPPER"]: + raise HTTPException(status_code=400, detail="ROLE not valid") + user_id = int(user_id) + if languages not in ["VN", "EN"]: + raise HTTPException(status_code=400, detail="Language not valid") + + if not user_input: + raise HTTPException(status_code=400, detail="User input empty") + if not isinstance(user_id, int) or user_id <= 0: + raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer") + + languages = "Vietnamese" if languages == "VN" else "English" + + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + + check_history_id = UserRepository.getChatHistory(user_id,chat_history_id) + if check_history_id is None: + raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id).first() + if not user: + return {"error": "User not found or has been deleted in MongoDB"} + + chat_history = None + if chat_history_id: + try: + chat_history_obj_id = ObjectId(chat_history_id) + chat_history = ChatHistory.objects(_id=chat_history_obj_id, user=user).first() + except Exception as e: + + print(f"⚠️ Invalid chat_history_id: {e}") + if not chat_history: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB") + # filtered_input = filter_sql_injection_1.filter_sql_injection(user_input) + # filtered_role_input = filter_role_1.filter_role(filtered_input) + # result = await execute_query_user(filtered_role_input, user_id, languages, role) + # result_final = query_result_1.query_result(user_input, result) + result_final = await pipeline_agent.multi_query_user(user_input,user_id,role,languages,chat_history_id, token) + + detail_chat = DetailChat( + id=ObjectId(), + chat_history=chat_history, + you_message=user_input, + ai_message=result_final, + timestamp=datetime.now(pytz.UTC) + ) + detail_chat.save() + chat_history_messages = DetailChat.objects(chat_history=chat_history).order_by('timestamp') + + + def convert_to_vn_time(timestamp): + return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S') + + sorted_messages = sorted(chat_history_messages, key=lambda msg: msg.timestamp, reverse=True) + + formatted_messages = [ + { + "index": i + 1, # Đánh số từ 1 + "id": str(msg.id), + "you_message": msg.you_message, + "ai_message": msg.ai_message, + "timestamp": convert_to_vn_time(msg.timestamp) + } + for i, msg in enumerate(sorted_messages) + ] + + return jsonable_encoder({ + "new_message": { + "id": str(detail_chat.id), + "you_message": detail_chat.you_message, + "ai_message": detail_chat.ai_message, + "timestamp": convert_to_vn_time(detail_chat.timestamp) + }, + "previous_messages": formatted_messages + }) + +from bson import ObjectId + + +async def get_chat_details(chat_id: str,user_id:int): + """ + Lấy tất cả `DetailChat` thuộc `ChatHistory` có `chat_id`, chỉ lấy bản ghi `is_deleted=False`. + """ + + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + + check_history_id = UserRepository.getChatHistory(user_id,chat_id) + if check_history_id is None: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + + chat = ChatHistory.objects(_id=ObjectId(chat_id), is_deleted=False).first() + + if not chat: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB") + + chat_details = DetailChat.objects(chat_history=chat, is_deleted=False) + def convert_to_vn_time(timestamp): + return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S') + + list_detail_response = [ + res_chat.DetailResponse( + id=str(index + 1), # ✅ Đánh số lại từ 1 + you_message=detail.you_message, + ai_message=detail.ai_message, + timestamp=convert_to_vn_time(detail.timestamp) # ✅ Chuyển sang GMT+7 + ) + for index, detail in enumerate(sorted(chat_details, key=lambda d: d.timestamp, reverse=True)) # ✅ Sắp xếp giảm dần +] + + return res_chat.ListDetailResponse( + chat_id=str(chat.id), + chat_name=chat.name_chat, + list_detail_response=list_detail_response +) + + + +async def regenerate( + user_question_new: str, + user_id: int, + languages: str, + role: str, + chat_history_id: str = None +) -> str: + """ + 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. + """ + PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_question_new) + if role not in ["ADMIN", "CUSTOMER", "SHIPPER"]: + raise HTTPException(status_code=400, detail="ROLE not valid") + user_id = int(user_id) + if languages not in ["VN", "EN"]: + raise HTTPException(status_code=400, detail="Language not valid") + + if not user_question_new: + raise HTTPException(status_code=400, detail="User input empty") + if not isinstance(user_id, int) or user_id <= 0: + raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer") + + languages = "Vietnamese" if languages == "VN" else "English" + + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + + check_history_id = UserRepository.getChatHistory(user_id,chat_history_id) + if check_history_id is None: + raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id).first() + if not user: + return {"error": "User not found or has been deleted in MongoDB"} + + chat_history = None + if chat_history_id: + try: + chat_history_obj_id = ObjectId(chat_history_id) # Chuyển đổi sang ObjectId + chat_history = ChatHistory.objects(_id=chat_history_obj_id, user=user).first() + except Exception as e: + + print(f"⚠️ Invalid chat_history_id: {e}") + if not chat_history: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB") + # filtered_input = filter_sql_injection_1.filter_sql_injection(user_input) + # filtered_role_input = filter_role_1.filter_role(filtered_input) + # result = await execute_query_user(filtered_role_input, user_id, languages, role) + # result_final = query_result_1.query_result(user_input, result) + if chat_history: + last_chat = ( + DetailChat.objects(chat_history=chat_history, is_deleted=False) + .order_by("-timestamp") + .first() + ) + + # if last_chat: + # print(f"Last chat - You: {last_chat.you_message}, AI: {last_chat.ai_message}") + # else: + # print("⚠️ No chat details found for this history.") + result_final = await pipeline_agent.multi_query_user(user_question_new,user_id,role,languages,chat_history_id) + last_chat.update(set__you_message=user_question_new, set__ai_message=result_final, set__timestamp = datetime.now(pytz.UTC)) + + last_chat_result = ( + DetailChat.objects(chat_history=chat_history, is_deleted=False) + .order_by("-timestamp") + .first() + ) + + # detail_chat = DetailChat( + # id=ObjectId(), + # chat_history=chat_history, + # you_message=user_input, + # ai_message=result_final, + # timestamp=datetime.now(pytz.UTC) + # ) + # detail_chat.save() + chat_history_messages = DetailChat.objects(chat_history=chat_history).order_by('timestamp') + + + def convert_to_vn_time(timestamp): + return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S') + + sorted_messages = sorted(chat_history_messages, key=lambda msg: msg.timestamp, reverse=True) + + formatted_messages = [ + { + "index": i + 1, # Đánh số từ 1 + "id": str(msg.id), + "you_message": msg.you_message, + "ai_message": msg.ai_message, + "timestamp": convert_to_vn_time(msg.timestamp) + } + for i, msg in enumerate(sorted_messages) + ] + + return jsonable_encoder({ + "new_message": { + "id": str(last_chat_result.id), + "you_message": last_chat_result.you_message, + "ai_message": last_chat_result.ai_message, + "timestamp": convert_to_vn_time(last_chat_result.timestamp) + }, + "previous_messages": formatted_messages + }) + +from bson import ObjectId + + +async def get_user_chat_history(user_id: int): + """ + API lấy danh sách tất cả các đoạn chat của một user_id. + """ + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + chat_histories = ChatHistory.objects(user=user, is_deleted=False) + chat_list = [ + res_chat.ChatResponse( + chat_id=str(chat.id), + chat_name=chat.name_chat, + timestamp=chat.date_deleted if chat.is_deleted else chat.id.generation_time + ) + for chat in chat_histories + ] + + return res_chat.UserChatHistoryResponse( + user_id=user.user_id, + user_name=user.user_name, + chat_list=chat_list + ) + + +from bson import ObjectId + +async def get_chat_details_text(chat_id: str, user_id: int): + """ + 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. + """ + # Kiểm tra xem user có tồn tại không + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + + # Kiểm tra xem chat history có tồn tại không + chat = ChatHistory.objects(_id=ObjectId(chat_id), user=user, is_deleted=False).first() + if not chat: + raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MongoDB") + + # Lấy tất cả các chi tiết chat liên quan + chat_details = DetailChat.objects(chat_history=chat, is_deleted=False).order_by('timestamp') + + if not chat_details: + return list() + + # Gom tất cả các câu hỏi và câu trả lời vào danh sách + chat_text_list = [] + for index, detail in enumerate(chat_details,start=1): + chat_text_list.append({ + "order":str(index), + "timestamp": detail.timestamp.strftime("%Y-%m-%d %H:%M:%S"), + "you_message": detail.you_message, + "ai_message": detail.ai_message + }) + + return chat_text_list + + +import asyncio +import os +import subprocess +from datetime import datetime +from pathlib import Path +from function.analyze import main +import asyncio +from models.Database_Entity import StopSignal +from function.analyze.gemini import result_analyze +async def check_should_stop(chat_id: str, stop_event: object = None): + # Trường hợp dừng qua RAM (in-memory) + await asyncio.sleep(0.1) + if stop_event and stop_event.is_set(): + print("🛑 Dừng qua stop_event.") + return {"status": "cancelled"} + + # Trường hợp dừng qua MongoDB + await asyncio.sleep(0.1) + if StopSignal.objects(chat_history=chat_id, is_stopped=True).first(): + print("🛑 Dừng vì có StopSignal trong DB.") + return {"status": "cancelled"} + + return None + +from typing import Optional +async def generate_and_save_code(question: str, user_id: int, role, languages: str,stop_event: Optional[asyncio.Event], filename: str = "analyze_result.py",chat_id: str = ""): + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + code_test, folder_name = await main.analyze( + question, + user_id, + languages, + role, + chat_id, + stop_event + ) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + code_clean = code_test.strip() + if code_clean.startswith("```python"): + code_clean = code_clean[9:].strip() + if code_clean.endswith("```"): + code_clean = code_clean[:-3].strip() + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + # code_clean = code_clean.replace("else:", "").strip() + code_clean = code_clean.replace("os_path:", "os.path").strip() + encoding_fix = 'import sys\nsys.stdout.reconfigure(encoding="utf-8")\n\n' + encoding_fix1= 'import numpy as np\n\n' + code_clean = encoding_fix + encoding_fix1 + code_clean + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + output_dir = Path(f"./Temp/{folder_name}_{timestamp}") + output_dir.mkdir(parents=True, exist_ok=True) + + file_path = output_dir / filename + with open(file_path, "w", encoding="utf-8") as f: + f.write(code_clean) + + + env = os.environ.copy() + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + env["OUTPUT_DIR"] = str(output_dir) + result = subprocess.run( + ["python", filename], + capture_output=True, + text=True, + env=env, + cwd=output_dir, + encoding="utf-8", + errors="replace" + ) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + output_folder = str(output_dir) + absolute_path = os.path.abspath(output_folder) + final_path = os.path.join(absolute_path, "test5") + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + result_final = result_analyze.generate(image_folder=final_path,question=question) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + return result_final, final_path diff --git "a/function/data/GI\341\273\232I THI\341\273\206U C\303\224NG TY.docx" "b/function/data/GI\341\273\232I THI\341\273\206U C\303\224NG TY.docx" new file mode 100644 index 0000000000000000000000000000000000000000..c4cfac5821e26cc9a137e6348c28912f836b1d9b Binary files /dev/null and "b/function/data/GI\341\273\232I THI\341\273\206U C\303\224NG TY.docx" differ diff --git a/function/file/extract_file.py b/function/file/extract_file.py new file mode 100644 index 0000000000000000000000000000000000000000..014f29f0a4a12437addc4255f0b89a8c634ee6e8 --- /dev/null +++ b/function/file/extract_file.py @@ -0,0 +1,662 @@ +from langchain_community.vectorstores import FAISS +import os +from langchain.text_splitter import CharacterTextSplitter +import json +import os +import random +import re +from concurrent.futures import ThreadPoolExecutor, as_completed +import google.generativeai as genai +import nltk +import pandas as pd +from groq import Groq +from langchain.chains.summarize import load_summarize_chain +from langchain.docstore.document import Document +from langchain.prompts import PromptTemplate +from langchain_community.retrievers import BM25Retriever +from langchain.retrievers import EnsembleRetriever +from langchain.retrievers.contextual_compression import ContextualCompressionRetriever +from langchain.text_splitter import CharacterTextSplitter +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_cohere import CohereRerank +from langchain_community.document_loaders import Docx2txtLoader +from langchain_community.document_loaders import TextLoader +from langchain_community.document_loaders import UnstructuredCSVLoader +from langchain_community.document_loaders import UnstructuredExcelLoader +from langchain_community.document_loaders import UnstructuredHTMLLoader +from langchain_community.document_loaders import UnstructuredMarkdownLoader +from langchain_community.document_loaders import UnstructuredPDFLoader +from langchain_community.document_loaders import UnstructuredPowerPointLoader +from langchain_community.document_loaders import UnstructuredXMLLoader +from langchain_community.document_loaders.csv_loader import CSVLoader +from langchain_community.llms import Cohere +from langchain_community.vectorstores import Chroma +from langchain_core.output_parsers.openai_tools import PydanticToolsParser +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.pydantic_v1 import BaseModel, Field +from langchain_core.runnables import RunnablePassthrough +from langchain_openai import ChatOpenAI +from typing import List +nltk.download('punkt') +from dotenv import load_dotenv +load_dotenv() +GROQ_API_KEY = os.getenv("GROQ_API_KEY") +COHERE_API_KEY = os.getenv("COHERE_API_KEY") +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") +GOOGLE_API_KEY1= os.getenv("GOOGLE_API_KEY_1") +GOOGLE_API_KEY= os.getenv("GOOGLE_API_KEY") + +client = Groq( + api_key= GROQ_API_KEY, +) +genai.configure(api_key=GOOGLE_API_KEY) +os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY +from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI +from langchain_google_vertexai import VertexAIEmbeddings +from langchain_huggingface import HuggingFaceEmbeddings +embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", task_type="retrieval_document") +llm = ChatGoogleGenerativeAI(model='gemini-2.0-flash-thinking-exp-01-21',temperature=0.6) + + +def check_persist_directory(id, file_name): + directory_path = f"./vector_database/{file_name}" + check = os.path.exists(directory_path) + return check + + +def check_path_exists(path): + return os.path.exists(path) + + +def load_file(loader): + return loader.load() + + +from langchain_community.document_loaders import PyPDFLoader +def extract_data2(): + documents = [] + base_dir = os.path.dirname(os.path.abspath(__file__)) + directory_path = os.path.join(base_dir, "..", "data") + # Chuẩn hóa đường dẫn + directory_path = os.path.abspath(directory_path) + if not os.path.exists(directory_path) or not any( + os.path.isfile(os.path.join(directory_path, f)) for f in os.listdir(directory_path)): + return False + tasks = [] + with ThreadPoolExecutor() as executor: + for file in os.listdir(directory_path): + if file.endswith(".pdf"): + pdf_path = os.path.join(directory_path, file) + loader = PyPDFLoader(pdf_path) + tasks.append(executor.submit(load_file, loader)) + elif file.endswith('.docx') or file.endswith('.doc'): + doc_path = os.path.join(directory_path, file) + loader = Docx2txtLoader(doc_path) + tasks.append(executor.submit(load_file, loader)) + elif file.endswith('.txt'): + txt_path = os.path.join(directory_path, file) + loader = TextLoader(txt_path, encoding="utf8") + tasks.append(executor.submit(load_file, loader)) + elif file.endswith('.pptx'): + ppt_path = os.path.join(directory_path, file) + loader = UnstructuredPowerPointLoader(ppt_path) + tasks.append(executor.submit(load_file, loader)) + elif file.endswith('.csv'): + csv_path = os.path.join(directory_path, file) + loader = UnstructuredCSVLoader(csv_path) + tasks.append(executor.submit(load_file, loader)) + elif file.endswith('.xlsx'): + excel_path = os.path.join(directory_path, file) + loader = UnstructuredExcelLoader(excel_path) + tasks.append(executor.submit(load_file, loader)) + elif file.endswith('.json'): + json_path = os.path.join(directory_path, file) + loader = TextLoader(json_path) + tasks.append(executor.submit(load_file, loader)) + elif file.endswith('.md'): + md_path = os.path.join(directory_path, file) + loader = UnstructuredMarkdownLoader(md_path) + tasks.append(executor.submit(load_file, loader)) + for future in as_completed(tasks): + result = future.result() + documents.extend(result) + text_splitter = CharacterTextSplitter(chunk_size=1500, chunk_overlap=500) + texts = text_splitter.split_documents(documents) + Chroma.from_documents(documents=texts, + embedding=embeddings, + persist_directory=f"./vector_database") + return texts + + +class Search(BaseModel): + queries: List[str] = Field( + ..., + description="Truy vấn riêng biệt để tìm kiếm, giữ nguyên ý chính câu hỏi riêng biệt", + ) + + +def query_analyzer(query): + output_parser = PydanticToolsParser(tools=[Search]) + system = """Bạn có khả năng đưa ra các truy vấn tìm kiếm chính xác để lấy thông tin giúp trả lời các yêu cầu của người dùng. Các truy vấn của bạn phải chính xác, không được bỏ ngắn rút gọn. + Nếu bạn cần tra cứu hai hoặc nhiều thông tin riêng biệt, bạn có thể làm điều đó!. Trả lời câu hỏi bằng tiếng Việt(Vietnamese), không được dùng ngôn ngữ khác. Bạn chỉ cần tách câu hỏi khi cần thiết hoặc giữ nguyên câu hỏi vui lòng là câu hỏi không phải tên của người dùng""" + prompt = ChatPromptTemplate.from_messages( + [ + ("system", system), + ("human", "{question}"), + ] + ) + llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.0) + structured_llm = llm.with_structured_output(Search) + query_analyzer = {"question": RunnablePassthrough()} | prompt | structured_llm + text = query_analyzer.invoke(query) + return text + + +def chat_llama3(prompt_query): + try: + chat_completion = client.chat.completions.create( + messages=[ + { + "role": "system", + "content": "Bạn là một trợ lý trung thưc, trả lời dựa trên nội dung tài liệu được cung cấp. Chỉ trả lời liên quan đến câu hỏi một cách đầy đủ chính xác, không bỏ sót thông tin." + }, + { + "role": "user", + "content": f"{prompt_query}", + } + ], + model="llama3-70b-8192", + temperature=0.0, + max_tokens=9000, + stop=None, + stream=False, + ) + return chat_completion.choices[0].message.content + except Exception as error: + return False + + +import os + +import os + +def extract_multi_metadata_content(texts, tests): + extracted_content = [] + + for idx, test in enumerate(tests): + test_filename = os.path.basename(test).lower() + temp_content = [] + + for x in texts: + source_path = x.metadata.get('source', '') + source_filename = os.path.basename(source_path).lower() + + if source_filename == test_filename: + temp_content.append(x.page_content) + + if not temp_content: + print(f"[!] Không tìm thấy nội dung cho file {test_filename}") + if idx == 0: + extracted_content.append(f"Dữ liệu của {test}:\n{''.join(temp_content)}") + else: + extracted_content.append(''.join(temp_content)) + + return '\n'.join(extracted_content) + + + + +def find_matching_files_in_docs_12_id(text, id): + base_dir = os.path.dirname(os.path.abspath(__file__)) + directory_path = os.path.join(base_dir, "..", "data") + # Chuẩn hóa đường dẫn + directory_path = os.path.abspath(directory_path) + folder_path = directory_path + + search_terms = [] + search_terms_old = [] + matching_index = [] + search_origin = re.findall(r'\b\w+\.\w+\b|\b\w+\b', text) + search_terms_origin = [] + for word in search_origin: + if '.' in word: + search_terms_origin.append(word) + else: + search_terms_origin.extend(re.findall(r'\b\w+\b', word)) + + file_names_with_extension = re.findall(r'\b\w+\.\w+\b|\b\w+\b', text.lower()) + file_names_with_extension_old = re.findall(r'\b(\w+\.\w+)\b', text) + for file_name in search_terms_origin: + if "." in file_name: + term_position = search_terms_origin.index(file_name) + search_terms_old.append(file_name) + for file_name in file_names_with_extension_old: + if "." in file_name: + search_terms_old.append(file_name) + for file_name in file_names_with_extension: + search_terms.append(file_name) + clean_text_old = text + clean_text = text.lower() + search_terms_old1 = list(set(search_terms_old)) + for term in search_terms_old: + clean_text_old = clean_text_old.replace(term, '') + for term in search_terms: + clean_text = clean_text.replace(term, '') + words_old = re.findall(r'\b\w+\b', clean_text_old) + search_terms_old.extend(words_old) + matching_files = set() + matching_files_old = set() + for root, dirs, files in os.walk(folder_path): + for file in files: + for term in search_terms: + if term.lower() in file.lower(): + term_position = search_terms.index(term) + term_value = search_terms_origin[term_position] + matching_files.add(file) + matching_index.append(term_position) + break + matching_files_old1 = [] + matching_index.sort() + for x in matching_index: + matching_files_old1.append(search_terms_origin[x]) + + return matching_files, matching_files_old1 + + +def convert_xlsx_to_csv(xlsx_file_path, csv_file_path): + df = pd.read_excel(xlsx_file_path) + df.to_csv(csv_file_path, index=False) + +def save_list_CSV_id(file_list, id): + text = "" + for x in file_list: + if x.endswith('.xlsx'): + old = f"./user_file/{id}/{x}" + new = old.replace(".xlsx", ".csv") + convert_xlsx_to_csv(old, new) + x = x.replace(".xlsx", ".csv") + loader1 = CSVLoader(f"./user_file/{id}/{x}") + docs1 = loader1.load() + text += f"Dữ liệu file {x}:\n" + for z in docs1: + text += z.page_content + "\n" + return text + + + +def merge_files(file_set, file_list): + """Hàm này ghép lại các tên file dựa trên điều kiện đã cho.""" + merged_files = {} + for file_name in file_list: + name = file_name.split('.')[0] + for f in file_set: + if name in f: + merged_files[name] = f + break + return merged_files + + + +def replace_keys_with_values(original_dict, replacement_dict): + new_dict = {} + for key, value in original_dict.items(): + if key in replacement_dict: + new_key = replacement_dict[key] + new_dict[new_key] = value + else: + new_dict[key] = value + return new_dict + + + +def aws1_csv_id(new_dict_csv, id): + text = "" + query_all = "" + keyword = [] + for key, value in new_dict_csv.items(): + query_all += value + keyword.append(key) + test = save_list_CSV_id(keyword, id) + text += test + sources = ",".join(keyword) + return text, query_all, sources + + +def chat_gemini(prompt): + generation_config = { + "temperature": 0.0, + "top_p": 0.0, + "top_k": 0, + "max_output_tokens": 8192, + } + safety_settings = [ + { + "category": "HARM_CATEGORY_HARASSMENT", + "threshold": "BLOCK_MEDIUM_AND_ABOVE" + }, + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "threshold": "BLOCK_MEDIUM_AND_ABOVE" + }, + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "threshold": "BLOCK_MEDIUM_AND_ABOVE" + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "threshold": "BLOCK_MEDIUM_AND_ABOVE" + }, + ] + model = genai.GenerativeModel(model_name="gemini-2.0-flash", + generation_config=generation_config, + safety_settings=safety_settings) + convo = model.start_chat(history=[]) + convo.send_message(prompt) + return convo.last.text + + +def question_answer(question): # Hàm sinh ra câu trả lời + try: + answer = chat_gemini(question) + return answer + except: + completion = chat_llama3(question) + if completion: + return completion + + +#bước 5,6 +def aws1_all_id(new_dict, text_alls, id, thread_id): + answer = "" + COHERE_API_KEY1 = os.getenv("COHERE_API_KEY_2") + os.environ["COHERE_API_KEY"] = COHERE_API_KEY1 + answer_relevant = "" + directory = "" + + for key, value in new_dict.items(): + query = value + keyword, keyword2 = find_matching_files_in_docs_12_id(query, id) # Tìm kiếm có từ khóa file trong câu hay không + data = extract_multi_metadata_content(text_alls, keyword) # lấy tất cả data liên quan đến keyword + if keyword: + # Extraction -> Spliting -> Embedding + file_name = next(iter(keyword)) + text_splitter = CharacterTextSplitter(chunk_size=3200, chunk_overlap=1500) + texts_data = text_splitter.split_text(data) + + + # bước 5: retrieal <-> retriever file + if check_persist_directory(id, file_name): + vectordb_query = Chroma(persist_directory=f"./vector_database/{file_name}", embedding_function=embeddings) + else: + vectordb_query = Chroma.from_texts(texts_data, + embedding=embeddings, + persist_directory=f"./vector_database/{file_name}") + + # bước 6 + # fustion retriever + bm25 + k_1 = len(texts_data) + retriever = vectordb_query.as_retriever(search_kwargs={f"k": k_1}) # Truy vấn từ vectordatabase + # Thiết lập BM25 + bm25_retriever = BM25Retriever.from_texts(texts_data) # Truy vấn từ đoạn text trích xuất đước + bm25_retriever.k = k_1 # Thiết lập số đoạn + + #Kết hợp cả 2 loại retriever + ensemble_retriever = EnsembleRetriever(retrievers=[bm25_retriever, retriever], + weights=[0.7, 0.4]) + + # Lấy kết quả truy vấn + docs = ensemble_retriever.get_relevant_documents(f"{query}") + + # storage and retriever query + # Bước 6 --> Lưu vào FAISS + path = f"./vector_database/FAISS/{file_name}" + if check_path_exists(path): + docsearch = FAISS.load_local(path, embeddings, allow_dangerous_deserialization=True) + else: + docsearch = FAISS.from_documents(docs, embeddings) + docsearch.save_local(f"./vector_database/FAISS/{file_name}") + docsearch = FAISS.load_local(path, embeddings, allow_dangerous_deserialization=True) + + k_2 = len(docs) + #bước 7 DÙNG cohere để xếp hạng lại các đoạn liên quan + compressor = CohereRerank(top_n=10,model = "rerank-multilingual-v3.0") + retrieve3 = docsearch.as_retriever(search_kwargs={f"k": k_2}) + compression_retriever = ContextualCompressionRetriever( + base_compressor=compressor, base_retriever=retrieve3 + ) + + + compressed_docs = compression_retriever.get_relevant_documents(f"{query}") # Lấy các đoạn liên quan (10 đoạn) + + # bước 8 + if compressed_docs: + data = compressed_docs[0].page_content + text = ''.join(map(lambda x: x.page_content, compressed_docs)) # Tổng hợp 10 đoạn liên quan nhất để bổ sung ngữ nghĩa + prompt_document = f"Dựa vào nội dung sau:{text}. Hãy trả lời câu hỏi sau đây: {query}. Mà không thay đổi nội dung mà mình đã cung cấp" + answer_for = question_answer(prompt_document) # Dùng gemini để response + answer += answer_for + "\n" + answer_relevant = data + directory = file_name + + return answer, answer_relevant, directory + +def extract_content_between_keywords(query, keywords): + contents = {} + num_keywords = len(keywords) + keyword_positions = [] + for i in range(num_keywords): + keyword = keywords[i] + keyword_position = query.find(keyword) + keyword_positions.append(keyword_position) + if keyword_position == -1: + continue + next_keyword_position = len(query) + for j in range(i + 1, num_keywords): + next_keyword = keywords[j] + next_keyword_position = query.find(next_keyword) + if next_keyword_position != -1: + break + if i == 0: + content_before = query[:keyword_position].strip() + else: + content_before = query[keyword_positions[i - 1] + len(keywords[i - 1]):keyword_position].strip() + if i == num_keywords - 1: + content_after = query[keyword_position + len(keyword):].strip() + else: + content_after = query[keyword_position + len(keyword):next_keyword_position].strip() + content = f"{content_before} {keyword} {content_after}" + contents[keyword] = content + return contents + +def handle_query(question, text_all, compression_retriever, id, thread_id): + COHERE_API_KEY_3 = os.environ["COHERE_API_KEY_3"] + os.environ["COHERE_API_KEY"] = COHERE_API_KEY_3 + query = question + x = query + # Tìm kiếm keyword liên quan + keyword, key_words_old = find_matching_files_in_docs_12_id(query, id) + file_list = keyword + #bước 4 --> Nhánh có key word + if file_list: + list_keywords2 = list(key_words_old) + contents1 = extract_content_between_keywords(query, list_keywords2) + merged_result = merge_files(keyword, list_keywords2) + original_dict = contents1 + replacement_dict = merged_result + new_dict = replace_keys_with_values(original_dict, replacement_dict) + files_to_remove = [filename for filename in new_dict.keys() if + filename.endswith('.xlsx') or filename.endswith('.csv')] + removed_files = {} + for filename in files_to_remove: + removed_files[filename] = new_dict[filename] + for filename in files_to_remove: + new_dict.pop(filename) + test_csv = "" + text_csv, query_csv, source = aws1_csv_id(removed_files, id) + prompt_csv = "" + answer_csv = "" + if test_csv: + prompt_csv = f"Dựa vào nội dung sau: {text_csv}. Hãy trả lời câu hỏi sau đây: {query_csv}. Bằng tiếng Việt" + answer_csv = question_answer(prompt_csv) + answer_document, data_relevant, source = aws1_all_id(new_dict, text_all, id, thread_id) + answer_all1 = answer_document + answer_csv + return answer_all1, data_relevant, source + #bước 4 --> Nhánh không có keyword + else: + compressed_docs = compression_retriever.get_relevant_documents(f"{query}") + relevance_score_float = float(compressed_docs[0].metadata['relevance_score']) + # Xử lý khi điểm số thấp + if relevance_score_float <= 0: + documents1 = [] + for file in os.listdir(f"./data"): + if file.endswith('.csv'): + csv_path = f"./data" + file + loader = UnstructuredCSVLoader(csv_path) + documents1.extend(loader.load()) + elif file.endswith('.xlsx'): + excel_path = f"./data" + file + loader = UnstructuredExcelLoader(excel_path) + documents1.extend(loader.load()) + text_splitter_csv = CharacterTextSplitter.from_tiktoken_encoder(chunk_size=2200, chunk_overlap=1500) + texts_csv = text_splitter_csv.split_documents(documents1) + vectordb_csv = Chroma.from_documents(documents=texts_csv, + embedding=embeddings, persist_directory=f'./vector_database/csv/{thread_id}') + k = len(texts_csv) + retriever_csv = vectordb_csv.as_retriever(search_kwargs={"k": k}) + llm = Cohere(temperature=0) + compressor_csv = CohereRerank(top_n=3, model="rerank-multilingual-v3.0") + compression_retriever_csv = ContextualCompressionRetriever( + base_compressor=compressor_csv, base_retriever=retriever_csv + ) + compressed_docs_csv = compression_retriever_csv.get_relevant_documents(f"{query}") + file_path = compressed_docs_csv[0].metadata['source'] + if file_path.endswith('.xlsx'): + new = file_path.replace(".xlsx", ".csv") + convert_xlsx_to_csv(file_path, new) + loader1 = CSVLoader(new) + else: + loader1 = CSVLoader(file_path) + docs1 = loader1.load() + text = " " + for z in docs1: + text += z.page_content + "\n" + prompt_csv = f"Dựa vào nội dung sau: {text}. Hãy trả lời câu hỏi sau đây: {query}. Bằng tiếng Việt" + answer_csv = question_answer(prompt_csv) + return answer_csv + else: + #bước 4 - trích xuất thông tin file liên quan nhất + file_path = compressed_docs[0].metadata['source'] + file_path = file_path.replace('\\', '/') + if file_path.endswith(".pdf"): + loader = PyPDFLoader(file_path) + elif file_path.endswith('.docx') or file_path.endswith('doc'): + loader = Docx2txtLoader(file_path) + elif file_path.endswith('.txt'): + loader = TextLoader(file_path, encoding="utf8") + elif file_path.endswith('.pptx'): + loader = UnstructuredPowerPointLoader(file_path) + elif file_path.endswith('.xml'): + loader = UnstructuredXMLLoader(file_path) + elif file_path.endswith('.html'): + loader = UnstructuredHTMLLoader(file_path) + elif file_path.endswith('.json'): + loader = TextLoader(file_path) + elif file_path.endswith('.md'): + loader = UnstructuredMarkdownLoader(file_path) + elif file_path.endswith('.xlsx'): + file_path_new = file_path.replace(".xlsx", ".csv") + convert_xlsx_to_csv(file_path, file_path_new) + loader = CSVLoader(file_path_new) + elif file_path.endswith('.csv'): + loader = CSVLoader(file_path) + + + # Extraction -> Spliting -> Embedding(phân tách chia file và nhúng vào Chroma) + text_splitter = CharacterTextSplitter(chunk_size=3200, chunk_overlap=1500) + texts = text_splitter.split_documents(loader.load()) + k_1 = len(texts) + # Bước 5 (Lưu vào trong Chroma) + file_name = os.path.basename(file_path) + if check_persist_directory(id, file_name): + vectordb_file = Chroma(persist_directory=f"./vector_database/{file_name}", + embedding_function=embeddings) + else: + vectordb_file = Chroma.from_documents(texts, + embedding=embeddings, + persist_directory=f"./vector_database/{file_name}") + # set up bm25 (Bước 6) + retriever_file = vectordb_file.as_retriever(search_kwargs={f"k": k_1}) # Truy vấn từ nội dung được lưu trong vectordb Chroma + bm25_retriever = BM25Retriever.from_documents(texts) # Truy vấn từ tài liệu extract được trong bước Extraction -> Spliting -> Embedding + bm25_retriever.k = k_1 + + #Kết hợp truy vấn + ensemble_retriever = EnsembleRetriever(retrievers=[bm25_retriever, retriever_file], + weights=[0.6, 0.4]) + docs = ensemble_retriever.get_relevant_documents(f"{query}") + import hashlib + file_name = os.path.basename(file_path) + hash_name = hashlib.md5(file_name.encode('utf-8')).hexdigest() + path = f"./vector_database/FAISS/{hash_name}" + +# Kiểm tra thư mục tồn tại + if os.path.exists(path): + docsearch = FAISS.load_local(path, embeddings, allow_dangerous_deserialization=True) + else: + docsearch = FAISS.from_documents(docs, embeddings) + docsearch.save_local(path) # Dùng biến path luôn cho thống nhất + docsearch = FAISS.load_local(path, embeddings, allow_dangerous_deserialization=True) + k_2 = len(docs) + #Dùng cohere truy vấn rerank xếp hạng lại một lần nx + retrieve3 = docsearch.as_retriever(search_kwargs={f"k": k_2}) + compressor_file = CohereRerank(top_n=10, model="rerank-multilingual-v3.0") # Sử dụng cohere để rerank + compression_retriever_file = ContextualCompressionRetriever( + base_compressor=compressor_file, base_retriever=retrieve3 + ) + compressed_docs_file = compression_retriever_file.get_relevant_documents(f"{x}") + query = question + + text = ''.join(map(lambda x: x.page_content, compressed_docs_file)) # Tổng hợp lại các đoạn liên quan để trả lời + prompt = f"Dựa vào nội dung sau:{text}. Hãy trả lời câu hỏi sau đây: {query}. Mà không thay đổi, chỉnh sửa nội dung mà mình đã cung cấp" + answer = question_answer(prompt) # Tạo ra câu trả lời + list_relevant = compressed_docs_file[0].page_content + source = file_name + return answer, list_relevant, source + + +def handle_query_upgrade_keyword_old(query_all, text_all, id): + COHERE_API_KEY_2 = os.environ["COHERE_API_KEY_2"] + #bước 3 + os.environ["COHERE_API_KEY"] = COHERE_API_KEY_2 + # phân tách câu hỏi ng dùng + test = query_analyzer(query_all) #nhận câu truy vấn được phân tích thành nhiều ý nhỏ + test_string = str(test) + #lấy list câu hỏi + matches = re.findall(r"'([^']*)'", test_string) + vectordb = Chroma(persist_directory=f"./vector_database", embedding_function=embeddings) + k = len(text_all) + retriever = vectordb.as_retriever(search_kwargs={"k": k}) + compressor = CohereRerank(top_n=5, model="rerank-multilingual-v3.0") + compression_retriever = ContextualCompressionRetriever(base_compressor=compressor, base_retriever= retriever) + with ThreadPoolExecutor() as executor: + futures = {executor.submit(handle_query, query, text_all, compression_retriever, id, i): query for i, query in + enumerate(matches)} + results = [] + data_relevant = [] + sources = [] + for future in as_completed(futures): + try: + result, list_data, list_source = future.result() + results.append(result) + data_relevant.append(list_data) + sources.append(list_source) + except Exception as e: + print(f'An error occurred: {e}') + answer_all = ''.join(results) + prompt1 = f"Dựa vào nội dung sau:{answer_all}. Hãy trả lời câu hỏi sau đây: {query_all}. Mà không thay đổi, chỉnh sửa nội dung mà mình đã cung cấp" + answer1 = question_answer(prompt1) + return answer1, data_relevant, sources + +# text_all1 = extract_data2() +# data = handle_query_upgrade_keyword_old("Tên người làm cùng khóa luận tốt nghiệp với Võ Như Ý trong file KLTN_20133118_20133080_Tuy_chinh_chatbot ",text_all1,"hello") + +# print(data[0]) \ No newline at end of file diff --git a/function/filter/__init__.py b/function/filter/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/function/filter/__pycache__/__init__.cpython-311.pyc b/function/filter/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bef4eb028d05e96d2e7cc75228411c77399bfb26 Binary files /dev/null and b/function/filter/__pycache__/__init__.cpython-311.pyc differ diff --git a/function/filter/__pycache__/filter_role.cpython-311.pyc b/function/filter/__pycache__/filter_role.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..616e10fc6dcc96393c3545f143c01359e79bc56f Binary files /dev/null and b/function/filter/__pycache__/filter_role.cpython-311.pyc differ diff --git a/function/filter/__pycache__/filter_role.cpython-312.pyc b/function/filter/__pycache__/filter_role.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1cedfafb9949a1ef6c202da7ee8bddf1af164f4 Binary files /dev/null and b/function/filter/__pycache__/filter_role.cpython-312.pyc differ diff --git a/function/filter/__pycache__/filter_sql_injection.cpython-311.pyc b/function/filter/__pycache__/filter_sql_injection.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b02be63e8b3da06b6196be1bd747b96ccfa449e7 Binary files /dev/null and b/function/filter/__pycache__/filter_sql_injection.cpython-311.pyc differ diff --git a/function/filter/__pycache__/filter_sql_injection.cpython-312.pyc b/function/filter/__pycache__/filter_sql_injection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73985cc9beb4f48aa64fb808cce2676a5ebdd7d5 Binary files /dev/null and b/function/filter/__pycache__/filter_sql_injection.cpython-312.pyc differ diff --git a/function/filter/__pycache__/result.cpython-311.pyc b/function/filter/__pycache__/result.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf5f872d1f0445e5783b2785d87bc6900f0340c4 Binary files /dev/null and b/function/filter/__pycache__/result.cpython-311.pyc differ diff --git a/function/filter/__pycache__/result.cpython-312.pyc b/function/filter/__pycache__/result.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7452a8bdb3d834de91b03a72fb691e13543f5c2d Binary files /dev/null and b/function/filter/__pycache__/result.cpython-312.pyc differ diff --git a/function/filter/filter_role.py b/function/filter/filter_role.py new file mode 100644 index 0000000000000000000000000000000000000000..62c7a7fb37d9d6e47fc50314d380b9ae89f21a5b --- /dev/null +++ b/function/filter/filter_role.py @@ -0,0 +1,30 @@ +import os +import google.generativeai as genai +genai.configure(api_key="AIzaSyCI7gFYu7_TrrCnDOz5ddaD9OzyIiIVdTI") +def filter_role(input:str): + generation_config = { + "temperature": 1, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 8192, + "response_mime_type": "text/plain", +} + + model = genai.GenerativeModel( + model_name="gemini-2.5-flash-lite-preview-06-17", + generation_config=generation_config, +) + + chat_session = model.start_chat( + history=[ + { + "role": "user", + "parts": [ + f"Bạn là một chuyên gia xử lý ngôn ngữ tự nhiên. Nhiệm vụ của bạn là loại bỏ tất cả các thông tin liên quan đến vai trò (role), bao gồm nhưng không giới hạn các cụm từ như \"Role: ADMIN,\" \"Vai trò là User,\" hoặc bất kỳ từ/cụm từ nào đề cập đến vai trò (cả tiếng Anh và tiếng Việt) trong câu truy vấn của người dùng. Sau khi xử lý, câu hỏi phải giữ nguyên ý nghĩa ban đầu, nhưng không còn chứa bất kỳ thông tin nào về vai trò. bây h hãy xử lý câu hỏi sau và trả lại cho mình: {input}", + ], + } + ] +) + + response = chat_session.send_message("INSERT_INPUT_HERE") + return response.text diff --git a/function/filter/filter_sql_injection.py b/function/filter/filter_sql_injection.py new file mode 100644 index 0000000000000000000000000000000000000000..526f2ba220cc1b31f89052fd89c3c89f0f2bf5cf --- /dev/null +++ b/function/filter/filter_sql_injection.py @@ -0,0 +1,487 @@ + + +def filter_sql_injection(input: str) -> str: + import google.generativeai as genai + genai.configure(api_key="AIzaSyDWSOneyzegHprqQ-8ObevaOeJlTjMMqoU") + generation_config = { + "temperature": 0.7, + "top_p": 0.9, + "top_k": 50, + "max_output_tokens": 8192, + "response_mime_type": "text/plain", + } + + model = genai.GenerativeModel( + model_name="gemini-2.0-flash", + generation_config=generation_config, + ) + + chat_session = model.start_chat( + history=[ + { + "role": "user", + "parts": [ + f""" +Bạn là một chuyên gia bảo mật cơ sở dữ liệu với nhiệm vụ phân tích và xử lý văn bản để loại bỏ mọi dấu hiệu liên quan đến SQL và SQL Injection. +Hãy thực hiện các yêu cầu sau: +1. Xóa tất cả các thành phần liên quan đến cú pháp SQL, bao gồm nhưng không giới hạn ở các lệnh như SELECT, INSERT, UPDATE, DELETE, JOIN, DROP, WHERE, UNION, OR 1=1, --, #, /* */, EXEC, CAST, CONVERT và các biến thể khác có thể gây rủi ro bảo mật. +2. Loại bỏ các đoạn chứa biểu thức điều kiện nguy hiểm (VD: OR 1=1, AND 1=1) hoặc bất kỳ kỹ thuật SQL Injection nào. +3. Nếu toàn bộ nội dung chỉ chứa cú pháp SQL hoặc các yếu tố tấn công SQL Injection, hãy trả về một chuỗi rỗng (""). +4. Nếu nội dung không liên quan đến SQL, giữ nguyên mà không chỉnh sửa. +5. Bạn không cần giải thích gì thêm + +Hãy xử lý văn bản sau và trả về kết quả đã được làm sạch: "{input}" +""" + ], + } + ] + ) + + # Gửi câu lệnh kiểm tra và trả về kết quả + response = chat_session.send_message(input) + return response.text.strip() + +import google.generativeai as genai + +def filter_task_analyze(input: str) -> bool: + import time + import google.generativeai as genai + + generation_config = { + "temperature": 0.0, + "top_p": 0.9, + "top_k": 40, + "max_output_tokens": 100, + "response_mime_type": "text/plain", + } + + def call_model_once() -> str: + model = genai.GenerativeModel( + model_name="gemini-2.0-flash", + generation_config=generation_config, + ) + + prompt = f""" +Bạn là một chuyên gia xác định đây có phải là một câu hỏi yêu cầu phân tích doanh thu, tình hình mua sắm,.. hay không. Quan trọng trong câu phải đề cập phân tích. +Hãy thực hiện các yêu cầu sau: + +1. Bạn không cần giải thích gì thêm +2. Viết rõ hơn kết quả: xác định có phải phân tích hay không +3. Nếu là câu hỏi yêu cầu phân tích thì trả về "True", nếu không thì trả về "False" (chỉ trả về một trong hai từ đó, không thêm gì khác) +4. Chỉ trả về True hoặc False + +Hãy xử lý văn bản sau: "{input}" +""" + response = model.generate_content(prompt) + return response.text.strip() + + # Gọi thử tối đa 2 lần + for attempt in range(2): + try: + result = call_model_once() + if result == "True": + return True + elif result == "False": + return False + else: + raise ValueError(f"Phản hồi không hợp lệ từ mô hình: {result}") + except Exception as e: + print(f"⚠️ Lỗi lần {attempt + 1}: {e}") + if attempt < 1: + time.sleep(1) + else: + raise Exception("❌ Gọi mô hình thất bại sau 2 lần thử.") + + + +# def filter_query_restore_delete(input: str) -> str: +# genai.configure(api_key="AIzaSyA4eTxl-suN_D8COMiRA5UmrF-6vW7etys") + +# generation_config = { +# "temperature": 0.0, +# "top_p": 0.9, +# "top_k": 40, +# "max_output_tokens": 100, +# "response_mime_type": "text/plain", +# } + +# model = genai.GenerativeModel( +# model_name="models/gemini-2.5-flash-preview-05-20", +# generation_config=generation_config, +# ) + +# prompt = f""" +# Bạn là một chuyên gia kiểm duyệt và giám sát các yêu cầu xử lý dữ liệu. + +# Nhiệm vụ của bạn là xác định xem câu đầu vào có yêu cầu thực hiện **một hành động nguy hiểm hoặc không được phép** hay không. + +# **Hành vi bị xem là NGUY HIỂM hoặc VI PHẠM** bao gồm: + +# 1. **Xóa dữ liệu**: +# - Dù là một phần, nhiều phần hay toàn bộ. +# - Bao gồm từ/cụm như: "xóa tất cả", "delete all", "remove toàn bộ", "clear dữ liệu", "truncate", v.v. + +# 2. **Khôi phục dữ liệu**: +# - Ví dụ: hoàn tác, đưa về trạng thái trước đó, undo, restore, rollback. +# - Bao gồm các từ: "khôi phục tất cả", "restore data", "revert changes", "undo update", v.v. + +# 3. **Cập nhật toàn bộ dữ liệu**: +# - Hành động không rõ phạm vi hoặc tác động tới toàn hệ thống. +# - Từ khóa: "update all", "cập nhật tất cả", "áp dụng toàn hệ thống", "overwrite data", "bulk update", v.v. + +# 4. **Cập nhật nhiều bản ghi** (trên 1 bản ghi): +# - Ví dụ: cập nhật trạng thái nhiều đơn hàng, áp dụng thay đổi cho danh sách. +# - Bao gồm từ: "cập nhật danh sách", "batch update", "update nhiều bản ghi", "apply to all", "multi-record update", "mass update", v.v. + +# --- + +# **Hành vi được phép (an toàn):** +# - Truy vấn, lọc hoặc xem dữ liệu. +# - Cập nhật 1 bản ghi cụ thể, có chỉ định rõ ràng (ví dụ: “cập nhật trạng thái cho đơn hàng ABC123”). +# - Xem hoặc tham khảo dữ liệu không cần thay đổi gì. + +# **Yêu cầu phản hồi:** +# - Nếu đầu vào chứa hành vi nguy hiểm phải trả về **"True"** +# - Nếu đầu vào không vi phạm, chỉ là thao tác an toàn → trả về **"False"** +# - Chỉ trả về duy nhất `"True"` hoặc `"False"` — không được giải thích thêm. + +# **Văn bản đầu vào cần xử lý:** +# \"{input}\" +# """ + + +# response = model.generate_content(prompt) +# print(response) +# result = response.text.strip() +# if result == "True": +# return True +# elif result == "False": +# return False +# else: +# raise ValueError(f"Phản hồi không hợp lệ: {result}") + +from google import genai +import time + +def filter_query_restore_delete(input: str) -> bool: + genai.configure(api_key="AIzaSyA4eTxl-suN_D8COMiRA5UmrF-6vW7etys") + + generation_config = { + "temperature": 0.0, + "top_p": 0.9, + "top_k": 40, + "max_output_tokens": 100, + "response_mime_type": "text/plain", + } + + model = genai.GenerativeModel( + model_name="models/gemini-2.5-flash-preview-05-20", + generation_config=generation_config, + ) + + prompt = f""" +Bạn là một chuyên gia kiểm duyệt và giám sát các yêu cầu xử lý dữ liệu. + +Nhiệm vụ của bạn là xác định xem câu đầu vào có yêu cầu thực hiện **một hành động nguy hiểm hoặc không được phép** hay không. + +**Hành vi bị xem là NGUY HIỂM hoặc VI PHẠM** bao gồm: + +1. **Xóa dữ liệu**: + - Dù là một phần, nhiều phần hay toàn bộ. + - Bao gồm từ/cụm như: "xóa tất cả", "delete all", "remove toàn bộ", "clear dữ liệu", "truncate", v.v. + +2. **Khôi phục dữ liệu**: + - Ví dụ: hoàn tác, đưa về trạng thái trước đó, undo, restore, rollback. + - Bao gồm các từ: "khôi phục tất cả", "restore data", "revert changes", "undo update", v.v. + +3. **Cập nhật toàn bộ dữ liệu**: + - Hành động không rõ phạm vi hoặc tác động tới toàn hệ thống. + - Từ khóa: "update all", "cập nhật tất cả", "áp dụng toàn hệ thống", "overwrite data", "bulk update", v.v. + +4. **Cập nhật nhiều bản ghi** (trên 1 bản ghi): + - Ví dụ: cập nhật trạng thái nhiều đơn hàng, áp dụng thay đổi cho danh sách. + - Bao gồm từ: "cập nhật danh sách", "batch update", "update nhiều bản ghi", "apply to all", "multi-record update", "mass update", v.v. + +--- + +**Hành vi được phép (an toàn):** +- Truy vấn, lọc hoặc xem dữ liệu. +- Cập nhật 1 bản ghi cụ thể, có chỉ định rõ ràng (ví dụ: “cập nhật trạng thái cho đơn hàng ABC123”). +- Xem hoặc tham khảo dữ liệu không cần thay đổi gì. + +**Yêu cầu phản hồi:** +- Nếu đầu vào chứa hành vi nguy hiểm phải trả về **"True"** +- Nếu đầu vào không vi phạm, chỉ là thao tác an toàn → trả về **"False"** +- Chỉ trả về duy nhất `"True"` hoặc `"False"` — không được giải thích thêm. + +**Văn bản đầu vào cần xử lý:** +\"{input}\"""" + + max_retries = 2 + for attempt in range(max_retries + 1): + try: + response = model.generate_content(prompt) + result = response.text.strip() + if result == "True": + return True + elif result == "False": + return False + else: + print(f"[Lần {attempt + 1}] Phản hồi không hợp lệ: {result}") + if attempt < max_retries: + time.sleep(1) + except Exception as e: + print(f"[Lần {attempt + 1}] Lỗi khi gọi Gemini: {e}") + if attempt < max_retries: + time.sleep(1) + + raise ValueError("Không thể xử lý đầu vào hoặc phản hồi không hợp lệ sau nhiều lần thử.") + + +# To run this code you need to install the following dependencies: +# pip install google-genai + +def filter_question_remove_restore(input: str): + import base64 + import os + import time + from google import genai + from google.genai import types + + client = genai.Client( + api_key="AIzaSyDAVIagntGC7kL93qmLgNZ-is1fsb7tsN4", + ) + + model = "gemini-2.5-flash-preview-05-20" + + prompt = f""" +Bạn là một chuyên gia kiểm duyệt và giám sát các yêu cầu xử lý dữ liệu. + +Nhiệm vụ của bạn là xác định xem câu đầu vào có yêu cầu thực hiện **một hành động nguy hiểm hoặc không được phép** hay không. + +--- + +**Hành vi bị xem là NGUY HIỂM hoặc VI PHẠM** bao gồm: + +1. **Xóa dữ liệu hệ thống hoặc tập dữ liệu lớn**: + - Ví dụ: "xóa tất cả đơn hàng", "xóa mọi dữ liệu", "clear toàn bộ", "truncate bảng", "delete all". + - Bao gồm các hành động xóa không giới hạn hoặc ảnh hưởng đến toàn bộ hệ thống. + +2. **Khôi phục dữ liệu toàn hệ thống hoặc rollback**: + - Ví dụ: "restore dữ liệu", "khôi phục toàn bộ", "undo toàn bộ thay đổi", "rollback hệ thống". + +3. **Cập nhật hàng loạt hoặc toàn bộ hệ thống**: + - Ví dụ: "update all", "bulk update", "áp dụng cho tất cả", "overwrite toàn bộ dữ liệu". + +4. **Thao tác với nhiều bản ghi mà không chỉ rõ**: + - Ví dụ: "cập nhật danh sách đơn hàng", "thay đổi trạng thái nhiều người dùng", v.v. + +--- + +**Hành vi AN TOÀN và ĐƯỢC PHÉP** (không vi phạm): + +- Xem, truy vấn, lọc hoặc tìm kiếm dữ liệu. +- Cập nhật **1 bản ghi cụ thể** (ví dụ: “cập nhật trạng thái đơn hàng ABC123”). +- **Xóa sản phẩm khỏi giỏ hàng cá nhân** hoặc **xóa giỏ hàng của chính mình**. + - Ví dụ: "xóa sản phẩm A khỏi giỏ", "xóa giỏ hàng của tôi", "loại bỏ mặt hàng khỏi cart", v.v. +- Các hành động được giới hạn rõ ràng trong phạm vi cá nhân của người dùng. + +--- + +**Yêu cầu phản hồi:** + +- Nếu đầu vào chứa hành vi nguy hiểm hoặc vi phạm → trả về **"True"** +- Nếu đầu vào là thao tác an toàn hoặc hợp lệ → trả về **"False"** +- Chỉ trả về **"True"** hoặc **"False"** — **không được giải thích thêm**. + +--- + +**Văn bản đầu vào cần xử lý:** +{input} +""" + + contents = [ + types.Content( + role="user", + parts=[ + types.Part.from_text(text=f"""{prompt}"""), + ], + ), + ] + generate_content_config = types.GenerateContentConfig( + response_mime_type="text/plain", + ) + + max_retries = 2 + for attempt in range(max_retries + 1): + try: + result = "" + for chunk in client.models.generate_content_stream( + model=model, + contents=contents, + config=generate_content_config, + ): + result += chunk.text + + result = result.strip() + print(result) # In ra kết quả một lần duy nhất sau khi xử lý + + if result == "True": + return True + elif result == "False": + return False + else: + print(f"[Lần {attempt + 1}] Phản hồi không hợp lệ: {result}") + if attempt < max_retries: + time.sleep(1) + except Exception as e: + print(f"[Lần {attempt + 1}] Lỗi khi gọi Gemini: {e}") + if attempt < max_retries: + time.sleep(1) + + raise ValueError(f"Không thể xử lý hoặc phản hồi không hợp lệ sau {max_retries + 1} lần thử.") + + + +# def filter_question_remove_restore(input: str): +# import base64 +# import os +# from google import genai +# from google.genai import types +# client = genai.Client( +# api_key="AIzaSyDAVIagntGC7kL93qmLgNZ-is1fsb7tsN4", +# ) + +# model = "gemini-2.5-flash-preview-05-20" +# prompt = f""" +# Bạn là một chuyên gia kiểm duyệt và giám sát các yêu cầu xử lý dữ liệu. + +# Nhiệm vụ của bạn là xác định xem câu đầu vào có yêu cầu thực hiện **một hành động nguy hiểm hoặc không được phép** hay không. + +# --- + +# **Hành vi bị xem là NGUY HIỂM hoặc VI PHẠM** bao gồm: + +# 1. **Xóa dữ liệu hệ thống hoặc tập dữ liệu lớn**: +# - Ví dụ: "xóa tất cả đơn hàng", "xóa mọi dữ liệu", "clear toàn bộ", "truncate bảng", "delete all". +# - Bao gồm các hành động xóa không giới hạn hoặc ảnh hưởng đến toàn bộ hệ thống. + +# 2. **Khôi phục dữ liệu toàn hệ thống hoặc rollback**: +# - Ví dụ: "restore dữ liệu", "khôi phục toàn bộ", "undo toàn bộ thay đổi", "rollback hệ thống". + +# 3. **Cập nhật hàng loạt hoặc toàn bộ hệ thống**: +# - Ví dụ: "update all", "bulk update", "áp dụng cho tất cả", "overwrite toàn bộ dữ liệu". + +# 4. **Thao tác với nhiều bản ghi mà không chỉ rõ**: +# - Ví dụ: "cập nhật danh sách đơn hàng", "thay đổi trạng thái nhiều người dùng", v.v. + +# --- + +# **Hành vi AN TOÀN và ĐƯỢC PHÉP** (không vi phạm): + +# - Xem, truy vấn, lọc hoặc tìm kiếm dữ liệu. +# - Cập nhật **1 bản ghi cụ thể** (ví dụ: “cập nhật trạng thái đơn hàng ABC123”). +# - **Xóa sản phẩm khỏi giỏ hàng cá nhân** hoặc **xóa giỏ hàng của chính mình**. +# - Ví dụ: "xóa sản phẩm A khỏi giỏ", "xóa giỏ hàng của tôi", "loại bỏ mặt hàng khỏi cart", v.v. +# - Các hành động được giới hạn rõ ràng trong phạm vi cá nhân của người dùng. + +# --- + +# **Yêu cầu phản hồi:** + +# - Nếu đầu vào chứa hành vi nguy hiểm hoặc vi phạm → trả về **"True"** +# - Nếu đầu vào là thao tác an toàn hoặc hợp lệ → trả về **"False"** +# - Chỉ trả về **"True"** hoặc **"False"** — **không được giải thích thêm**. + +# --- + +# **Văn bản đầu vào cần xử lý:** +# {input} +# """ +# contents = [ +# types.Content( +# role="user", +# parts=[ +# types.Part.from_text(text=f"""{prompt}"""), +# ], +# ), +# ] +# generate_content_config = types.GenerateContentConfig( +# response_mime_type="text/plain", +# ) + +# result = "" + +# for chunk in client.models.generate_content_stream( +# model=model, +# contents=contents, +# config=generate_content_config, +# ): +# result += chunk.text # Gom tất cả vào 1 chuỗi + +# result = result.strip() +# if result == "True": +# return True +# elif result == "False": +# return False +# else: +# raise ValueError(f"Phản hồi không hợp lệ: {result}") +# print(result) # In ra một lần sau khi hoàn tất + + +def normalize_query(input: str) -> str: + import base64 + import os + from google import genai + from google.genai import types + import time + + client = genai.Client( + api_key="AIzaSyCiIljsse8oLS9O0rnpR_CxxaXcnVcL1lM", + ) + + model = "gemini-2.5-flash-lite-preview-06-17" + prompt = f""" +Bạn là một chuyên gia trong việc xử lý và chuẩn hóa các từ trong câu hỏi về đúng định dạng, không ghi tắt. +Bạn sẽ thực hiện chuẩn hóa cả chữ tiếng Việt đánh sai chính tả và các từ viết tắt trong câu hỏi. +**Yêu cầu phản hồi:** +- Chỉ trả về câu duy nhất sau khi chuẩn hóa. Yêu cầu không giải thích gì thêm. +- Nếu đầu vào không có từ viết tắt hoặc không cần chuẩn hóa thì trả về chính nó. + +**Văn bản đầu vào cần xử lý:** +\"{input}\"""" + + contents = [ + types.Content( + role="user", + parts=[types.Part.from_text(text=prompt)], + ) + ] + + generate_content_config = types.GenerateContentConfig( + response_mime_type="text/plain", + ) + + # Hàm con dùng để gọi mô hình + def call_model() -> str: + result = "" + for chunk in client.models.generate_content_stream( + model=model, + contents=contents, + config=generate_content_config, + ): + result += chunk.text + return result.strip() + + # Gọi thử tối đa 2 lần + for attempt in range(2): + try: + return call_model() + except Exception as e: + print(f"⚠️ Lỗi lần {attempt + 1}: {e}") + if attempt < 1: + time.sleep(1) + else: + raise Exception("❌ Gọi mô hình thất bại sau 2 lần thử.") + + diff --git a/function/filter/result.py b/function/filter/result.py new file mode 100644 index 0000000000000000000000000000000000000000..006795e5441d35d8d9a56bd6461df600b310dbe5 --- /dev/null +++ b/function/filter/result.py @@ -0,0 +1,98 @@ +import google.generativeai as genai +import yaml +with open("config.yaml", "r") as f: + config = yaml.safe_load(f) + +base_backend_java = config["callback_urls"]["base"] +base_frontend = config["frontend"]["base"] + +def query_result(input_query: str, input_result: str): + import time + from google import generativeai as genai + + genai.configure(api_key="AIzaSyDAVIagntGC7kL93qmLgNZ-is1fsb7tsN4") + + config = { + "temperature": 0.7, + "top_p": 0.4, + "top_k": 50, + "max_output_tokens": 8192, + "response_mime_type": "text/plain", + } + + prompt = f""" +Dựa trên kết quả trả về từ câu lệnh SQL: {input_result}, hãy trả lời câu hỏi sau: "{input_query}". +Bạn cần trả lời bằng ngôn ngữ tự nhiên, dễ hiểu và lịch sự, làm cho người dùng cảm thấy như đang nói chuyện với con người. +Không sử dụng ngôn ngữ quá kỹ thuật hoặc giống máy móc. Nếu câu trả lời có link hình hãy thể hiện link hỉnh ảnh trong câu trả lời để hiện ra chứ không phải trả lời cái link ảnh. +Chú ý: Vui lòng đính kèm tất cả hình ảnh và không bỏ sót vào dạng markdown để có thể hiện hình ảnh. +### Luôn đảm bảo: Nếu tác vụ về tìm kiếm sản phẩm hay tìm sản phẩm, có liên quan sản phẩm trong danh sách sản phẩm, hỏi về sản phẩm gì đó bạn vui lòng với mỗi proId mình cung cấp cho sản phẩm đó hãy gán thêm vào một đường link là ví dụ {base_frontend}/product/3 ("Luôn luôn đảm bảo chính xác {base_frontend}. Vui lòng gán link này vào trong markdown gọn gàng hơn không để ròm rà. Nếu có proId bạn chỉ được phép gán vào link không được phép thể hiện proId ra ngoài. Luôn đảm bảo port chính xác là 5173. Mỗi danh sách sản phẩm hay proId nào đó đều phải gán link.** +Khi trả lời không cần giải thích gì thêm. +Vui lòng trả lời đầy đủ thông tin được cung cấp. +Vui lòng nếu có đơn hàng thì kèm theo mã đơn hàng. +Nếu có hình ảnh vui lòng đính kèm vào câu trả lời. +**Ngoài ra tùy vào câu hỏi mà câu hỏi ngược lại của bạn phải sao cho hơp lý ví dụ sản phẩm đó không tồn tại, không có size đó, không đủ số lượng thì phải hỏi lại người dùng.** +**Diễn đạt các sản phẩm trong giỏ 1 cách tự nhiên. Nếu sản phẩm không tồn tại, không có size đó, không đủ số lượng thì báo là như vậy chứ không phải báo là sản phẩm có trong giỏ hàng** +** Khi đã thanh toán không cần hỏi người dùng thêm sản phẩm khác hay không** +** Nếu trong câu hỏi có thêm sản phẩm mà thông tin cung cấp đã thành công thì hỏi người dùng có muốn thêm sản phẩm hay thanh toán đồ không. Nếu lỗi không tìm thấy sản phẩm, không đủ size thì hãy nhắc nhở người dùng. Vui lòng dùng lời viets không quá khô khan thô lỗ, phải dễ nghe** +""" + + model = genai.GenerativeModel( + model_name="gemini-2.0-flash", + generation_config=config, + ) + + max_retries = 2 + for attempt in range(max_retries + 1): + try: + session = model.start_chat(history=[ + { + "role": "user", + "parts": [prompt], + } + ]) + + response = session.send_message("Hãy trả lời câu hỏi trên bằng tiếng Việt tự nhiên. hãy lưu ý rằng với các câu SQL trả về kết quả rỗng tức là đã hoàn thành thành công bạn hãy dùng lời diễn đạt cho người dùng chat hiểu.") + return response.text + + except Exception as e: + print(f"[Lần {attempt + 1}] Lỗi khi gọi Gemini: {e}") + if attempt < max_retries: + time.sleep(1) + + raise RuntimeError(f"Không thể lấy phản hồi từ Gemini sau {max_retries + 1} lần thử.") + + +def query_result_analyze(input_query: str, input_result: str): + genai.configure(api_key="AIzaSyDWSOneyzegHprqQ-8ObevaOeJlTjMMqoU") + config= { + "temperature": 0.8, + "top_p": 0.4, + "top_k": 50, + "max_output_tokens": 1024, + "response_mime_type": "text/plain", + } + + # Khởi tạo mô hình + model = genai.GenerativeModel( + model_name="gemini-2.5-flash-preview-04-17", + generation_config=config, + ) + + # Tạo phiên trò chuyện + session= model.start_chat( + history=[ + { + "role": "user", + "parts": [ + f""" +Dựa trên kết quả trả về từ câu lệnh SQL: {input_result}, hãy trả lời câu hỏi sau: "{input_query}". +Bạn không cần làm gì cả chỉ cần gán lại các link ảnh cho đẹp và lấy kết quả gán vào phần dưới là xong. Bạn vui lòng giũ nguyên nhung nhận xét và hình ảnh. Chỉ cân format lại cho đẹp là đuoc. +""" + ], + } + ] + ) + + # Gửi câu hỏi và lấy phản hồi + response = session.send_message("Hãy tuân thủ các điều trên") + return response.text diff --git a/function/function_agent/__init__.py b/function/function_agent/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/function/function_agent/__pycache__/__init__.cpython-311.pyc b/function/function_agent/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10e7741cb7b4c2712fc7b415c36f5499e954d355 Binary files /dev/null and b/function/function_agent/__pycache__/__init__.cpython-311.pyc differ diff --git a/function/function_agent/__pycache__/function.cpython-311.pyc b/function/function_agent/__pycache__/function.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef7ddfbb0563ba4905c7e69e661e7561dcf56da6 Binary files /dev/null and b/function/function_agent/__pycache__/function.cpython-311.pyc differ diff --git a/function/function_agent/__pycache__/map_toolcall.cpython-311.pyc b/function/function_agent/__pycache__/map_toolcall.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e466c73a37a7bc750df495e3746635badf13611 Binary files /dev/null and b/function/function_agent/__pycache__/map_toolcall.cpython-311.pyc differ diff --git a/function/function_agent/__pycache__/map_toolcall.cpython-312.pyc b/function/function_agent/__pycache__/map_toolcall.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96a567356f196943e40709a9f4a3259fe2c20828 Binary files /dev/null and b/function/function_agent/__pycache__/map_toolcall.cpython-312.pyc differ diff --git a/function/function_agent/__pycache__/multi_agent.cpython-311.pyc b/function/function_agent/__pycache__/multi_agent.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48098a511fd452b5446d851a44ea92d5dafa1a48 Binary files /dev/null and b/function/function_agent/__pycache__/multi_agent.cpython-311.pyc differ diff --git a/function/function_agent/__pycache__/multi_agent.cpython-312.pyc b/function/function_agent/__pycache__/multi_agent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33065eb4e237e92ce5a3fa2ae4c400980734313c Binary files /dev/null and b/function/function_agent/__pycache__/multi_agent.cpython-312.pyc differ diff --git a/function/function_agent/map_toolcall.py b/function/function_agent/map_toolcall.py new file mode 100644 index 0000000000000000000000000000000000000000..43a9973e86e05a2d11b917a6868de84ea2344695 --- /dev/null +++ b/function/function_agent/map_toolcall.py @@ -0,0 +1,11 @@ +def map_toolcalls_with_results(tool_calls, task_results): + mapped_results = [] + + for tc in tool_calls: + call_id = tc["id"] + query = tc["args"].get("query", "") + result = task_results.get(call_id, {}).get("result", "Không có kết quả") + + mapped_results.append({"query": query, "result": result}) + + return mapped_results diff --git a/function/function_agent/multi_agent.py b/function/function_agent/multi_agent.py new file mode 100644 index 0000000000000000000000000000000000000000..fa2cc46cdffaa39aab2fa6b1aeac136dd40ce0a5 --- /dev/null +++ b/function/function_agent/multi_agent.py @@ -0,0 +1,950 @@ +import asyncio +import json +import os,sys +BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) +sys.path.insert(0, BASE_DIR) +from function.file import extract_file as extract_file +import function.filter.filter_role as filter_role_1 +import function.filter.filter_sql_injection as filter_sql_injection_1 +import function.filter.result as query_result_1 +import function.chat as chat_sql +import function.gemini_response.response_general as res_general +import function.gemini_response.response_hello as res_hello +import cloudinary +import cloudinary.uploader +from typing import List,Any +import cloudinary +from cloudinary.utils import cloudinary_url +from cloudinary import uploader +from typing import Optional +import asyncio +from models.Database_Entity import StopSignal + + +async def check_should_stop(chat_id: str, stop_event:Optional[asyncio.Event]): + # Trường hợp dừng qua RAM (in-memory) + await asyncio.sleep(0.1) + if stop_event and stop_event.is_set(): + print("🛑 Dừng qua stop_event.") + return {"status": "cancelled"} + + # Trường hợp dừng qua MongoDB + await asyncio.sleep(0.1) + if StopSignal.objects(chat_history= chat_id, is_stopped=True).first(): + print("🛑 Dừng vì có StopSignal trong DB.") + return {"status": "cancelled"} + + return None + +# Dán nguyên chuỗi URL này vào +cloudinary.config( + cloud_name = "dgj8n2ggn", + api_key = "287331723817617", + api_secret = "MAI7dpoikIy-ap7IjPCiwfRgMQ0", + secure=True +) + +def upload_images_to_cloudinary_and_markdown(folder_path): + markdown_images = "" + image_files = [ + f for f in os.listdir(folder_path) + if f.lower().endswith(('.png', '.jpg', '.jpeg')) + ] + + for img in image_files: + img_path = os.path.join(folder_path, img) + try: + upload_result = cloudinary.uploader.upload(img_path) + print(img_path) + image_url = upload_result.get("secure_url") + print(image_url) + if image_url: + markdown_images += f"![{img}]({image_url})\n\n" + except Exception as e: + print(f"❌ Upload lỗi với ảnh {img}: {e}") + return markdown_images + + +async def agent_information(task,user_id,languages, role, chat_id,token,stop_event: Optional[asyncio.Event]): + result_check = await check_should_stop(chat_id,stop_event) + if result_check: + return result_check + text_all= extract_file.extract_data2() + result_check = await check_should_stop(chat_id,stop_event) + if result_check: + return result_check + task = filter_sql_injection_1.normalize_query(task) + print("text: ",text_all) + result_check = await check_should_stop(chat_id,stop_event) + if result_check: + return result_check + return extract_file.handle_query_upgrade_keyword_old(f"{task}",text_all,"demohmdrinks") + + +async def agent_hello(task,user_id,languages, role,chat_id,token,stop_event: Optional[asyncio.Event]): + result_check = await check_should_stop(chat_id,stop_event) + if result_check: + return result_check + result = await res_hello.response_hello(task) + result_check = await check_should_stop(chat_id,stop_event) + if result_check: + return result_check + return result + + +from mongoengine import connect +from dotenv import load_dotenv +load_dotenv() +MONGO_URI = os.getenv("MONGO_URI", "") +connect("chatbot_hmdrinks", host=MONGO_URI) + +from models.Database_Entity import User, ChatHistory, DetailChat +def get_chat_history(chat_history_id): + messages = DetailChat.objects(chat_history=chat_history_id).order_by('timestamp') + history_text = "" + for msg in messages: + history_text += f"Người dùng hỏi: {msg.you_message}\n" + history_text += f"Trợ lý: {msg.ai_message}\n\n" + return history_text.strip() + + +from mongoengine import connect +from dotenv import load_dotenv +load_dotenv() +MONGO_URI = os.getenv("MONGO_URI", "") +connect("chatbot_hmdrinks", host=MONGO_URI) +import sys +import os + +project_root = os.path.abspath(os.path.join(os.getcwd(), '..', '..', '..')) + +if project_root not in sys.path: + sys.path.append(project_root) +from models.Database_Entity import User, ChatHistory, DetailChat,ChatCart, CartProduct +import base64 +from advance_shopping.prompt import context_prompt, multitool_prompt, user_intent + +from advance_shopping.call_gemini import tool_call +from advance_shopping.server_java import server_java +from advance_shopping.function import test as test_func + + +def get_chat_history(chat_history_id): + messages = DetailChat.objects(chat_history=chat_history_id).order_by('timestamp') + print(DetailChat._meta) # in ra cấu hình model + + history_text = "" + for msg in messages: + history_text += f"Người dùng: {msg.you_message}\n" + history_text += f"Trợ lý: {msg.ai_message}\n\n" + return history_text.strip() + + +from bson import ObjectId +def chat_history_has_unconfirmed_cart(chat_history_id: str) -> bool: + """ + Trả về True nếu trong đoạn chat có giỏ hàng chưa được xác nhận đặt hàng. + Trả về False nếu không có giỏ hàng, hoặc tất cả giỏ hàng đã được xác nhận. + """ + return ChatCart.objects( + chat_history=ObjectId(chat_history_id), + status="pending" + ).count() > 0 + + +from bson import ObjectId + + +def process_data(structured_data, data): + list_data1 = structured_data[:] + print("data",data) + if data == "None": + return structured_data + if isinstance(data, list) and not data: + print("✅ Đây là một list rỗng.") + return structured_data + if data: + for item in data: + action = item.get("action", "").lower() + name = item.get("name", "").strip().lower() + size = item.get("size", "").strip().lower() + size_old = item.get("size_old", "").strip().lower() if "size_old" in item else None + + matched = False + if action == "update" and size_old: + for item_old in list_data1: + name_old = item_old.get("name", "").strip().lower() + size_old_in_list = item_old.get("size", "").strip().lower() + + if name == name_old and size_old == size_old_in_list: + matched = True + # Cập nhật các trường + for key in ["quantity", "size"]: + if key in item: + item_old[key] = item[key] + break + + else: + for item_old in list_data1: + name_old = item_old.get("name", "").strip().lower() + size_old_in_list = item_old.get("size", "").strip().lower() + + if name == name_old and size == size_old_in_list: + matched = True + + if action == "delete": + list_data1.remove(item_old) + break + + elif action == "update": + for key in ["quantity", "size"]: + if key in item: + item_old[key] = item[key] + break + elif action == "add": + if "quantity" in item: + item_old["quantity"] = int(item_old.get("quantity", 0)) + int(item["quantity"]) + break + + if not matched and action == "add": + item_to_add = {k: v for k, v in item.items() if k != "action"} + list_data1.append(item_to_add) + filtered_list = [] + for item in list_data1: + if (item.get("name") not in [None, "", "None"]): + filtered_list.append(item) + return filtered_list + + +def get_last_ai_message(chat_history_id: str) -> str: + last_detail = DetailChat.objects( + chat_history=ObjectId(chat_history_id), + ai_message__ne=None + ).order_by('-timestamp').first() + + return last_detail.ai_message if last_detail else "None" + + +# async def agent_sql(task,user_id,languages, role, chat_id,token,stop_event: Optional[asyncio.Event] = None): +# check_analyze = filter_sql_injection_1.filter_task_analyze(task) +# check_restore_delete = filter_sql_injection_1.filter_question_remove_restore(task) +# prompt_check_shopping = "" +# print(check_restore_delete) +# if check_restore_delete == True: +# return "Bạn không được phép khôi phục hoặc xóa dữ liệu. Vui lòng kiểm tra lại câu hỏi của bạn." +# print(check_analyze) +# if check_analyze == False: +# result_final = "" +# cart_status = "" +# if chat_history_has_unconfirmed_cart(chat_id) is True: +# cart_status = "Yes (Đang Pending)" +# else: +# cart_status = "No (Không có)" + +# print(cart_status) +# prompt_check_shopping = user_intent.build_detailed_user_intent_prompt(user_question=task, +# chat_history=get_chat_history(chat_id), +# cart_status=cart_status) +# check_shopping = tool_call.generate(prompt_check_shopping) +# print(check_shopping) +# type = check_shopping["type"] +# if str(type) == "shopping_question": +# tools = ["confirm_order", "insert_cart", "update_cart", "delete_item_cart", "deleted all cart"] +# last_ai_message = get_last_ai_message(chat_id) +# context_prompt = multitool_prompt.build_multi_tool_prompt(question=last_ai_message,tools=tools,user_answer=task) +# data_context = {} +# try: +# data_context = tool_call.generate(context_prompt) +# if not data_context["question"]: +# data_context = tool_call.generate(context_prompt) +# except: +# data_context = tool_call.generate(context_prompt) +# if not data_context["question"]: +# data_context = tool_call.generate(context_prompt) +# print("data_context: ",data_context) +# question_new = data_context["question"] +# latest_unconfirmed_cart = ChatCart.objects( +# chat_history=chat_id, +# status="pending" +# ).order_by('-created_at').first() +# previous_data = [] +# if latest_unconfirmed_cart: +# for product in latest_unconfirmed_cart.cart_products: +# product_dict = product.to_mongo().to_dict() +# if "_id" in product_dict: +# product_dict["_id"] = str(product_dict["_id"]) +# previous_data.append(product_dict) + + +# data = test_func.classify_data(input = f"""{question_new}""", structured_data=previous_data) +# list_data = list() +# print(data) +# if data == "None" or data == '': +# data = list() +# else: +# try: +# list_data = json.loads(data) +# except: +# list_data = list() +# print("List data:", list_data) +# print("Preious_data", previous_data) +# previous_data = list(previous_data) +# list_data1 = previous_data +# clean_list = list() +# full_test = "" +# result = [] +# print(list_data) +# try: +# print("Nhay vo test2") +# result = process_data(list_data1, list_data) +# except: +# if data is None or data == "None": +# print("Nhay vo test1") +# result = process_data(list_data1, []) +# clean_cart_products = [] +# chat_cart = ChatCart() +# if list_data: +# annotated_list, clean_list, full_test = test_func.validate_product_list(data_list=result,server_java=server_java) +# print(result) +# print(clean_list) + +# results = [] +# if data_context["question_normal"]: + +# for key, q in data_context["question_normal"].items(): +# task_1 = filter_sql_injection_1.normalize_query(q) +# filtered_input = filter_sql_injection_1.filter_sql_injection(task_1) +# filtered_role_input = filter_role_1.filter_role(filtered_input) +# result = await chat_sql.execute_query_user(filtered_role_input, user_id, languages, role) +# result_final = query_result_1.query_result(task, result) +# results.append({"question": q, "result": result_final}) + +# result_final += str(results) + +# for item in clean_list: +# # Đảm bảo quantity là số nguyên +# product = CartProduct( +# name=item.get('name'), +# size=item.get('size'), +# quantity=int(item.get('quantity', 1)) +# ) +# clean_cart_products.append(product) +# print("Clean_cart", clean_cart_products) +# print("Full test", full_test) + + +# if latest_unconfirmed_cart: +# print("🛒 Đã có giỏ hàng 'pending' trước đó. Đang cập nhật lại sản phẩm...") +# latest_unconfirmed_cart.cart_products = clean_cart_products +# latest_unconfirmed_cart.save() +# chat_cart = latest_unconfirmed_cart +# else: +# print("Chưa có giỏ hàng 'pending'. Đang tạo mới...") +# chat_cart = ChatCart( +# chat_history=chat_id, +# cart_products=clean_cart_products, +# status="pending" +# ) +# chat_cart.save() +# if full_test: +# return full_test + f"Hãy tạo ra câu hỏi để người dùng thay đổi ý định mua sản phẩm. Ngoài ra luôn hiển thị lại tình trạng giỏ của người dùng: giỏ hàng{clean_list}" + str(results) + +# if check_shopping.get("is_delete_cart") == "Yes": +# print("🗑️ Đang xóa toàn bộ sản phẩm trong giỏ hàng...") +# chat_cart.cart_products = [] +# chat_cart.status = "failed" +# chat_cart.save() +# result_final += "Đã xóa toàn bộ sản phẩm trong giỏ hàng..." +# if check_shopping.get("is_view_cart") == "Yes": +# print("👀 Đang xem giỏ hàng...") +# chat_cart = ChatCart.objects(chat_history=chat_id, status="pending").first() + +# if not chat_cart or not chat_cart.cart_products: +# print("🛒 Giỏ hàng hiện đang trống.") +# result_final += "🛒 Giỏ hàng hiện đang trống.\n" +# else: +# message_lines = ["🛒 Danh sách sản phẩm trong giỏ:"] +# for product in chat_cart.cart_products: +# print(f"- Tên sản phẩm: {product.name} | Số Lượng: {product.quantity} | Size(Kích cỡ): {product.size}") +# message_lines.append( +# f"- Tên sản phẩm: {product.name} | Số Lượng: {product.quantity} | Size(Kích cỡ): {product.size}" +# ) +# data = "\n".join(message_lines) +# result_final += data + "\n" + + + +# if check_shopping["is_confirm_order"] == "Yes": +# try: +# print("🔹 [STEP 1] Tạo giỏ hàng mới...") +# data = server_java.create_cart(int(user_id), token) +# cart_id = data.get("cartId") +# print(f"✅ Giỏ hàng tạo thành công: cart_id = {cart_id}") +# except Exception as e: +# print(f"❌ Lỗi khi tạo giỏ hàng: {e}") +# cart_id = None + +# if cart_id: +# for idx, item in enumerate(clean_cart_products, 1): +# try: +# print(f"\n🔹 [STEP 2.{idx}] Tìm kiếm sản phẩm: {item['name']} (size: {item['size']})...") +# pro_id, pro_name, size_check, stock_check, price_check = server_java.search_product( +# keyword=item["name"], size=item["size"] +# ) +# print(f"✅ Tìm thấy sản phẩm '{pro_name}' (ID: {pro_id}, Size: {size_check})") + +# print(f"🔹 [STEP 3.{idx}] Thêm sản phẩm vào giỏ hàng...") +# data1 = server_java.insert_cartItem(user_id, cart_id, pro_id, size_check, item["quantity"], token) +# print(f"✅ Đã thêm sản phẩm '{pro_name}' vào giỏ hàng.") +# except Exception as e: +# print(f"❌ Lỗi khi xử lý sản phẩm thứ {idx}: {e}") +# continue # vẫn tiếp tục vòng lặp cho sản phẩm tiếp theo + +# try: +# print("\n🔹 [STEP 4] Tạo đơn hàng từ giỏ hàng...") +# create_orders = server_java.create_orders(user_id=user_id, cartId=cart_id, token=token) +# order_id = create_orders["body"]["orderId"] +# print(f"✅ Đơn hàng được tạo: order_id = {order_id}") +# except Exception as e: +# print(f"❌ Lỗi khi tạo đơn hàng: {e}") +# order_id = None + +# if order_id: +# try: +# print("🔹 [STEP 5] Xác nhận đơn hàng với AI...") +# confirm_order = server_java.confirm_orders_AI(user_id=user_id, token=token, orderId=order_id) +# print("✅ Phản hồi xác nhận đơn hàng từ AI:") +# print(confirm_order["body"]) +# if confirm_order["body"] == "Confirm success": +# chat_cart.status = "confirmed" +# chat_cart.confirmed_order = True +# chat_cart.save() +# from urllib.parse import urlencode +# base_url = "https://vnexpress.net/tin-tuc-24h" +# params = { +# "order_id": order_id, +# "token": token, +# "user_id": user_id +# } +# link_payment = f"https://vnexpress.net/tin-tuc-24h?order_id={order_id}&token={token}&user_id={user_id}" + + + +# return "Yêu cầu: " + str(task) + f". Yêu cầu của bạn đã thành công. Tình trạng giỏ hiện tại (Luôn luôn hiển thị lại trạng thái này để người dùng biết giỏ của họ) {clean_list}. Bạn đã xác nhận thanh toán thành công sẽ tiến hành chuyển sang link thanh toán{link_payment}. Vui lòng thực hiện thanh toán trên trang này trong vòng 30 phút tối đa" + str(results) +# else: +# chat_cart.status = "failed" +# chat_cart.confirmed_order = False +# chat_cart.save() + +# except Exception as e: +# print(f"❌ Lỗi khi xác nhận đơn hàng với AI: {e}") +# chat_cart.status = "failed" +# chat_cart.confirmed_order = False +# chat_cart.save() + +# if check_shopping["is_confirm_order"] == "No" and len(clean_cart_products) != 0 : +# result_final += "Yêu cầu: " + str(task) + f". Yêu cầu của bạn đã thành công. Tình trạng giỏ hiện tại(Luôn luôn hiển thị lại trạng thái này để người dùng biết giỏ của họ): {clean_list}" + "kết quả thực thi:" + str(results) + +# return result_final +# else: +# task = filter_sql_injection_1.normalize_query(task) +# filtered_input = filter_sql_injection_1.filter_sql_injection(task) +# filtered_role_input = filter_role_1.filter_role(filtered_input) +# result = await chat_sql.execute_query_user(filtered_role_input, user_id, languages, role) +# result_final = query_result_1.query_result(task, result) +# return result_final +# else: +# result = "" +# task = filter_sql_injection_1.normalize_query(task) +# filtered_input = filter_sql_injection_1.filter_sql_injection(task) +# filtered_role_input = filter_role_1.filter_role(filtered_input) +# output = await chat_sql.generate_and_save_code(filtered_role_input, user_id, role, languages) +# if isinstance(output, tuple): +# result, path_image = output +# else: +# result = output +# path_image = None + +# if path_image and os.path.exists(path_image): +# images_md = upload_images_to_cloudinary_and_markdown(path_image) +# if images_md: +# result = f"### Tất cả Hình ảnh sinh ra:\n\n{images_md}\n\n{result}" +# return result + + +# +# ----- PHẦN 2: HÀM AGENT_SQL ĐÃ TÁI CẤU TRÚC ----- +# + +# --- Các hàm trợ giúp (Helper Functions) --- +from typing import Optional +async def _handle_shopping_intent(task: str, user_id: int, chat_id: Any, token: str, languages,role, stop_event: Optional[asyncio.Event]) -> str: + """ + Xử lý các tác vụ liên quan đến mua sắm: xem giỏ, thêm/sửa/xóa, xác nhận đơn hàng. + """ + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + try: + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + cart_status = "Yes (Đang Pending)" if chat_history_has_unconfirmed_cart(chat_id) else "No (Không có)" + prompt_context = multitool_prompt.build_multi_tool_prompt( + question=get_last_ai_message(chat_id), + tools=["confirm_order", "insert_cart", "update_cart", "delete_item_cart", "deleted all cart"], + user_answer=task + ) + data_context = tool_call.generate(prompt_context) + print(f"DEBUG: data_context: {data_context}") + + # 2. Lấy giỏ hàng đang chờ xử lý hoặc tạo mới + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + latest_unconfirmed_cart = ChatCart.objects(chat_history=chat_id, status="pending").order_by('-created_at').first() + if latest_unconfirmed_cart: + chat_cart = latest_unconfirmed_cart + previous_cart_items = [p.to_mongo().to_dict() for p in chat_cart.cart_products] + print("INFO: Đã tìm thấy giỏ hàng 'pending', sẽ cập nhật.") + else: + chat_cart = ChatCart(chat_history=chat_id, status="pending") + previous_cart_items = [] + print("INFO: Không tìm thấy giỏ hàng 'pending', sẽ tạo mới.") + + #Backup data + chat_cart.backup_cart_products = chat_cart.cart_products.copy() + chat_cart.backup_status = chat_cart.status + chat_cart.backup_confirmed_order = chat_cart.confirmed_order + chat_cart.save() + + # 3. Trích xuất và xử lý thông tin sản phẩm từ câu hỏi của người dùng + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + new_items_str = test_func.classify_data(input=f"{data_context.get('question', '')}", structured_data=previous_cart_items) + new_items_list = json.loads(new_items_str) if new_items_str and new_items_str != "None" else [] + updated_cart_list = process_data(previous_cart_items, new_items_list) + + # 4. Xác thực sản phẩm và cập nhật giỏ hàng trong DB + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + annotated_list, clean_list, validation_message = test_func.validate_product_list(data_list=updated_cart_list, server_java=server_java) + + # clean_cart_products = [CartProduct(**item) for item in clean_list] + clean_cart_products = [] + for item in clean_list: + # Đảm bảo quantity là số nguyên + product = CartProduct( + name=item.get('name'), + size=item.get('size'), + quantity=int(item.get('quantity', 1)) + ) + clean_cart_products.append(product) + print("Clean_cart", clean_cart_products) + chat_cart.cart_products = clean_cart_products + chat_cart.save() + print(f"INFO: Giỏ hàng đã được cập nhật với {len(clean_cart_products)} sản phẩm.") + + # 5. Xử lý các câu hỏi phụ (nếu có) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + other_results = [] + if data_context.get("question_normal"): + for q_key, q_text in data_context["question_normal"].items(): + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + result = await _handle_simple_query(q_text, user_id, languages, role, stop_event, chat_id) + other_results.append({"question": q_text, "result": result}) + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + response_parts = [] + if validation_message: + response_parts.append(validation_message) + + final_cart_state_msg = f"Tình trạng giỏ hàng hiện tại của bạn: {clean_list}" + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + shopping_intent = tool_call.generate(user_intent.build_detailed_user_intent_prompt( + user_question=task, chat_history=get_chat_history(chat_id), cart_status=cart_status + )) + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + if shopping_intent.get("is_delete_cart") == "Yes": + chat_cart.cart_products = [] + chat_cart.status = "failed" + chat_cart.save() + response_parts.append("Đã xóa toàn bộ sản phẩm trong giỏ hàng.") + final_cart_state_msg = "Tình trạng giỏ hàng hiện tại của bạn: Giỏ hàng trống." + elif shopping_intent.get("is_view_cart") == "Yes": + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + response_parts.append("Đây là giỏ hàng của bạn.") + elif shopping_intent.get("is_confirm_order") == "Yes": + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + if not chat_cart.cart_products: + return "Giỏ hàng của bạn đang trống. Vui lòng thêm sản phẩm trước khi xác nhận đơn hàng." + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + confirmation_result = await _execute_order_confirmation(user_id, token, chat_cart, stop_event,chat_id) + confirmation_result_str = "" + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + chat_cart.status = chat_cart.backup_status + chat_cart.confirmed_order = chat_cart.backup_confirmed_order + chat_cart.save() + if confirmation_result[1]: + server_java.confirm_cancel_AI(int(user_id),token,confirmation_result[1]) + server_java.remove_all_cartItem(int(user_id), confirmation_result[2], token) + confirmation_result_str = confirmation_result[0] + else: + confirmation_result_str = confirmation_result + return result_check + + response_parts.append(str(confirmation_result)) + else: + response_parts.append(f"Yêu cầu '{task}' của bạn đã được xử lý.") + + response_parts.append(final_cart_state_msg) + if other_results: + response_parts.append(f"Kết quả cho các câu hỏi khác: {other_results}") + + return " ".join(response_parts) + except asyncio.CancelledError as e: + print(f"STOP: {e}") + if 'chat_cart' in locals(): + if chat_cart.backup_cart_products: + chat_cart.cart_products = chat_cart.backup_cart_products + chat_cart.status = chat_cart.backup_status + chat_cart.confirmed_order = chat_cart.backup_confirmed_order + chat_cart.save() + print("INFO: Đã khôi phục giỏ hàng từ bản backup do bị hủy.") + return "Quá trình đã bị dừng theo yêu cầu. Giỏ hàng đã được khôi phục như ban đầu." + # except Exception as e: + # print(f"ERROR in _handle_shopping_intent: {e}") + # return "Đã có lỗi xảy ra trong quá trình xử lý yêu cầu mua sắm của bạn. Vui lòng thử lại." + + +# async def rollback_cart_and_order(chat_cart: ChatCart, user_id: int, token: str): +# try: +# if chat_cart.temp_order_id: +# server_java.delete_order(chat_cart.temp_order_id, token) +# print(f"INFO: Đã rollback đơn hàng {chat_cart.temp_order_id}") +# if chat_cart.temp_cart_id: +# server_java.delete_cart(chat_cart.temp_cart_id, token) +# print(f"INFO: Đã rollback giỏ hàng {chat_cart.temp_cart_id}") +# chat_cart.status = "cancelled" +# chat_cart.save() +# except Exception as e: +# print(f"WARNING: Lỗi khi rollback: {e}") + + +async def rollback_cart_and_order(chat_cart: ChatCart, user_id: int, token: str,order_id): + try: + if chat_cart.temp_order_id: + server_java.delete_order(chat_cart.temp_order_id, token) + print(f"INFO: Đã rollback đơn hàng {chat_cart.temp_order_id}") + if chat_cart.temp_cart_id: + server_java.delete_cart(chat_cart.temp_cart_id, token) + print(f"INFO: Đã rollback giỏ hàng {chat_cart.temp_cart_id}") + chat_cart.status = "cancelled" + chat_cart.save() + except Exception as e: + print(f"WARNING: Lỗi khi rollback: {e}") + + +async def _execute_order_confirmation(user_id: int, token: str, chat_cart: ChatCart, stop_event: Optional[asyncio.Event],chat_id:str= ""): + """ + Thực hiện quy trình 5 bước để xác nhận đơn hàng với server_java. + """ + try: + + #Backup data + chat_cart.backup_status = chat_cart.status + chat_cart.backup_confirmed_order = chat_cart.confirmed_order + chat_cart.save() + + print("INFO: Bắt đầu quy trình xác nhận đơn hàng.") + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + chat_cart.status = chat_cart.backup_status + chat_cart.confirmed_order = chat_cart.backup_confirmed_order + chat_cart.save() + return result_check + cart_data = server_java.create_cart(int(user_id), token) + cart_id = cart_data.get("cartId") + if not cart_id: raise Exception("Không thể tạo giỏ hàng trên hệ thống.") + print(f"SUCCESS: Tạo giỏ hàng tạm thành công: cart_id = {cart_id}") + + # Step 2 & 3: Add items to cart + for item in chat_cart.cart_products: + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + chat_cart.status = chat_cart.backup_status + chat_cart.confirmed_order = chat_cart.backup_confirmed_order + chat_cart.save() + rollback = server_java.remove_all_cartItem(int(user_id), cart_id, token) + return result_check + pro_id, _, size_check, _, _ = server_java.search_product(keyword=item.name, size=item.size) + server_java.insert_cartItem(user_id, cart_id, pro_id, size_check, item.quantity, token) + print(f"SUCCESS: Đã thêm '{item.name}' vào giỏ hàng tạm.") + + # Step 4: Create Order + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + chat_cart.status = "pending" + chat_cart.confirmed_order = False + chat_cart.save() + server_java.remove_all_cartItem(int(user_id), cart_id, token) + return result_check + order_data = server_java.create_orders(user_id=user_id, cartId=cart_id, token=token) + order_id = order_data["body"]["orderId"] + if not order_id: raise Exception("Không thể tạo đơn hàng từ giỏ hàng.") + print(f"SUCCESS: Tạo đơn hàng thành công: order_id = {order_id}") + + # Step 5: Confirm Order + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + chat_cart.status = chat_cart.backup_status + chat_cart.confirmed_order = chat_cart.backup_confirmed_order + chat_cart.save() + server_java.confirm_cancel_AI(int(user_id),token,order_id) + server_java.remove_all_cartItem(int(user_id), cart_id, token) + return result_check + confirm_data = server_java.confirm_orders_AI(user_id=user_id, token=token, orderId=order_id) + if confirm_data.get("body") == "Confirm success": + chat_cart.status = "confirmed" + chat_cart.confirmed_order = True + chat_cart.order_id = order_id + chat_cart.cart_id = cart_id + chat_cart.save() + link_payment = f"http://localhost:5173/payment?order_id={order_id}&token={token}&user_id={user_id}" + data = f"Đơn hàng của bạn đã được xác nhận thành công. Vui lòng truy cập link sau để thanh toán trong vòng 30 phút: {link_payment}(Vui lòng không bỏ sót link này)" + return data, order_id, cart_id + else: + print(f"Hệ thống không thể xác nhận đơn hàng. Phản hồi: {confirm_data.get('body')}") + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + chat_cart.status = chat_cart.backup_status + chat_cart.confirmed_order = chat_cart.backup_confirmed_order + chat_cart.save() + server_java.confirm_cancel_AI(int(user_id),token,order_id) + server_java.remove_all_cartItem(int(user_id), cart_id, token) + + except Exception as e: + print(f"ERROR in _execute_order_confirmation: {e}") + chat_cart.status = "failed" + chat_cart.save() + return f"Đã xảy ra lỗi khi xác nhận đơn hàng: {e}. Giỏ hàng của bạn vẫn được lưu." + + +async def _handle_simple_query(task: str, user_id: int, languages: List[str], role: str,stop_event: Optional[asyncio.Event],chat_id: str = "") -> str: + """Xử lý một câu hỏi SQL đơn giản.""" + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + normalized_task = filter_sql_injection_1.normalize_query(task) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + filtered_input = filter_sql_injection_1.filter_sql_injection(normalized_task) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + filtered_role_input = filter_role_1.filter_role(filtered_input) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + query_result = await chat_sql.execute_query_user(filtered_role_input, user_id, languages, role) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + return query_result_1.query_result(task, query_result) + + +async def _handle_analysis_intent(task: str, user_id: int, languages: List[str], role: str, stop_event: Optional[asyncio.Event],chat_id: str = "") -> str: + """Xử lý các tác vụ yêu cầu phân tích, sinh code và vẽ biểu đồ.""" + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + normalized_task = filter_sql_injection_1.normalize_query(task) + filtered_input = filter_sql_injection_1.filter_sql_injection(normalized_task) + filtered_role_input = filter_role_1.filter_role(filtered_input) + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + output = await chat_sql.generate_and_save_code( + filtered_role_input, + user_id, + role, + languages, + stop_event, + chat_id=chat_id +) + + + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + result, path_image = output if isinstance(output, tuple) else (output, None) + + if path_image and os.path.exists(path_image): + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check # Trả về kết quả đã có + + images_md = upload_images_to_cloudinary_and_markdown(path_image) + if images_md: + result = f"### Biểu đồ được tạo:\n\n{images_md}\n\n{result}" + + return result + + +# --- Hàm chính (Main Function) --- + +async def agent_sql(task: str, user_id: int, languages: str, role: str, chat_id: Any, token: str, stop_event: Optional[asyncio.Event] = None): + """ + Hàm chính điều phối các tác vụ, có khả năng dừng giữa chừng. + """ + # KIỂM TRA DỪNG NGAY LẬP TỨC + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + # 1. KIỂM TRA AN TOÀN BAN ĐẦU + if filter_sql_injection_1.filter_question_remove_restore(task): + return "Bạn không được phép khôi phục hoặc xóa dữ liệu. Vui lòng kiểm tra lại câu hỏi của bạn." + + # 2. PHÂN LOẠI Ý ĐỊNH CHÍNH: PHÂN TÍCH (cần sinh code) vs HỎI ĐÁP/MUA SẮM + is_analysis_task = filter_sql_injection_1.filter_task_analyze(task) + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + if is_analysis_task: + print("INFO: Nhận diện ý định PHÂN TÍCH.") + if result_check: + return result_check + data = await _handle_analysis_intent(task, user_id, languages, role, stop_event,chat_id) + if result_check: + return result_check + return data, False + + + # 3. PHÂN LOẠI Ý ĐỊNH PHỤ: MUA SẮM vs HỎI ĐÁP THÔNG THƯỜNG + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + cart_status = "Yes (Đang Pending)" if chat_history_has_unconfirmed_cart(chat_id) else "No (Không có)" + prompt_check_shopping = user_intent.build_detailed_user_intent_prompt( + user_question=task, + chat_history=get_chat_history(chat_id), + cart_status=cart_status + ) + + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + + intent_result = tool_call.generate(prompt_check_shopping) + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + if str(intent_result.get("type")) == "shopping_question": + print("INFO: Nhận diện ý định MUA SẮM.") + result = await _handle_shopping_intent(task, user_id, chat_id, token, languages, role, stop_event) + result = query_result_1.query_result(task, result) + return result, True + else: + print("INFO: Nhận diện ý định HỎI ĐÁP THÔNG THƯỜNG.") + result = await _handle_simple_query(task, user_id, languages, role, stop_event,chat_id) + result = query_result_1.query_result(task, result) + return result, False + + +async def agent_general(task, user_id, languages, role, chat_id, token, stop_event): + task = filter_sql_injection_1.normalize_query(task) + # Kiểm tra stop event trước khi gọi xử lý nặng + result_check = await check_should_stop(chat_id, stop_event) + if result_check: + return result_check + # Gọi xử lý chính + result_task = asyncio.create_task(res_general.response_general(task)) + # Chờ xử lý, nhưng cũng kiểm tra stop event + while not result_task.done(): + if stop_event.is_set(): + print("Tín hiệu stop trong quá trình xử lý. Hủy task.") + result_task.cancel() + try: + await result_task + except asyncio.CancelledError: + print("Task đã bị huỷ.") + return None + await asyncio.sleep(0.1) # Cho các task khác thời gian chạy + + +function_mapping = { + "question_hello": agent_hello, + "question_information": agent_information, + "question_sql": agent_sql, + "question_general": agent_general + +} + + +from typing import Optional +async def process_toolcalls_with_order(toolcalls,token, stop_event: Optional[asyncio.Event] = None, chat_id:str = ""): + task_results = {} + ordered_results = {} + filtered_toolcalls = [tc for tc in toolcalls if tc["name"] in function_mapping] + for tc in filtered_toolcalls: + + orig_name = tc["name"] + task_func = function_mapping.get(orig_name) + if task_func is None: + continue + task_param = tc["args"].get("query", "") + user_id = tc.get("user_id", "unknown_user") + role = tc.get("role", "unknown_role") + language = tc.get("language", "unknown_language") + chat_id = tc.get("chat_id","unknow") + if asyncio.iscoroutinefunction(task_func): + result = await task_func(task_param, user_id, language,role,chat_id,token,stop_event) + else: + result = task_func(task_param, user_id, language,role,chat_id,token,stop_event) + if isinstance(result, tuple) and len(result) == 2: + result, is_shopping = result + else: + result = result + is_shopping = False + # Lưu kết quả theo id của tool_call + task_results[tc["id"]] = { + "result": result, + "user_id": user_id, + "role": role, + "language": language, + "is_shopping": is_shopping + } + + + ordered_results = {tc["id"]: task_results.get(tc["id"], {"result": "Không có kết quả"}) for tc in filtered_toolcalls} + return ordered_results \ No newline at end of file diff --git a/function/gemini_response/__init__.py b/function/gemini_response/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..99a8091366e2ac7f301452706d5cfdff5e320a0d --- /dev/null +++ b/function/gemini_response/__init__.py @@ -0,0 +1,2 @@ + + diff --git a/function/gemini_response/__pycache__/__init__.cpython-311.pyc b/function/gemini_response/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49b081c37c6735a35de0046f24bb4c6da2754dd6 Binary files /dev/null and b/function/gemini_response/__pycache__/__init__.cpython-311.pyc differ diff --git a/function/gemini_response/__pycache__/__init__.cpython-312.pyc b/function/gemini_response/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec7b9c0187ec3cb2f2845c661e9867324ec6b129 Binary files /dev/null and b/function/gemini_response/__pycache__/__init__.cpython-312.pyc differ diff --git a/function/gemini_response/__pycache__/filter_query_internal.cpython-311.pyc b/function/gemini_response/__pycache__/filter_query_internal.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..045d9f3f100efba7f5965b99ad1fb5f0373f230b Binary files /dev/null and b/function/gemini_response/__pycache__/filter_query_internal.cpython-311.pyc differ diff --git a/function/gemini_response/__pycache__/filter_query_internal.cpython-312.pyc b/function/gemini_response/__pycache__/filter_query_internal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6fd2d6b013c58eb36006b7004291e7e1536d14f Binary files /dev/null and b/function/gemini_response/__pycache__/filter_query_internal.cpython-312.pyc differ diff --git a/function/gemini_response/__pycache__/get_table.cpython-311.pyc b/function/gemini_response/__pycache__/get_table.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0f18690888986dad8e92d6a7548b48c15d5bb42 Binary files /dev/null and b/function/gemini_response/__pycache__/get_table.cpython-311.pyc differ diff --git a/function/gemini_response/__pycache__/get_table.cpython-312.pyc b/function/gemini_response/__pycache__/get_table.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42a9591cacfcedba5102619c99656409d8c65b1f Binary files /dev/null and b/function/gemini_response/__pycache__/get_table.cpython-312.pyc differ diff --git a/function/gemini_response/__pycache__/response_all.cpython-311.pyc b/function/gemini_response/__pycache__/response_all.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7373ed7d57b306728eab388832bc5e2a38b6b369 Binary files /dev/null and b/function/gemini_response/__pycache__/response_all.cpython-311.pyc differ diff --git a/function/gemini_response/__pycache__/response_all.cpython-312.pyc b/function/gemini_response/__pycache__/response_all.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a685a23e49fbc8cdb4cebc124880c58575705ff6 Binary files /dev/null and b/function/gemini_response/__pycache__/response_all.cpython-312.pyc differ diff --git a/function/gemini_response/__pycache__/response_general.cpython-311.pyc b/function/gemini_response/__pycache__/response_general.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bdc0e9683debf4b3aa314b73e7db09cda40bf4b1 Binary files /dev/null and b/function/gemini_response/__pycache__/response_general.cpython-311.pyc differ diff --git a/function/gemini_response/__pycache__/response_general.cpython-312.pyc b/function/gemini_response/__pycache__/response_general.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1b86b2d01a20a37bda1d96dbe2ff0b70e8514a6 Binary files /dev/null and b/function/gemini_response/__pycache__/response_general.cpython-312.pyc differ diff --git a/function/gemini_response/__pycache__/response_hello.cpython-311.pyc b/function/gemini_response/__pycache__/response_hello.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f02f492466924d6990c47a820cfbf1d636c594d Binary files /dev/null and b/function/gemini_response/__pycache__/response_hello.cpython-311.pyc differ diff --git a/function/gemini_response/__pycache__/response_hello.cpython-312.pyc b/function/gemini_response/__pycache__/response_hello.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b91a4613cd7958c8ab9128576ab9cd16e524cdb7 Binary files /dev/null and b/function/gemini_response/__pycache__/response_hello.cpython-312.pyc differ diff --git a/function/gemini_response/filter_query_internal.py b/function/gemini_response/filter_query_internal.py new file mode 100644 index 0000000000000000000000000000000000000000..dd2028b54d62d5ff8a069d26b770d5096de016ba --- /dev/null +++ b/function/gemini_response/filter_query_internal.py @@ -0,0 +1,321 @@ +import os +import google.generativeai as genai +import os, sys +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) # Lên 2 cấp +sys.path.append(project_root) +import asyncio +from support import get_key + +api_key = get_key.get_random_api_key() +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +genai.configure(api_key=api_key) + + +PROMPT_CLASSIFICATION = """ +Bạn là một chuyên gia trong việc phân loại câu hỏi và định tuyến chúng đến đúng hàm xử lý. +Hãy phân tích câu hỏi của người dùng và xác định **một trong bốn** hàm phù hợp từ danh sách dưới đây. +Chỉ trả về **tên hàm chính xác** (ví dụ: `question_sql`), không kèm bất kỳ nội dung nào khác. + +### 1. `question_information(query: str)` +Trả lời các câu hỏi về thông tin liên hệ của cửa hàng, thông tin về sinh viên, tên đề tài các nội dung liên quan mà không phải sql +➡️ **Dùng khi câu hỏi liên quan đến thông tin liên hệ của cửa hàng**, bao gồm: + - Số điện thoại liên hệ. + - Địa chỉ cửa hàng. + - Giờ làm việc. + - Email hỗ trợ. + - Các kênh liên hệ khác (Facebook, Zalo, v.v.). + +⚠ **Không sử dụng hàm này nếu câu hỏi liên quan đến sản phẩm, giá cả, chương trình khuyến mãi, đơn hàng, cách pha chế, hoặc gợi ý đồ uống.** + +--- + +### 2. `question_sql(query: str)` +➡️ **Dùng khi câu hỏi liên quan đến truy vấn dữ liệu từ database**, bao gồm các bảng sau: + +✅ **Danh mục sản phẩm & sản phẩm** + - Các loại trà, nước ép, sinh tố, soda, cà phê, trà thảo dược,... + - Danh sách sản phẩm, chi tiết sản phẩm, thông tin sản phẩm theo danh mục. + - Sản phẩm nào đang bán chạy nhất? + - Sản phẩm nào được đánh giá tốt nhất? + +✅ **Giỏ hàng & yêu thích** + - Thông tin giỏ hàng, danh sách sản phẩm trong giỏ. + - Sản phẩm yêu thích của người dùng. + +✅ **Đơn hàng & thanh toán** + - Lịch sử đặt hàng, trạng thái đơn hàng. + - Cách thanh toán, phương thức thanh toán có sẵn. + - Chi tiết vận chuyển và giao hàng. + +✅ **Đánh giá & lịch sử giá** + - Sản phẩm nào có đánh giá cao nhất? + - Lịch sử thay đổi giá của một sản phẩm cụ thể. + +✅ **Người dùng & tài khoản** + - Lịch sử mua hàng của người dùng. + - Thông tin tài khoản, số dư tài khoản. + - Các voucher hiện có của người dùng (bảng `user_voucher`). + - Voucher nào có thể sử dụng? Voucher nào đã hết hạn? + - Người dùng đã sử dụng những voucher nào trong đơn hàng nào? + - Số lần sử dụng còn lại của voucher. + +✅ **Bài viết & tin tức** + - Bài viết liên quan đến sản phẩm. + - Tin tức mới nhất về sản phẩm và chương trình khuyến mãi. + +✅ **Mã giảm giá & voucher** + - Các mã giảm giá hiện có. + - Điều kiện áp dụng voucher. + - Danh sách voucher theo từng loại sản phẩm. +- Quyết định mua hàng, loại bỏ/thêm/ cập nhật sản phẩm khỏi giỏ hàng + +⚠ **Không dùng hàm này cho câu hỏi về thông tin liên hệ của cửa hàng.** + +--- + +### 3. `question_hello(query: str)` +➡️ **Dùng khi câu hỏi thuộc nhóm chào hỏi**, chẳng hạn như: + - "Xin chào", "Chào bạn", "Hello", "Hi", "Hey", "Good morning", "Good evening". + - Câu xã giao như: "Hôm nay bạn thế nào?", "Chúc một ngày tốt lành". + +⚠ **Không áp dụng cho câu hỏi về sản phẩm, cửa hàng, hoặc truy vấn dữ liệu.** + +--- + +### 4. `question_general(query: str)` +➡️ **Dùng khi câu hỏi không thuộc bất kỳ nhóm nào trên.** + - Không liên quan đến SQL. + - Không liên quan đến thông tin liên hệ của cửa hàng. + - Phân biệt rõ ràng tên của cửa hàng là `Hmdrinks` các tên mà có dấu hiệu khác tên này(tên riêng cửa hàng ví dụ như: cửa hàng Trung Nguyên,..). Các tên riêng cửa hàng mà khác Hmdrink đều thuộc question_general + - Không phải câu chào hỏi. + +📌 **Yêu cầu của bạn:** +- **Phân tích câu hỏi** của người dùng. +- **Chọn đúng hàm xử lý** từ danh sách trên. +- **Chỉ trả về tên hàm chính xác**, không kèm bất kỳ nội dung nào khác. + +🔍 **Ví dụ:** +- **Input:** "Cửa hàng mở cửa lúc mấy giờ?" → **Output:** `question_information` +- **Input:** "Sản phẩm nào đang bán chạy nhất?" → **Output:** `question_sql` +- **Input:** "Xin chào!" → **Output:** `question_hello` +- **Input:** "Bạn có thể giúp tôi với một câu hỏi khác không?" → **Output:** `question_general` +- **Input:** "thông tin về số điện thoại Ủy ban nhân dân tỉnh Khánh Hòa." → nhìn có vẻ giống như là hỏi về số điện thoại nhưng chú ý vế sau"Ủy ban nhân dân tỉnh Khánh Hòa". Điều này không liên quan nên **Output:** `question_general` +""" + + +async def response_general(input:str,list_history)->str: + from support import get_key + api_key = get_key.get_random_api_key() + genai.configure(api_key=api_key) + generation_config = { + "temperature": 0.5, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 8192, + "response_mime_type": "text/plain", +} + + model = genai.GenerativeModel( + model_name="gemini-2.5-flash-preview-05-20", + generation_config=generation_config, +) + prompt = f""" + Vui lòng đọc kĩ càng mô tả này để xác định: + {PROMPT_CLASSIFICATION} + Câu hỏi của người dùng: {input}. + Chỉ cần trả lại tên hàm chính xác thôi. Ví dụ chỉ cần trả: question_hello — không cần trả thêm gì khác. + Lưu ý rằng phải xác định đúng tên hàm. Với các câu hỏi thuộc nhóm question_sql, chúng thường hỏi về sản phẩm, tên sản phẩm, danh sách, v.v. — cần phân biệt chính xác để xác định đúng agent cần gọi. Hãy tự suy luận và phản hồi chính xác. + + """ + + try: + model = genai.GenerativeModel( + model_name="gemini-2.5-flash-preview-05-20", + generation_config=generation_config + ) + + chat_session = model.start_chat( + history=[ + { + "role": "user", + "parts": [prompt] + } + ] + ) + + response = await chat_session.send_message_async("Vui lòng chỉ phản hồi lại cho mình câu trả lời, không cần giải thích gì thêm") + return response.text.strip() + + except Exception as e: + print(f"[Lỗi lần 1]: {e} — Thử lại sau 2 giây...") + await asyncio.sleep(2) + + try: + # Có thể thay API key khác nếu dùng luân phiên + api_key_retry = get_key.get_random_api_key() + genai.configure(api_key=api_key_retry) + + model_retry = genai.GenerativeModel( + model_name="gemini-2.5-flash-preview-05-20", + generation_config=generation_config + ) + + chat_session_retry = model_retry.start_chat( + history=[ + { + "role": "user", + "parts": [prompt] + } + ] + ) + + response_retry = await chat_session_retry.send_message_async("Vui lòng chỉ phản hồi lại cho mình câu trả lời, không cần giải thích gì thêm") + return response_retry.text.strip() + + except Exception as retry_error: + print(f"[Lỗi lần 2]: {retry_error}") + raise Exception("Gọi mô hình thất bại sau khi thử lại.") + +# chat_session = model.start_chat( +# history=[ +# { +# "role": "user", +# "parts": [ +# f""" +# {PROMPT_CLASSIFICATION} + +# Câu hỏi của người dùng: {input}. +# Chỉ cần trả lại tên hàm chính xác thôi.ví dụ chỉ cần trả question_hello như vậy thôi không cần trả gì thêm. +# Lưu ý rằng xác định rõ xem thuộc hàm nào. Với các câu hỏi question_sql luôn là các câu hỏi có thể hỏi về các sản phẩm, tên sản phẩm, nên cần phân biệt kĩ càng để xác định chính xác agent mà mình muốn gọi hãy tự suy luận và trả lời cho mình. +# """, +# ], +# } +# ] +# ) + +# response = chat_session.send_message("Vui lòng chỉ phản hồi lại cho mình câu trả lời, không cần giải thích gì thêm") + +# return response.text + + +async def response_rename_question(input: str, list_history, question_create) -> str: + api_key = get_key.get_random_api_key() + genai.configure(api_key=api_key) + """ + Hàm bất đồng bộ để phân tích và điều chỉnh lại câu hỏi của người dùng dựa trên lịch sử trò chuyện. + + Args: + input (str): Câu hỏi hiện tại của người dùng cần xử lý. + list_history (List[str]): Danh sách lịch sử các câu hỏi trước đó của người dùng. + + Returns: + str: Câu hỏi đã được điều chỉnh (nếu cần) hoặc câu hỏi gốc nếu không cần thay đổi. + + Raises: + ValueError: Nếu input rỗng hoặc list_history không hợp lệ. + Exception: Nếu có lỗi khi gọi API của mô hình AI. + """ + + if not input or not isinstance(input, str): + raise ValueError("Câu hỏi đầu vào không được rỗng và phải là chuỗi ký tự") + if not isinstance(list_history, list): + raise ValueError("Lịch sử trò chuyện phải là một danh sách") + + generation_config = { + "temperature": 0.6, # Độ sáng tạo của mô hình (0.0 - 1.0, 0.5 là trung bình) + "top_p": 0.95, # Lọc xác suất tích lũy để chọn từ (0.95 là tiêu chuẩn) + "top_k": 40, # Số lượng từ có xác suất cao nhất được xem xét + "max_output_tokens": 12800, # Số lượng token tối đa trong phản hồi + "response_mime_type": "text/plain" # Định dạng phản hồi là văn bản thuần + } + + #gemini-2.0-flash-thinking-exp-01-21 + model = genai.GenerativeModel( + model_name="gemini-2.5-flash-preview-05-20", # Tên mô hình AI được sử dụng + generation_config=generation_config # Áp dụng cấu hình đã thiết lập + ) + + prompt = f""" +Bạn đang xử lý một yêu cầu phân tích cuộc hội thoại từ người dùng. + +### Dữ liệu đầu vào: +- Câu hỏi đầy đủ mà người dùng vừa gửi: {input} +- Câu hỏi được trích xuất ra một phần: {question_create} (Tập trung vào câu hỏi này) +- Danh sách lịch sử câu hỏi gần đây của người dùng: list_history {list_history} + +### Nhiệm vụ: +1. **Phân tích kỹ 2–4 câu hỏi cuối trong lịch sử chat** (`list_history`) để xác định xem câu hỏi hiện tại (`question_create`) **có phụ thuộc hoặc liên quan ngữ cảnh** đến các câu hỏi trước đó không. +2. Nếu có liên quan (ví dụ: tên sản phẩm, size, số lượng, mục đích,...), bạn cần **điều chỉnh lại `question_create`** để: + - Hoàn chỉnh hơn về ngữ nghĩa. + - Khớp với bối cảnh cuộc trò chuyện hiện tại. + - Không bị thiếu ý so với `input`. + +3. Ngược lại, **nếu `question_create` đã đầy đủ, rõ ràng và không phụ thuộc vào ngữ cảnh**, thì **giữ nguyên** không cần chỉnh sửa. + +4. Tuyệt đối **không được chỉ chấp nhận `question_create` như nó đang có mà không phân tích kỹ `input` và `list_history`**. Vì đôi khi `question_create` chỉ là phần trích thiếu ý, không đủ ngữ cảnh nếu tách rời. +### Quy tắc: +- Nếu `question_create` là các câu **liên quan đến thông tin cửa hàng** (giờ mở cửa, địa chỉ, số điện thoại, email, Facebook, Zalo...) hoặc **câu chào hỏi xã giao** (“Hi”, “Hello”, “Chúc bạn một ngày tốt lành”, v.v.), **thì không cần chỉnh sửa**. +- Nếu `question_create` được trích xuất từ `input` và đã **đáp ứng đầy đủ ý** so với `input` và **không phụ thuộc gì vào `list_history`**, **cũng không cần chỉnh sửa**. +- Trong các trường hợp khác, nếu có sự phụ thuộc vào các câu hỏi gần đây trong `list_history`, bạn cần sửa lại `question_create` sao cho: + - Khớp với ngữ cảnh. + - Ngữ pháp rõ ràng. + - Không làm mất đi ý định gốc từ `input`. +### Quan trọng: +- Khi có các gì liên quan đến giỏ thì hãy chuyển thành giỏ hàng để rõ ràng hon. +### Ví dụ minh họa: +- Nếu `input` là: "Thêm trà dâu size L 1 ly và nước ép dưa hấu size L 5 ly vào giỏ" +- Mà `question_create` chỉ là: "Thêm trà dâu size L 1 ly" + Thì điều này **vẫn chấp nhận được** vì `question_create` nằm trọn trong `input`, **không phụ thuộc ngữ cảnh khác**. +### Ví dụ minh họa sai: +- Nếu `input` là: "Danh sách các mã khuyến mãi còn sử dụng được mà tôi sỡ hữu và hãy thêm sản phẩm trà chanh size L 5 ly vào giỏ hàng" +- Mà `question_create` chỉ là: "'Danh sách các mã giảm giá mà người dùng hiện tại đang sở hữu và còn hiệu lực sử dụng" + Thì điều này **vẫn chấp nhận được** vì nó đã bao gồm 1 phần trong câu hỏi và nếu bạn cố tình tạo ra nguyên câu gốc là Danh sách các mã khuyến mãi còn sử dụng được mà tôi sỡ hữu và hãy thêm sản phẩm trà chanh size L 5 ly vào giỏ hàng thì điều này hoàn toàn sai về mặt logic nó dẫn đến dư thừa. +### nếu câu hỏi là xác nhận mua hàng đặt hàng hãy để chính xác câu hỏi trong đó. không cần phait thay đổi, các câu hỏi mua hàng phải giữ được đúng ý nghĩa của chúng. không được phép làm mới tạo quá lố. Bạn luôn phải suy nghĩ sao cho cẩn thận nhất. +Hãy luôn ưu tiên tính rõ ràng, mạch lạc, hợp ngữ cảnh và đúng ý người dùng. Nếu cần thiết, sử dụng lại thông tin từ `input` để hoàn chỉnh câu hỏi trích xuất. +""" + + + try: + chat_session = model.start_chat( + history=[{ + "role": "user", + "parts": [prompt] + }] + ) + + response = await chat_session.send_message_async( + "Vui lòng chỉ phản hồi lại cho mình câu trả lời, không cần giải thích gì thêm" + ) + + return response.text.strip() + + except Exception as e: + print(f"[Lỗi xảy ra]: {e}. Đang thử lại sau 2 giây...") + import asyncio + await asyncio.sleep(1) + + try: + api_key_retry = get_key.get_random_api_key() + print(api_key_retry) + genai.configure(api_key=api_key_retry) + model_retry = genai.GenerativeModel( + model_name="gemini-2.5-flash-preview-05-20", + generation_config=generation_config + ) + chat_session_retry = model_retry.start_chat( + history=[{ + "role": "user", + "parts": [prompt] + }] + ) + + response_retry = await chat_session_retry.send_message_async( + "Vui lòng chỉ phản hồi lại cho mình câu trả lời, không cần giải thích gì thêm" + ) + + return response_retry.text.strip() + + except Exception as retry_error: + print(f"[Thất bại lần 2]: {retry_error}") + raise Exception("Gọi mô hình thất bại sau khi thử lại.") diff --git a/function/gemini_response/get_table.py b/function/gemini_response/get_table.py new file mode 100644 index 0000000000000000000000000000000000000000..99dad67034b7801b64a7b4061c38d0dce484e4a3 --- /dev/null +++ b/function/gemini_response/get_table.py @@ -0,0 +1,232 @@ +from langchain_community.utilities.sql_database import SQLDatabase +from langchain_experimental.sql import SQLDatabaseChain +import os, sys +import os +from dotenv import load_dotenv + +# Load biến môi trường từ file .env +BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) # hoặc ".." tùy vị trí +dotenv_path = os.path.join(BASE_DIR, ".env") +import os +from dotenv import load_dotenv + +# # Gán đường dẫn tuyệt đối đến file .env +# dotenv_path = r"d:\HmDrinks_Chat\V2\chatbot - 19_5\.env" + +from dotenv import load_dotenv, find_dotenv + +# Tự động tìm file .env gần nhất trong cây thư mục cha +load_dotenv(find_dotenv(), override=True) + +# Debug kiểm tra +# print(">> DEBUG: dotenv_path =", dotenv_path) +# print(">> DEBUG: DB_HOST =", os.getenv("DB_HOST")) +# print(">> DEBUG: DB_USER =", os.getenv("DB_USER")) +# print(">> DEBUG: DB_PASSWORD =", os.getenv("DB_PASSWORD")) +# print(">> DEBUG: DB_NAME =", os.getenv("DB_NAME")) +# print(">> DEBUG: DB_PORT =", os.getenv("DB_PORT")) + + + + +DB_HOST = os.getenv("DB_HOST") +DB_USER = os.getenv("DB_USER") +DB_PASSWORD = os.getenv("DB_PASSWORD") +DB_NAME = os.getenv("DB_NAME") +DB_PORT = os.getenv("DB_PORT") +import os +from urllib.parse import quote + +password = os.getenv("DB_PASSWORD") +DB_PASSWORD = quote(password) +# Tạo connection string +connection_uri = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" +print(">> DEBUG connection_uri:", connection_uri) +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) # Lên 2 cấp +sys.path.append(project_root) + + + +from support import get_key + +api_key = get_key.get_random_api_key() +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +os.environ["GOOGLE_API_KEY"] = "AIzaSyCO-RlqYewC4e9BEPoC8m-AxHUY7J3_o2E" +import prompt.prompt_main as prompt +from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI +llm1 = ChatGoogleGenerativeAI(model='gemini-2.0-flash-thinking-exp-01-21',temperature=0.6,api_key=api_key) + + + +db = SQLDatabase.from_uri(connection_uri) +db.get_table_info_no_throw() +import re +db_chain = SQLDatabaseChain.from_llm(llm=llm1,db=db,prompt= prompt.PROMPT) +import prompt.prompt_table as prompt_table +import prompt.prompt_create_table as prompt_create +import json + +# import google.generativeai as genai +# genai.configure(api_key=api_key) + +# async def response_general(input:str)->list: +# api_key = get_key.get_random_api_key() +# genai.configure(api_key=api_key) +# generation_config = { +# "temperature": 1, +# "top_p": 0.95, +# "top_k": 40, +# "max_output_tokens": 8192, +# "response_mime_type": "text/plain", +# } + + +# model = genai.GenerativeModel( +# model_name="gemini-2.5-flash-preview-04-17", +# generation_config=generation_config, +# ) + +# chat_session = model.start_chat( +# history=[ +# { +# "role": "user", +# "parts": [ +# f"""Hãy vui lòng đọc kỹ các bảng sau đây gồm có các thuộc tÍNH, khóa ngoại của các bảng sau khi tạo bảng trong MySQL. {prompt_create.PROMPT_TABLE}. +# Mình có định nghĩa các thuộc tính tham số, mối quan hệ các bảng sau đây: {prompt_table}. Luôn đọc kĩ càng các thông tin mô tả này. Hãy xác định kĩ càng các bước, mô tả của mình để lấy các bảng cần dùng chính xác nhất. +# Hãy cân nhắc cho câu hỏi sau đây: {input}. Phân tích câu hỏi và chọn các bảng cần thiết để có thể trả lời câu hỏi. +# Hãy phân biệt rõ giữa đơn hàng bình thường(orders) và đơn hàng nhóm(group_orders). +# ** Cách trả lời: +# - Bạn vui lòng trả lại danh sách các table liên quan. Vui lòng ghép chúng vào trong list và nối với nhau bằng , ví dụ ["cart","user"]. +# - Luôn bao gồm bảng user trong mọi trường hợp câu hỏi. +# - Chỉ lấy tên các bảng mà mình đã cung cấp, không được tự ý sinh thêm bảng. Điều này là cấm. +# - Không được phép dùng bảng "Token, user_chat, notification" +# - Nếu trong list có bảng cart thì phải kèm theo cart_item +# - Nếu trong list có bảng cart_group thì phải kèm theo cart_item_group +# - Nếu trong bảng có chứa các bảng như cart_item, cart_item_group thì vui lòng kèm theo các bảng liên quan về sản phẩm như product_translation và product_variants, product +# - Nếu trong list có bảng category thì phải kèm theo category_translation, product phải kèm theo product_translation và product_variant +# - Nếu trong list có bảng group_orders thì phải kèm theo group_order_members. +# - Nếu trong list có bảng group_order_members thì phải kèm theo cart_group. +# - Nếu trong list trả về có các bảng như product, post, hay category phải kèm theo bảng translation liên kết với nó. +# - Khi trong list có liên quan product hãy luôn cung cấp product_variants để có thể cung cấp thêm giá tùy trương hợp. +# - Không yêu cầu giải thích gì thêm chỉ cần trả về list theo mình mô tả ví dụ. +# - Luôn đảm bảo Xác định chính xác bảng cần dùng cho câu hỏi, không dư thừa bảng, không bỏ sót bảng cần thiết. +# - Luôn luôn trả về đúng list []. Cấm trả sai định dạng mà mình đã cung cấp""", +# ], +# } +# ] +# ) + +# response = chat_session.send_message("Hãy luôn đảm bảo rằng bạn tuân thủ những gì mình đưa ra, không được phép làm khác đi.") +# data = response.text +# if data.strip(): # Kiểm tra dữ liệu không rỗng +# try: +# # Cố gắng parse JSON +# data_list = json.loads(data) +# return data_list +# except json.JSONDecodeError: +# # Nếu không phải JSON, thử tìm danh sách bảng trong chuỗi văn bản +# match = re.search(r'\[(.*?)\]', data) +# if match: +# # Lấy phần trong dấu [ ], tách thành list +# items_str = match.group(1) +# items = [item.strip().strip('"').strip("'") for item in items_str.split(',')] +# print("Danh sách bảng đã trích xuất:", items) +# return items +# else: +# print("Không tìm thấy danh sách bảng trong dữ liệu văn bản!") +# else: +# print("Dữ liệu đầu vào rỗng!") + + +import asyncio +import json +import re +from support import get_key +import google.generativeai as genai + +async def response_general(input: str) -> list: + async def call_model_once() -> list: + # Lấy API key và cấu hình + api_key = get_key.get_random_api_key() + genai.configure(api_key=api_key) + + generation_config = { + "temperature": 1, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 8192, + "response_mime_type": "text/plain", + } + + model = genai.GenerativeModel( + model_name="gemini-2.5-flash-preview-04-17", + generation_config=generation_config, + ) + + chat_session = model.start_chat( + history=[ + { + "role": "user", + "parts": [ + f"""Hãy vui lòng đọc kỹ các bảng sau đây gồm có các thuộc tÍNH, khóa ngoại của các bảng sau khi tạo bảng trong MySQL. {prompt_create.PROMPT_TABLE}. + Mình có định nghĩa các thuộc tính tham số, mối quan hệ các bảng sau đây: {prompt_table}. Luôn đọc kĩ càng các thông tin mô tả này. Hãy xác định kĩ càng các bước, mô tả của mình để lấy các bảng cần dùng chính xác nhất. + Hãy cân nhắc cho câu hỏi sau đây: {input}. Phân tích câu hỏi và chọn các bảng cần thiết để có thể trả lời câu hỏi. + Hãy phân biệt rõ giữa đơn hàng bình thường(orders) và đơn hàng nhóm(group_orders). + ** Cách trả lời: + - Bạn vui lòng trả lại danh sách các table liên quan. Vui lòng ghép chúng vào trong list và nối với nhau bằng , ví dụ ["cart","user"]. + - Luôn bao gồm bảng user trong mọi trường hợp câu hỏi. + - Chỉ lấy tên các bảng mà mình đã cung cấp, không được tự ý sinh thêm bảng. Điều này là cấm. + - Không được phép dùng bảng "Token, user_chat, notification" + - Nếu trong list có bảng cart thì phải kèm theo cart_item + - Nếu trong list có bảng cart_group thì phải kèm theo cart_item_group + - Nếu trong bảng có chứa các bảng như cart_item, cart_item_group thì vui lòng kèm theo các bảng liên quan về sản phẩm như product_translation và product_variants, product + - Nếu trong list có bảng category thì phải kèm theo category_translation, product phải kèm theo product_translation và product_variant + - Nếu trong list có bảng group_orders thì phải kèm theo group_order_members. + - Nếu trong list có bảng group_order_members thì phải kèm theo cart_group. + - Nếu trong list trả về có các bảng như product, post, hay category phải kèm theo bảng translation liên kết với nó. + - Khi trong list có liên quan product hãy luôn cung cấp product_variants để có thể cung cấp thêm giá tùy trương hợp. + - Không yêu cầu giải thích gì thêm chỉ cần trả về list theo mình mô tả ví dụ. + - Luôn đảm bảo Xác định chính xác bảng cần dùng cho câu hỏi, không dư thừa bảng, không bỏ sót bảng cần thiết. + - Luôn luôn trả về đúng list []. Cấm trả sai định dạng mà mình đã cung cấp""", + ], + } + ] + ) + + response = await chat_session.send_message_async("Hãy luôn đảm bảo rằng bạn tuân thủ những gì mình đưa ra, không được phép làm khác đi.") + data = response.text + + if data.strip(): + try: + return json.loads(data) + except json.JSONDecodeError: + match = re.search(r'\[(.*?)\]', data) + if match: + items_str = match.group(1) + items = [item.strip().strip('"').strip("'") for item in items_str.split(',')] + print("✅ Danh sách bảng đã trích xuất:", items) + return items + else: + raise ValueError("Không tìm thấy danh sách bảng hợp lệ trong phản hồi văn bản.") + else: + raise ValueError("Dữ liệu phản hồi trống.") + + # Gọi lần đầu + try: + return await call_model_once() + except Exception as e: + print(f"⚠️ Lỗi lần đầu: {e} — thử lại sau 2 giây...") + await asyncio.sleep(2) + + try: + return await call_model_once() + except Exception as retry_error: + print(f"❌ Lỗi lần 2: {retry_error}") + raise Exception("Gọi mô hình thất bại sau khi thử lại.") + + +# if __name__ == "__main__": +# import asyncio +# asyncio.run(response_general("Danh sách bài đăng mới nhất")) \ No newline at end of file diff --git a/function/gemini_response/response_all.py b/function/gemini_response/response_all.py new file mode 100644 index 0000000000000000000000000000000000000000..9c454ab51a9610203902e745f51ea8f821d030df --- /dev/null +++ b/function/gemini_response/response_all.py @@ -0,0 +1,66 @@ +import os +import google.generativeai as genai + + +import os, sys +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) # Lên 2 cấp +sys.path.append(project_root) + +from support import get_key + +api_key = get_key.get_random_api_key() +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +genai.configure(api_key=api_key) + +async def response_all(input_original:str,result_multi:list)->str: + api_key = get_key.get_random_api_key() + genai.configure(api_key=api_key) + generation_config = { + "temperature": 1, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 128000, + "response_mime_type": "text/plain", +} +# gemini-2.0-flash-thinking-exp-01-21 + model = genai.GenerativeModel( + model_name="gemini-2.5-flash-preview-04-17", + generation_config=generation_config, +) + + chat_session = model.start_chat( + history=[ + { + "role": "user", + "parts": [ + f"""Bạn là một chuyên gia trong việc tổng hợp phản hồi từ các ý nhỏ của một câu hỏi và trả lời về đúng câu hỏi gốc từ nhiều câu hỏi. Nhiệm vụ của bạn là phối hợp các kết quả từ result nhỏ và tổng hợp lại sao cho thành một câu trả lời hợp lý. \n + Bạn hãy xử lý câu hỏi gốc sau: {input_original} và các câu trả lời nhỏ liên quan từ câu gốc: {result_multi}. + Khi trả lời chỉ trả lời từ nội dung mình đã cung cấp không được phép trả lời nội dung bên ngoài, không liên quan. + Lưu ý quan trọng(Chú Ý): Nếu câu hỏi có phần phân tích vui lòng giữ nguyên các link hình ảnh và phân tích được cung cấp không đụng chạm vào. + +- Nếu câu hỏi có yêu cầu về phân tích vui lòng không tóm gọn mà trình bày chi tiết, cụ thể ra theo result của phân tích đó. Ví dụ: "Phân tích tình thua thu mua của tôi trong tháng 4". Bạn chỉ dựa vào câu trả lời phân tích đó và dán lại không cần phải viết tóm gọn. Nhớ giữ nguyên +. Khi trả lời câu hỏi, hãy dùng lời lẽ đơn giản, dễ nắm bắt, ví dụ thực tế nếu cần, . Tránh dùng từ ngữ quá kỹ thuật hoặc kiểu trả lời máy móc, trích dẫn sách vở. Không áp dụng cho việc phân tích. Nếu có phân tích phải giữ lại chi tiết đầy đủ toàn bộ. +- Yêu cầu các câu hỏi phân tích vui lòng giữ nguyên result mà mình cung cấp không cần viết lại + + +Ví dụ: +- Nhưng vẫn bao gồm sự nghiêm túc trong câu trả lời. +- Nếu không chắc, hãy thẳng thắn nói "mình không rõ lắm, nhưng mình nghĩ là..." thay vì bịa ra câu trả lời nghe cho “ngầu”. +- Tuyệt đối không nhắc đến cơ sở dữ liệu, thuật toán, hoặc các khái niệm kỹ thuật trừ khi người dùng hỏi rõ ràng về chúng. +- kHÔNG NHẮC ĐẾN SQL khi trả lời lại cho người dùng + +Hãy luôn ưu tiên sự dễ hiểu, tự nhiên, và thân thiện trong mọi câu trả lời. +- Luôn giữ đầy đủ và nguyên vẹn tất cả các hình ảnh tôi cung cấp, không được bỏ sót bất kỳ link hay hình nào(Chỉ áp dụng cho câu phân tích) +- Các câu hỏi thường nếu câu trả lời có hình ảnh chỉ lấy về 1 hình duy nhất. +- Nếu task liên quan đến phân tích, vui lòng trích nguyên văn câu trả lời từ hình ảnh/tài liệu nếu có — không được viết lại, diễn giải lại, hay tóm tắt. +- Nếu task liên quan đến phân tích, Luôn luôn giữ các hình ảnh mà mình cung cấp. Vui lòng không bỏ sót dù là 1 link. Nếu task đó về phân tích vui lòng lấy luôn câu trả lời và thể hiện lại. Luôn luôn không được viết lại câu trả lời của câu hỏi phân tích + + """, + ], + } + ] +) + + response = chat_session.send_message("Vui lòng chỉ phản hồi lại câu trả lời, không cần giải thích gì thêm") + return response.text diff --git a/function/gemini_response/response_general.py b/function/gemini_response/response_general.py new file mode 100644 index 0000000000000000000000000000000000000000..d142e6896d7b46c1b89f515cc5f045552ff6e597 --- /dev/null +++ b/function/gemini_response/response_general.py @@ -0,0 +1,46 @@ +import os +import google.generativeai as genai +import os, sys +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) +sys.path.append(project_root) + +from support import get_key + +api_key = get_key.get_random_api_key() +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +genai.configure(api_key=api_key) + + +async def response_general(input:str)->str: + api_key = get_key.get_random_api_key() + genai.configure(api_key=api_key) + generation_config = { + "temperature": 1, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 8192, + "response_mime_type": "text/plain", +} + + model = genai.GenerativeModel( + model_name="gemini-2.0-flash", + generation_config=generation_config, +) + + chat_session = model.start_chat( + history=[ + { + "role": "user", + "parts": [ + f"""Bạn vui lòng phản hồi lại câu chào hỏi dưới đây với giọng điệu thận trọng cẩn thận nhắc nhở người dùng rằng những câu hỏi này sẽ không được phép trả lời. + Bạn hãy xử lý câu hỏi sau và trả lại cho mình kết quả: {input}. + Vui lòng chỉ trả lời theo đúng yêu cầu mình đưa ra. Không được phép trả lời ngoài lề. """, + ], + } + ] +) + + response = chat_session.send_message("Vui lòng chỉ phản hồi lại cho mình câu trả lời, không cần giải thích gì thêm") + + return response.text diff --git a/function/gemini_response/response_hello.py b/function/gemini_response/response_hello.py new file mode 100644 index 0000000000000000000000000000000000000000..a8ef830a644f79ab65f4fd962eacd33381ce591c --- /dev/null +++ b/function/gemini_response/response_hello.py @@ -0,0 +1,41 @@ +import os +import google.generativeai as genai +import os, sys +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) +sys.path.append(project_root) +from support import get_key +api_key = get_key.get_random_api_key() +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +genai.configure(api_key=api_key) + + +async def response_hello(input:str)->str: + api_key = get_key.get_random_api_key() + genai.configure(api_key=api_key) + generation_config = { + "temperature": 1, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 8192, + "response_mime_type": "text/plain", +} + + model = genai.GenerativeModel( + model_name="gemini-2.0-flash", + generation_config=generation_config, +) + + chat_session = model.start_chat( + history=[ + { + "role": "user", + "parts": [ + f"Bạn vui lòng phản hồi lại câu chào hỏi dưới đây với giọng điệu nhẹ nhàng dễ nghe, dễ giao tiếp nói chuyện. Bạn hãy xử lý câu hỏi sau và trả lại cho mình: {input}", + ], + } + ] +) + + response = chat_session.send_message("Vui lòng chỉ phản hồi lại câu trả lời, không cần giải thích gì thêm") + return response.text \ No newline at end of file diff --git a/function/prompt/__init__.py b/function/prompt/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/function/prompt/__pycache__/__init__.cpython-311.pyc b/function/prompt/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a70bd95ebbb9bed4c7d97680d28a4c6442e796a Binary files /dev/null and b/function/prompt/__pycache__/__init__.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/__init__.cpython-312.pyc b/function/prompt/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bca4536dbf18fdd591891baafec7f56883d745b Binary files /dev/null and b/function/prompt/__pycache__/__init__.cpython-312.pyc differ diff --git a/function/prompt/__pycache__/general_rule.cpython-311.pyc b/function/prompt/__pycache__/general_rule.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6fd9f2fdd5020d30e46849efcdef05408afa384 Binary files /dev/null and b/function/prompt/__pycache__/general_rule.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/general_rule.cpython-312.pyc b/function/prompt/__pycache__/general_rule.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d78422eb3e35f1cc57712fbb36031e766a0fdae Binary files /dev/null and b/function/prompt/__pycache__/general_rule.cpython-312.pyc differ diff --git a/function/prompt/__pycache__/personalization.cpython-311.pyc b/function/prompt/__pycache__/personalization.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b74d19db3a18724a754aadb6a15cd63d39cc66f Binary files /dev/null and b/function/prompt/__pycache__/personalization.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/personalization.cpython-312.pyc b/function/prompt/__pycache__/personalization.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69411de78e64fe53e09e02ef250040949eb75a83 Binary files /dev/null and b/function/prompt/__pycache__/personalization.cpython-312.pyc differ diff --git a/function/prompt/__pycache__/prompt_create_table.cpython-311.pyc b/function/prompt/__pycache__/prompt_create_table.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0365e710095aec1f97a208de1a9cb17f3f186a4c Binary files /dev/null and b/function/prompt/__pycache__/prompt_create_table.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/prompt_create_table.cpython-312.pyc b/function/prompt/__pycache__/prompt_create_table.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ebc23cd8947bc90ab08e7c1c08527528b9db0fd Binary files /dev/null and b/function/prompt/__pycache__/prompt_create_table.cpython-312.pyc differ diff --git a/function/prompt/__pycache__/prompt_custom.cpython-311.pyc b/function/prompt/__pycache__/prompt_custom.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab11cfec4f772eefbe645574a6c0a54fc635c79c Binary files /dev/null and b/function/prompt/__pycache__/prompt_custom.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/prompt_custom.cpython-312.pyc b/function/prompt/__pycache__/prompt_custom.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2accbb1e7865faecbac9af81e990a3d6935f4a01 Binary files /dev/null and b/function/prompt/__pycache__/prompt_custom.cpython-312.pyc differ diff --git a/function/prompt/__pycache__/prompt_detail_table.cpython-311.pyc b/function/prompt/__pycache__/prompt_detail_table.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5c2d313af1165451a3fe1d24a2497e702fbdd0b Binary files /dev/null and b/function/prompt/__pycache__/prompt_detail_table.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/prompt_detail_table.cpython-312.pyc b/function/prompt/__pycache__/prompt_detail_table.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a74e5dd003b579adb72a0176f5ead2aa62e596b Binary files /dev/null and b/function/prompt/__pycache__/prompt_detail_table.cpython-312.pyc differ diff --git a/function/prompt/__pycache__/prompt_main.cpython-311.pyc b/function/prompt/__pycache__/prompt_main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9a74c1f4b384b200c40dfd69128fff46045b895 Binary files /dev/null and b/function/prompt/__pycache__/prompt_main.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/prompt_main.cpython-312.pyc b/function/prompt/__pycache__/prompt_main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0aa73f53fab63e0412273a21c443050d60d2821c Binary files /dev/null and b/function/prompt/__pycache__/prompt_main.cpython-312.pyc differ diff --git a/function/prompt/__pycache__/prompt_selling.cpython-311.pyc b/function/prompt/__pycache__/prompt_selling.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f7692f7beca1f130dd38d7eea9800a5d6558f91 Binary files /dev/null and b/function/prompt/__pycache__/prompt_selling.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/prompt_selling.cpython-312.pyc b/function/prompt/__pycache__/prompt_selling.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa98d16256d1728786aef6e03119f90af0e31c88 Binary files /dev/null and b/function/prompt/__pycache__/prompt_selling.cpython-312.pyc differ diff --git a/function/prompt/__pycache__/prompt_syntax_insert.cpython-311.pyc b/function/prompt/__pycache__/prompt_syntax_insert.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29c8de603a07766994139734338c0c1a3a63728e Binary files /dev/null and b/function/prompt/__pycache__/prompt_syntax_insert.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/prompt_syntax_insert.cpython-312.pyc b/function/prompt/__pycache__/prompt_syntax_insert.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5b260dff9994f6467910ef6bdae22c9bc3adb44 Binary files /dev/null and b/function/prompt/__pycache__/prompt_syntax_insert.cpython-312.pyc differ diff --git a/function/prompt/__pycache__/prompt_table.cpython-311.pyc b/function/prompt/__pycache__/prompt_table.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41577fe0156802437155362437985b672aafd95a Binary files /dev/null and b/function/prompt/__pycache__/prompt_table.cpython-311.pyc differ diff --git a/function/prompt/__pycache__/prompt_table.cpython-312.pyc b/function/prompt/__pycache__/prompt_table.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbd64430e4f0b200810d2b8779e5271c501ed710 Binary files /dev/null and b/function/prompt/__pycache__/prompt_table.cpython-312.pyc differ diff --git a/function/prompt/general_rule.py b/function/prompt/general_rule.py new file mode 100644 index 0000000000000000000000000000000000000000..d35383ea220e35b1105bf4d65ae98cdcdd9391d3 --- /dev/null +++ b/function/prompt/general_rule.py @@ -0,0 +1,89 @@ +prompt_rule = """ +**General Rules for SQL Queries and Database Operations** + +### 1. **Data Visibility and Security** +- Ensure that sensitive fields such as `id`, `is_deleted`, `data_deleted`, `update_date`, and `create_date` are not exposed in query results. +- Ensure that sensitive fields such as `id`, `is_deleted`, `date_deleted`, `update_date`, and `create_date` are not exposed in query results, **except for queries related to products**. + +- Enforce strict role management to prevent unauthorized access to data. +- Users without the `ADMIN` role cannot access, view, or modify records of other users. +- Before processing a request, verify whether the requested information matches the authenticated user's identity. If there is a mismatch, return an immediate error: + ``` + ERROR: Unauthorized access to another user's information. + ``` +- If a user attempts to access personal details of another user via `ID`, `email`, `phone number`, or `full name`, first execute a validation query to check authorization. If unauthorized, return an error and stop execution. + +### 2. **SQL Query Optimization for Product Recommendations** +- You are an expert in SQL specializing in product recommendations. Your task is to optimize SQL queries to efficiently retrieve relevant product information by joining appropriate tables. +- Queries should include product details such as `size`, `price`, and `images`. +- Example queries to optimize: + - Best-selling products. + - Highest-rated products. + - Trending products for the current month. +- Ensure queries are optimized for performance and correctness. + +### 3. **Role-based Access Control** +- Users without the `ADMIN` role cannot add, modify, or delete records in the following tables: + - `category` + - `category_translation` + - `post` + - `post_translation` + - `orders` + - `order_item` + - `shipment` + - `payment` + - `product` + - `product_translation` + - `voucher` + - `product_variants` +- Any unauthorized attempt must return an immediate error: + ``` + ERROR: User does not have permission to perform this action. + ``` +- Only Shippers can update shipment statuses, following specific rules for `SUCCESS` and `CANCELLED` updates. + +### 4. **Update Operations in MySQL** +- When performing an `UPDATE` operation in MySQL, avoid direct self-references to prevent `Error Code: 1093`. +- Use a subquery with a derived table (temporary alias) instead of directly referencing the same table. +- Example: + ```sql + SET @pro_id_to_update = (SELECT pro_id FROM product WHERE pro_name = 'Trà ổi lá hồng'); + ``` +- Store intermediate results in variables before performing the update. + +### 5. **Logical Deletion Instead of Physical Deletion** +- No `DELETE` operations are allowed for any role on any table. +- Use logical deletion (`is_deleted` and `deletion_date`) instead. +- When restoring data, ensure that all related records follow the same logical approach: + - When a category (`category`) is deleted, all related products (`product`), `product_translation`, and `product_variants` must also be marked as deleted. + - When a product (`product`) is deleted, its related `product_variants` and `product_translation` must also be updated. + - When a product variant (`product_variants`) is deleted, related entries in the cart (`cart_item`) and favorite list (`favourite_item`) must be updated. + - When deleting a post, mark related vouchers as deleted and update the users' voucher status to `USED`. + - When restoring data, reverse the process by reactivating vouchers, restoring user voucher statuses, and updating relevant translations. + +### 6. **Revenue Calculation** +- When calculating revenue, refer to the `orders` table. +- Only include orders where: + - The payment was `SUCCESSFUL`. + - No refunds have been issued. + +### 7. **Handling Unauthorized Access** +- When a query involves accessing another user's details, execute a validation query first. +- If unauthorized, return an error and stop execution. +- Example error message: + ``` + ERROR: User does not have permission to access this data. + ``` +- Ensure robust error handling to address potential security violations. + +## **8. Xác Thực Danh Tính Trước Khi Xử Lý Yêu Cầu** +- **Quy tắc xác thực:** + - Nếu vai trò là **CUSTOMER hoặc SHIPPER**, hệ thống phải **xác minh rằng thông tin yêu cầu thuộc về chính người dùng đang xác thực** (dựa trên `user_id`). + +- **Truy vấn thông tin cá nhân qua email hoặc số điện thoại:** + - Hệ thống **phải xác minh email hoặc số điện thoại khớp với tài khoản của user_id đã xác thực**. + - Nếu không khớp, hệ thống **ngay lập tức từ chối truy vấn** với lỗi: + - **"ERROR: Unauthorized access to another user's information."** + + +""" \ No newline at end of file diff --git a/function/prompt/personalization.py b/function/prompt/personalization.py new file mode 100644 index 0000000000000000000000000000000000000000..b333a11b3bba590676756a03a3f36715f8647ef2 --- /dev/null +++ b/function/prompt/personalization.py @@ -0,0 +1,83 @@ +prompt_custom = """ +Bạn là một trợ lý kỹ thuật xử lý truy vấn SQL. Khi người dùng hỏi về tình trạng đơn hàng, tình trạng giao hàng, hoặc liên quan đến đơn mua – bạn PHẢI thực hiện logic sau: + +🎯 Mục tiêu: +Trả về đầy đủ thông tin trạng thái của: +- orders.status (trạng thái đơn hàng) +- payments.status (trạng thái thanh toán) +- shipment.status (trạng thái giao hàng) +- shipment_group.status (trạng thái nhóm giao hàng nếu có) + +🔄 Quy tắc JOIN: +- LUÔN LUÔN sử dụng LEFT JOIN giữa các bảng: + - orders LEFT JOIN payments + - payments LEFT JOIN shipment + - shipment LEFT JOIN shipment_group +- Điều kiện lọc `is_deleted = FALSE` của các bảng phải được đưa vào phần ON của JOIN, không đặt trong WHERE để tránh loại bỏ các bản ghi chưa phát sinh quan hệ. + +📦 WHERE: +- Lọc theo `orders.user_id = ?` hoặc `orders.order_id = ?` +- Luôn kiểm tra `orders.is_deleted = FALSE` + +📋 Cần tránh: +- Không được dùng INNER JOIN trong trường hợp này vì có thể làm mất dữ liệu khi đơn hàng chưa có thanh toán hoặc shipment. +- Nếu status của bảng nào là NULL thì hiểu là chưa tạo hoặc chưa xử lý tới bước đó. + +✅ Output SQL phải rõ ràng, đúng cấu trúc, đủ 4 trạng thái, an toàn, và dễ hiểu. + + +""" +prompt = """ +Mẫu dưới này chỉ có ý nghĩa tham khảo để bạn có thể hiểu và suy luận đúng cách về câu hỏi người dùng. +Đối với sản phẩm luôn ưu tiên hình ảnh, giá, size, màu sắc, và các thuộc tính cần thiết khác. +Đối với đơn hàng luôn ưu tiên số đơn hàng thành công, số đơn hàng thất bại, và các thuộc tính khác. + +Đây là prompt hướng dẫn bạn cách cá nhân hóa, hiểu rõ câu hỏi của người dùng với ý định của họ gồm có các mẫu và ví dụ: + +- Mẫu 1: số đơn giao hàng thành công. Đây là một câu hỏi mà cách suy luận của bạn phải về số đơn hàng thành công theo đúng ý hiểu nếu người dùng (CUSTOMER, SHIPPER) thì chỉ là số đơn của họ, còn với ADMIN thì đó là số đơn giao của cửa hàng. + +- Mẫu 2: Khi hỏi về bán. Bạn phải cung cấp được thứ nhất gồm có hình ảnh, giá, và các size tương ứng cho chúng chứ không thể thiếu những thuộc tính này. + +- Mẫu 3: Khi hỏi về các mẫu đồ uống nào, sản phẩm nào bạn phải cung cấp một cách đa dạng lên các sản phẩm chứ không phải 1,2 sản phẩm là xong. + +- Mẫu 4: Khi chuẩn hóa các câu hỏi trả về vui lòng dùng câu truy vấn không được phép trả về thuộc tính id vì đây là điều bắt buộc để đảm bảo an toàn cho hệ thống. + +- Mẫu 5: Ví dụ tôi yêu cầu 5 sản phẩm thì bạn phải lấy được 5 sản phẩm chứ không phải lấy 1,2. Tôi muốn bạn phải chính xác theo câu hỏi người dùng. + +- Mẫu 6: Khi người dùng hỏi về các món làm từ một nguyên liệu cụ thể (ví dụ: "đào", "xoài", "trà xanh"...), bạn phải hiểu đây là câu hỏi tìm kiếm sản phẩm có chứa nguyên liệu đó. Hãy truy xuất vào database bằng cách lọc theo thành phần nguyên liệu và trả về đầy đủ danh sách các sản phẩm phù hợp. Mỗi sản phẩm phải bao gồm: hình ảnh, tên sản phẩm, giá, các size (nếu có), và màu sắc hoặc thuộc tính khác nếu có. Tránh chỉ trả về 1-2 sản phẩm nếu có thể nhiều hơn. + +- Mẫu 7: Khi người dùng yêu cầu cập nhật số lượng một sản phẩm trong giỏ hàng "thành tối đa", bạn phải hiểu rõ "tối đa" ở đây nghĩa là bằng số lượng tồn kho (stock) hiện có của biến thể sản phẩm (variant) tương ứng. +Ví dụ câu hỏi: "hãy cập nhật số lượng soda dâu tây size M trong giỏ của tôi thành tối đa". +Hành động đúng là: xác định sản phẩm theo tên và size, truy xuất biến thể phù hợp, lấy giá trị stock hiện tại của biến thể đó, và cập nhật số lượng trong giỏ hàng bằng giá trị này. +Không được dùng id sản phẩm, và phải đảm bảo thao tác chính xác theo yêu cầu ngữ nghĩa. + +- Mẫu 8: Khi người dùng hỏi về tình trạng đơn hàng, giao hàng thì phải luôn luôn lấy status tương ứng gồm có order, payment, shipment, shipmentgroup đều phải lấy status kèm theo và vận dụng câu sql để sinh ra hợp lý. KHi ghép join các bảng order payment hay shipment luôn luôn dùng "left join". Phải tuân thủ theo: Bạn là một trợ lý kỹ thuật xử lý truy vấn SQL. Khi người dùng hỏi về tình trạng đơn hàng, tình trạng giao hàng, hoặc liên quan đến đơn mua – bạn PHẢI thực hiện logic sau: + +🎯 Mục tiêu: +Trả về đầy đủ thông tin trạng thái của: +- orders.order_id (Mã đơn hàng: Phải có) +- orders.status (trạng thái đơn hàng) +- payments.status (trạng thái thanh toán) +- shipment.status (trạng thái giao hàng) +- shipment_group.status (trạng thái nhóm giao hàng nếu có) + +🔄 Quy tắc JOIN: +- LUÔN LUÔN sử dụng LEFT JOIN giữa các bảng: + - orders LEFT JOIN payments + - payments LEFT JOIN shipment + - shipment LEFT JOIN shipment_group +- Điều kiện lọc `is_deleted = FALSE` của các bảng phải được đưa vào phần ON của JOIN, không đặt trong WHERE để tránh loại bỏ các bản ghi chưa phát sinh quan hệ. + +📦 WHERE: +- Lọc theo `orders.user_id = ?` hoặc `orders.order_id = ?` +- Luôn kiểm tra `orders.is_deleted = FALSE` + +📋 Cần tránh: +- Không được dùng INNER JOIN trong trường hợp này vì có thể làm mất dữ liệu khi đơn hàng chưa có thanh toán hoặc shipment. +- Nếu status của bảng nào là NULL thì hiểu là chưa tạo hoặc chưa xử lý tới bước đó. + +✅ Output SQL phải rõ ràng, đúng cấu trúc, đủ 4 trạng thái, an toàn, và dễ hiểu. + + + +""" \ No newline at end of file diff --git a/function/prompt/prompt_create_table.py b/function/prompt/prompt_create_table.py new file mode 100644 index 0000000000000000000000000000000000000000..4d844217da8c9685dbdf67f88972fde41fd6a0b7 --- /dev/null +++ b/function/prompt/prompt_create_table.py @@ -0,0 +1,659 @@ +PROMPT_TABLE = """ +Danh sách bảng: ['absence_request', 'cart', 'cart_group', 'cart_item', 'cart_item_group', 'category', 'category_translation', 'contact', 'favourite', 'favourite_item', 'group_order_members', 'group_orders', 'map_directions', 'notification', 'order_item', 'orders', 'otp', 'payments', 'payments_group', 'post', 'post_translation', 'price_history', 'product', 'product_translation', 'product_variants', 'review', 'shipment', 'shipment_group', 'shipper_attendance', 'shipper_commission_detail', 'shipper_salary_summary', 'step_details', 'token', 'user', 'user_chat', 'user_coin', 'user_voucher', 'voucher'] + +--- CREATE TABLE `absence_request` --- +CREATE TABLE "absence_request" ( + "request_id" int NOT NULL AUTO_INCREMENT, + "end_date" datetime DEFAULT NULL, + "reason" text COLLATE utf8mb4_general_ci NOT NULL, + "start_date" datetime DEFAULT NULL, + "status" enum('APPROVED','REJECTED','WAITING') COLLATE utf8mb4_general_ci NOT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("request_id"), + KEY "FK7xuwoeagvnchyta5oous76oo1" ("user_id"), + CONSTRAINT "FK7xuwoeagvnchyta5oous76oo1" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `cart` --- +CREATE TABLE "cart" ( + "cart_id" int NOT NULL AUTO_INCREMENT, + "status" enum('COMPLETED','NEW','RESTORE','COMPLETED_PAUSE') COLLATE utf8mb4_general_ci DEFAULT NULL, + "total_price" double DEFAULT NULL, + "total_product" int DEFAULT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("cart_id"), + KEY "FKl70asp4l4w0jmbm1tqyofho4o" ("user_id"), + CONSTRAINT "FKl70asp4l4w0jmbm1tqyofho4o" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `cart_group` --- +CREATE TABLE "cart_group" ( + "cart_id" int NOT NULL AUTO_INCREMENT, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "total_price" double DEFAULT NULL, + "total_product" int DEFAULT NULL, + "member_id" int NOT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("cart_id"), + UNIQUE KEY "UKpq71nxpdtgr7m079b1whec06q" ("member_id"), + KEY "FKiubh7si2qpkohrgookkqb0lgu" ("user_id"), + CONSTRAINT "FKiubh7si2qpkohrgookkqb0lgu" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id"), + CONSTRAINT "FKrqau0ohjusewslefosxagi5bg" FOREIGN KEY ("member_id") REFERENCES "group_order_members" ("member_id") +) + +--- CREATE TABLE `cart_item` --- +CREATE TABLE "cart_item" ( + "cart_item_id" int NOT NULL AUTO_INCREMENT, + "quantity" int DEFAULT NULL, + "total_price" double DEFAULT NULL, + "cart_id" int NOT NULL, + "pro_id" int DEFAULT NULL, + "size" enum('L','M','S') COLLATE utf8mb4_general_ci DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "note" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + PRIMARY KEY ("cart_item_id"), + KEY "FK1uobyhgl1wvgt1jpccia8xxs3" ("cart_id"), + KEY "FKnn3h5nbk6wn36ndrt86ybfpy1" ("pro_id","size"), + CONSTRAINT "FK1uobyhgl1wvgt1jpccia8xxs3" FOREIGN KEY ("cart_id") REFERENCES "cart" ("cart_id"), + CONSTRAINT "FKnn3h5nbk6wn36ndrt86ybfpy1" FOREIGN KEY ("pro_id", "size") REFERENCES "product_variants" ("pro_id", "size") +) + +--- CREATE TABLE `cart_item_group` --- +CREATE TABLE "cart_item_group" ( + "cart_item_id" int NOT NULL AUTO_INCREMENT, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "item_price" double DEFAULT NULL, + "note" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "quantity" int DEFAULT NULL, + "total_price" double DEFAULT NULL, + "cart_id" int NOT NULL, + "pro_id" int DEFAULT NULL, + "size" enum('L','M','S') COLLATE utf8mb4_general_ci DEFAULT NULL, + PRIMARY KEY ("cart_item_id"), + KEY "FKqw9wot6jc4p5hwiwlhtb6jbog" ("cart_id"), + KEY "FKtfldbunlels28kg10rft89cv" ("pro_id","size"), + CONSTRAINT "FKqw9wot6jc4p5hwiwlhtb6jbog" FOREIGN KEY ("cart_id") REFERENCES "cart_group" ("cart_id"), + CONSTRAINT "FKtfldbunlels28kg10rft89cv" FOREIGN KEY ("pro_id", "size") REFERENCES "product_variants" ("pro_id", "size") +) + +--- CREATE TABLE `category` --- +CREATE TABLE "category" ( + "cate_id" int NOT NULL AUTO_INCREMENT, + "cate_img" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "cate_name" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + PRIMARY KEY ("cate_id") +) + +--- CREATE TABLE `category_translation` --- +CREATE TABLE "category_translation" ( + "cate_trans_id" int NOT NULL AUTO_INCREMENT, + "cate_name" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "language_code" enum('EN','VN') COLLATE utf8mb4_general_ci DEFAULT NULL, + "cate_id" int DEFAULT NULL, + PRIMARY KEY ("cate_trans_id"), + KEY "FKqfx54ldadyi0o390j5w0xgpe1" ("cate_id"), + CONSTRAINT "FKqfx54ldadyi0o390j5w0xgpe1" FOREIGN KEY ("cate_id") REFERENCES "category" ("cate_id") +) + +--- CREATE TABLE `contact` --- +CREATE TABLE "contact" ( + "contact_id" bigint NOT NULL AUTO_INCREMENT, + "create_date" datetime NOT NULL, + "date_deleted" datetime DEFAULT NULL, + "description" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "email" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "full_name" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "phone_number" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "status" enum('COMPLETED','WAITING') COLLATE utf8mb4_general_ci NOT NULL, + "date_updated" datetime DEFAULT NULL, + "user_id" int DEFAULT NULL, + PRIMARY KEY ("contact_id") +) + +--- CREATE TABLE `favourite` --- +CREATE TABLE "favourite" ( + "fav_id" int NOT NULL AUTO_INCREMENT, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("fav_id"), + UNIQUE KEY "UKtn0yetnb6bftwuygek6071cs4" ("user_id"), + CONSTRAINT "FK83lccer6s8bgj5jgjwan5eipk" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `favourite_item` --- +CREATE TABLE "favourite_item" ( + "fav_item_id" int NOT NULL AUTO_INCREMENT, + "fav_id" int NOT NULL, + "pro_id" int DEFAULT NULL, + "size" enum('L','M','S') COLLATE utf8mb4_general_ci DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + PRIMARY KEY ("fav_item_id"), + KEY "FKbug2wkmy00j351syuegu20fnr" ("fav_id"), + KEY "FK2xoaduosag75t69tclidxxgfq" ("pro_id","size"), + CONSTRAINT "FK2xoaduosag75t69tclidxxgfq" FOREIGN KEY ("pro_id", "size") REFERENCES "product_variants" ("pro_id", "size"), + CONSTRAINT "FKbug2wkmy00j351syuegu20fnr" FOREIGN KEY ("fav_id") REFERENCES "favourite" ("fav_id") +) + +--- CREATE TABLE `group_order_members` --- +CREATE TABLE "group_order_members" ( + "member_id" int NOT NULL AUTO_INCREMENT, + "amount" double DEFAULT NULL, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "is_leader" bit(1) NOT NULL, + "is_paid" bit(1) NOT NULL, + "note" text COLLATE utf8mb4_general_ci, + "quantity" int DEFAULT NULL, + "status" enum('CANCELED','CHECKOUT','COMPLETED','CREATED','SHOPPING') COLLATE utf8mb4_general_ci NOT NULL, + "type_payment" enum('CASH','MOMO','NONE','PAYOS','VNPAY','ZALO') COLLATE utf8mb4_general_ci NOT NULL, + "cart_id" int DEFAULT NULL, + "group_order_id" int NOT NULL, + "user_id" int NOT NULL, + "is_deleted_leader" bit(1) DEFAULT NULL, + PRIMARY KEY ("member_id"), + KEY "FKjebhpyrg58enl703ofk7fx57j" ("cart_id"), + KEY "FK3dkn2632hygabsmi5fldast6t" ("group_order_id"), + KEY "FK41od466att9w31qfd5k1lm85d" ("user_id"), + CONSTRAINT "FK3dkn2632hygabsmi5fldast6t" FOREIGN KEY ("group_order_id") REFERENCES "group_orders" ("group_order_id"), + CONSTRAINT "FK41od466att9w31qfd5k1lm85d" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id"), + CONSTRAINT "FKjebhpyrg58enl703ofk7fx57j" FOREIGN KEY ("cart_id") REFERENCES "cart_group" ("cart_id") +) + +--- CREATE TABLE `group_orders` --- +CREATE TABLE "group_orders" ( + "group_order_id" int NOT NULL AUTO_INCREMENT, + "address" text COLLATE utf8mb4_general_ci, + "code" varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "deadline_payment" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "is_flexible_payment" bit(1) DEFAULT NULL, + "link" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "name_group" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "note" text COLLATE utf8mb4_general_ci, + "order_date" datetime NOT NULL, + "status" enum('CANCELED','CHECKOUT','COMPLETED','CREATED','SHOPPING') COLLATE utf8mb4_general_ci NOT NULL, + "total_price" double NOT NULL, + "total_quantity" double NOT NULL, + "type_bill" enum('PAY_FOR_ALL','SPLIT_BILL_WITH_ALL') COLLATE utf8mb4_general_ci NOT NULL, + "type_payment" enum('CASH','MOMO','NONE','PAYOS','VNPAY','ZALO') COLLATE utf8mb4_general_ci NOT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("group_order_id"), + UNIQUE KEY "UK35u679oel6xd7ey4unhh5v9oq" ("code"), + UNIQUE KEY "UK401ngfww9iugp63r17wlbk3n" ("link"), + KEY "FKffxlnnw76ajjwyx14h1e4lb9g" ("user_id"), + CONSTRAINT "FKffxlnnw76ajjwyx14h1e4lb9g" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `map_directions` --- +CREATE TABLE "map_directions" ( + "map_direction_id" bigint NOT NULL AUTO_INCREMENT, + "created_at" datetime DEFAULT NULL, + "deleted_at" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "latitude_end" double DEFAULT NULL, + "latitude_start" double DEFAULT NULL, + "longitude_end" double DEFAULT NULL, + "longitude_start" double DEFAULT NULL, + "overview_polyline" text COLLATE utf8mb4_general_ci, + "shipment_id" int DEFAULT NULL, + "shipment_group_id" int DEFAULT NULL, + PRIMARY KEY ("map_direction_id"), + UNIQUE KEY "UKmu9kycqumup9hqbby5eodro2h" ("shipment_id"), + UNIQUE KEY "UKm7yn0emg8qoyg52u7hr23d0kp" ("shipment_group_id"), + CONSTRAINT "FKpbu0kubi0l2xxq1vwcgt897l6" FOREIGN KEY ("shipment_group_id") REFERENCES "shipment_group" ("shipment_id"), + CONSTRAINT "FKrkgexcp0f6a1rksne3tfsujut" FOREIGN KEY ("shipment_id") REFERENCES "shipment" ("shipment_id") +) + +--- CREATE TABLE `notification` --- +CREATE TABLE "notification" ( + "notifi_id" int NOT NULL AUTO_INCREMENT, + "is_read" bit(1) NOT NULL, + "message" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "shipment_id" int DEFAULT NULL, + "time" datetime(6) NOT NULL, + "user_id" int DEFAULT NULL, + "group_order_id" int DEFAULT NULL, + PRIMARY KEY ("notifi_id"), + KEY "FKb0yvoep4h4k92ipon31wmdf7e" ("user_id"), + CONSTRAINT "FKb0yvoep4h4k92ipon31wmdf7e" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `order_item` --- +CREATE TABLE "order_item" ( + "order_item_id" int NOT NULL AUTO_INCREMENT, + "date_created" datetime(6) DEFAULT NULL, + "date_deleted" datetime(6) DEFAULT NULL, + "date_updated" datetime(6) DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "quantity" int NOT NULL, + "total_price" double NOT NULL, + "cart_id" int DEFAULT NULL, + "order_id" int NOT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("order_item_id"), + UNIQUE KEY "UK5gjhq2fmknk50h8859nf0bcmx" ("order_id"), + KEY "FKkgu3wv2n7r2shg2wbvc4nsu7l" ("cart_id"), + KEY "FKt5mosdtftirppcdhv4wk963m" ("user_id"), + CONSTRAINT "FKkgu3wv2n7r2shg2wbvc4nsu7l" FOREIGN KEY ("cart_id") REFERENCES "cart" ("cart_id"), + CONSTRAINT "FKt4dc2r9nbvbujrljv3e23iibt" FOREIGN KEY ("order_id") REFERENCES "orders" ("order_id"), + CONSTRAINT "FKt5mosdtftirppcdhv4wk963m" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `orders` --- +CREATE TABLE "orders" ( + "order_id" int NOT NULL AUTO_INCREMENT, + "address" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "delivery_date" datetime DEFAULT NULL, + "delivery_fee" double DEFAULT NULL, + "discount_price" double NOT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "note" text COLLATE utf8mb4_general_ci, + "order_date" datetime NOT NULL, + "phone_number" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "status" enum('CANCELLED','CONFIRMED','WAITING') COLLATE utf8mb4_general_ci NOT NULL, + "total_price" double NOT NULL, + "user_id" int NOT NULL, + "voucher_id" bigint DEFAULT NULL, + "cancel_reason" enum('CHANGED_MY_MIND','DELIVERY_TOO_SLOW','FOUND_CHEAPER_ELSEWHERE','NOT_NEEDED_ANYMORE','ORDERED_BY_MISTAKE','OTHER_REASON','PAYMENT_ISSUES','PREFER_DIFFERENT_STORE','UNSATISFIED_WITH_SERVICE','WRONG_PRODUCT_SELECTED') COLLATE utf8mb4_general_ci DEFAULT NULL, + "date_canceled" datetime DEFAULT NULL, + "is_cancel_reason" bit(1) DEFAULT NULL, + "point_coin_use" float DEFAULT NULL, + PRIMARY KEY ("order_id"), + KEY "FKel9kyl84ego2otj2accfd8mr7" ("user_id"), + KEY "FKrx5vk9ur428660yp19hw98nr2" ("voucher_id"), + CONSTRAINT "FKel9kyl84ego2otj2accfd8mr7" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id"), + CONSTRAINT "FKrx5vk9ur428660yp19hw98nr2" FOREIGN KEY ("voucher_id") REFERENCES "voucher" ("voucher_id") +) + +--- CREATE TABLE `otp` --- +CREATE TABLE "otp" ( + "otp_id" int NOT NULL AUTO_INCREMENT, + "otp" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "status" bit(1) NOT NULL, + "time_otp" datetime NOT NULL, + "user_name" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "email" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + PRIMARY KEY ("otp_id"), + KEY "FK1fk48pp7wr309plc62bpw74jk" ("email"), + CONSTRAINT "FK1fk48pp7wr309plc62bpw74jk" FOREIGN KEY ("email") REFERENCES "user" ("email") +) + +--- CREATE TABLE `payments` --- +CREATE TABLE "payments" ( + "payment_id" int NOT NULL AUTO_INCREMENT, + "amount" double NOT NULL, + "date_created" datetime(6) DEFAULT NULL, + "date_deleted" datetime(6) DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "order_id_payment" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "payment_method" enum('CASH','CREDIT') COLLATE utf8mb4_general_ci NOT NULL, + "status" enum('COMPLETED','FAILED','PENDING','REFUND') COLLATE utf8mb4_general_ci NOT NULL, + "order_id" int NOT NULL, + "is_refunded" bit(1) DEFAULT NULL, + "date_refunded" datetime DEFAULT NULL, + "link" text COLLATE utf8mb4_general_ci, + PRIMARY KEY ("payment_id"), + UNIQUE KEY "UK8vo36cen604as7etdfwmyjsxt" ("order_id"), + CONSTRAINT "FK81gagumt0r8y3rmudcgpbk42l" FOREIGN KEY ("order_id") REFERENCES "orders" ("order_id") +) + +--- CREATE TABLE `payments_group` --- +CREATE TABLE "payments_group" ( + "payment_id" int NOT NULL AUTO_INCREMENT, + "amount" double NOT NULL, + "date_created" datetime(6) DEFAULT NULL, + "date_deleted" datetime(6) DEFAULT NULL, + "date_refunded" datetime DEFAULT NULL, + "discount_percent" double DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "is_refunded" bit(1) DEFAULT NULL, + "link" text COLLATE utf8mb4_general_ci, + "order_id_payment" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "payment_method" enum('CASH','CREDIT') COLLATE utf8mb4_general_ci NOT NULL, + "status" enum('COMPLETED','FAILED','PENDING','REFUND') COLLATE utf8mb4_general_ci NOT NULL, + "group_order_id" int NOT NULL, + PRIMARY KEY ("payment_id"), + KEY "fk_group_order_id" ("group_order_id"), + CONSTRAINT "fk_group_order_id" FOREIGN KEY ("group_order_id") REFERENCES "group_orders" ("group_order_id") +) + +--- CREATE TABLE `post` --- +CREATE TABLE "post" ( + "post_id" bigint NOT NULL AUTO_INCREMENT, + "banner_url" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "date_create" datetime(6) NOT NULL, + "date_deleted" datetime(6) DEFAULT NULL, + "description" text COLLATE utf8mb4_general_ci, + "is_deleted" bit(1) DEFAULT NULL, + "short_des" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "title" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "type" enum('DISCOUNT','EVENT','NEW') COLLATE utf8mb4_general_ci NOT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("post_id"), + KEY "FK72mt33dhhs48hf9gcqrq4fxte" ("user_id"), + CONSTRAINT "FK72mt33dhhs48hf9gcqrq4fxte" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `post_translation` --- +CREATE TABLE "post_translation" ( + "post_trans_id" bigint NOT NULL AUTO_INCREMENT, + "date_create" datetime(6) NOT NULL, + "date_deleted" datetime DEFAULT NULL, + "description" text COLLATE utf8mb4_general_ci, + "is_deleted" bit(1) DEFAULT NULL, + "language_code" enum('EN','VN') COLLATE utf8mb4_general_ci DEFAULT NULL, + "short_des" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "title" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "post_id" bigint DEFAULT NULL, + PRIMARY KEY ("post_trans_id"), + KEY "FK918cv1v5x5vs2uuescygypo58" ("post_id"), + CONSTRAINT "FK918cv1v5x5vs2uuescygypo58" FOREIGN KEY ("post_id") REFERENCES "post" ("post_id") +) + +--- CREATE TABLE `price_history` --- +CREATE TABLE "price_history" ( + "history_id" int NOT NULL AUTO_INCREMENT, + "change_reason" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "date_changed" datetime NOT NULL, + "new_price" double NOT NULL, + "old_price" double NOT NULL, + "var_id" int NOT NULL, + PRIMARY KEY ("history_id"), + KEY "FK1djlj52eh9jgdvcavm5dqc4b" ("var_id"), + CONSTRAINT "FK1djlj52eh9jgdvcavm5dqc4b" FOREIGN KEY ("var_id") REFERENCES "product_variants" ("var_id") +) + +--- CREATE TABLE `product` --- +CREATE TABLE "product" ( + "pro_id" int NOT NULL AUTO_INCREMENT, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "description" text COLLATE utf8mb4_general_ci NOT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "list_pro_img" text COLLATE utf8mb4_general_ci NOT NULL, + "pro_name" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "category_id" int DEFAULT NULL, + PRIMARY KEY ("pro_id"), + KEY "FK1mtsbur82frn64de7balymq9s" ("category_id"), + CONSTRAINT "FK1mtsbur82frn64de7balymq9s" FOREIGN KEY ("category_id") REFERENCES "category" ("cate_id") +) + +--- CREATE TABLE `product_translation` --- +CREATE TABLE "product_translation" ( + "pro_trans_id" int NOT NULL AUTO_INCREMENT, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "description" text COLLATE utf8mb4_general_ci NOT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "language_code" enum('EN','VN') COLLATE utf8mb4_general_ci DEFAULT NULL, + "pro_name" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "pro_id" int DEFAULT NULL, + PRIMARY KEY ("pro_trans_id"), + KEY "FKi91apo1hg2g0dpms5fhxwj0kf" ("pro_id"), + CONSTRAINT "FKi91apo1hg2g0dpms5fhxwj0kf" FOREIGN KEY ("pro_id") REFERENCES "product" ("pro_id") +) + +--- CREATE TABLE `product_variants` --- +CREATE TABLE "product_variants" ( + "var_id" int NOT NULL AUTO_INCREMENT, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "price" double NOT NULL, + "size" enum('L','M','S') COLLATE utf8mb4_general_ci NOT NULL, + "stock" int NOT NULL, + "pro_id" int NOT NULL, + PRIMARY KEY ("var_id"), + UNIQUE KEY "UKlnqyv1m6iiohs1w461p2vkjsy" ("pro_id","size"), + CONSTRAINT "FKjo9ykg9lsgirx0od3vt2jfpcb" FOREIGN KEY ("pro_id") REFERENCES "product" ("pro_id") +) + +--- CREATE TABLE `review` --- +CREATE TABLE "review" ( + "review_id" int NOT NULL AUTO_INCREMENT, + "content" text COLLATE utf8mb4_general_ci, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "rating_star" int DEFAULT NULL, + "pro_id" int NOT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("review_id"), + KEY "FKpxif5c2yg8322fbotvs3aeiki" ("pro_id"), + KEY "FKiyf57dy48lyiftdrf7y87rnxi" ("user_id"), + CONSTRAINT "FKiyf57dy48lyiftdrf7y87rnxi" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id"), + CONSTRAINT "FKpxif5c2yg8322fbotvs3aeiki" FOREIGN KEY ("pro_id") REFERENCES "product" ("pro_id") +) + +--- CREATE TABLE `shipment` --- +CREATE TABLE "shipment" ( + "shipment_id" int NOT NULL AUTO_INCREMENT, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_delivered" datetime DEFAULT NULL, + "date_shipped" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "status" enum('CANCELLED','SHIPPING','SUCCESS','WAITING') COLLATE utf8mb4_general_ci NOT NULL, + "payment_id" int NOT NULL, + "user_id" int DEFAULT NULL, + "date_canceled" datetime DEFAULT NULL, + "distance" double DEFAULT NULL, + "note" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + PRIMARY KEY ("shipment_id"), + UNIQUE KEY "UKaa2ydyemd8m3wsp81qks4903e" ("payment_id"), + KEY "FKsh7hhx6dbueu6ilnf92s9jub9" ("user_id"), + CONSTRAINT "FK2g2rx292u6cuk4t4glphs1sro" FOREIGN KEY ("payment_id") REFERENCES "payments" ("payment_id"), + CONSTRAINT "FKsh7hhx6dbueu6ilnf92s9jub9" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `shipment_group` --- +CREATE TABLE "shipment_group" ( + "shipment_id" int NOT NULL AUTO_INCREMENT, + "date_canceled" datetime DEFAULT NULL, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_delivered" datetime DEFAULT NULL, + "date_shipped" datetime DEFAULT NULL, + "distance" double DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "note" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "status" enum('CANCELLED','SHIPPING','SUCCESS','WAITING') COLLATE utf8mb4_general_ci NOT NULL, + "payment_id" int NOT NULL, + "user_id" int DEFAULT NULL, + PRIMARY KEY ("shipment_id"), + UNIQUE KEY "UKi1cv59irp0fyp9bjwguuatje8" ("payment_id"), + KEY "FK2ayjkl3cyuws0gh9bgmdfarh3" ("user_id"), + CONSTRAINT "FK2ayjkl3cyuws0gh9bgmdfarh3" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id"), + CONSTRAINT "FK3tvd7d0l5kw7lgpkgssadkgmr" FOREIGN KEY ("payment_id") REFERENCES "payments_group" ("payment_id") +) + +--- CREATE TABLE `shipper_attendance` --- +CREATE TABLE "shipper_attendance" ( + "id" int NOT NULL AUTO_INCREMENT, + "attendance_date" date NOT NULL, + "check_in_time" datetime(6) DEFAULT NULL, + "created_at" datetime(6) NOT NULL, + "is_present" bit(1) NOT NULL, + "note" text COLLATE utf8mb4_general_ci, + "status" enum('ABSENT','LATE','NONE','ON_LEAVE','ON_TIME') COLLATE utf8mb4_general_ci NOT NULL, + "updated_at" datetime(6) DEFAULT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("id"), + KEY "FKrf9fsm0mlbs22027shonc9vb8" ("user_id"), + CONSTRAINT "FKrf9fsm0mlbs22027shonc9vb8" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `shipper_commission_detail` --- +CREATE TABLE "shipper_commission_detail" ( + "id" int NOT NULL AUTO_INCREMENT, + "bonus" double DEFAULT NULL, + "commission_date" date NOT NULL, + "daily_commission" decimal(12,2) NOT NULL, + "note" text COLLATE utf8mb4_general_ci, + "order_count" int NOT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("id"), + KEY "FKomaoanegngrlnbcdda3bcjc4f" ("user_id"), + CONSTRAINT "FKomaoanegngrlnbcdda3bcjc4f" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `shipper_salary_summary` --- +CREATE TABLE "shipper_salary_summary" ( + "id" int NOT NULL AUTO_INCREMENT, + "approved_leave_days" int NOT NULL, + "base_salary" decimal(12,2) NOT NULL, + "commission" decimal(12,2) NOT NULL, + "created_at" datetime(6) NOT NULL, + "date_deleted" datetime DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "month" int NOT NULL, + "note" text COLLATE utf8mb4_general_ci, + "total_orders" int NOT NULL, + "total_salary" decimal(12,2) NOT NULL, + "updated_at" datetime(6) DEFAULT NULL, + "working_days" int NOT NULL, + "year" int NOT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("id"), + KEY "FKcb0of348deujp4irkpy2b32de" ("user_id"), + CONSTRAINT "FKcb0of348deujp4irkpy2b32de" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `step_details` --- +CREATE TABLE "step_details" ( + "step_id" bigint NOT NULL AUTO_INCREMENT, + "distance_text" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "duration_text" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "instruction" text COLLATE utf8mb4_general_ci, + "latitude" double DEFAULT NULL, + "longitude" double DEFAULT NULL, + "map_direction_id" bigint DEFAULT NULL, + PRIMARY KEY ("step_id"), + KEY "FKgjv6l6fmp2ple3e9jpdltbohe" ("map_direction_id"), + CONSTRAINT "FKgjv6l6fmp2ple3e9jpdltbohe" FOREIGN KEY ("map_direction_id") REFERENCES "map_directions" ("map_direction_id") +) + +--- CREATE TABLE `token` --- +CREATE TABLE "token" ( + "token_id" bigint NOT NULL AUTO_INCREMENT, + "access_token" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "expire" datetime(6) DEFAULT NULL, + "refresh_token" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "user_id" int NOT NULL, + PRIMARY KEY ("token_id"), + UNIQUE KEY "UKg7im3j7f0g31yhl6qco2iboy5" ("user_id"), + CONSTRAINT "FKe32ek7ixanakfqsdaokm4q9y2" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `user` --- +CREATE TABLE "user" ( + "user_id" int NOT NULL AUTO_INCREMENT, + "avatar" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "birth_date" datetime(6) DEFAULT NULL, + "city" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "district" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "email" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "full_name" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "password" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "phone_number" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "role" enum('ADMIN','CUSTOMER','SHIPPER') COLLATE utf8mb4_general_ci NOT NULL, + "sex" enum('FEMALE','MALE','OTHER') COLLATE utf8mb4_general_ci DEFAULT NULL, + "street" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "type" enum('BASIC','BOTH','EMAIL') COLLATE utf8mb4_general_ci NOT NULL, + "username" varchar(255) COLLATE utf8mb4_general_ci NOT NULL, + "ward" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + PRIMARY KEY ("user_id"), + UNIQUE KEY "unique_email" ("email") +) + +--- CREATE TABLE `user_chat` --- +CREATE TABLE "user_chat" ( + "user_chat_id" int NOT NULL AUTO_INCREMENT, + "chat_name" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "date_created" datetime DEFAULT NULL, + "date_deleted" datetime DEFAULT NULL, + "date_updated" datetime DEFAULT NULL, + "id_mongo_db" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "user_id" int DEFAULT NULL, + PRIMARY KEY ("user_chat_id"), + KEY "FKojd9hqbl3e7kq3vvr9ym218i4" ("user_id"), + CONSTRAINT "FKojd9hqbl3e7kq3vvr9ym218i4" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `user_coin` --- +CREATE TABLE "user_coin" ( + "user_coin_id" int NOT NULL AUTO_INCREMENT, + "point_coin" float DEFAULT NULL, + "user_id" int DEFAULT NULL, + PRIMARY KEY ("user_coin_id"), + KEY "FKn9afcbhayp9k1vl22n3xsu8p5" ("user_id"), + CONSTRAINT "FKn9afcbhayp9k1vl22n3xsu8p5" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `user_voucher` --- +CREATE TABLE "user_voucher" ( + "user_voucher_id" int NOT NULL AUTO_INCREMENT, + "status" enum('INACTIVE','USED','EXPIRED') COLLATE utf8mb4_general_ci DEFAULT NULL, + "user_id" int DEFAULT NULL, + "voucher_id" bigint DEFAULT NULL, + PRIMARY KEY ("user_voucher_id"), + KEY "FK98wtp768dsh1cjpuiqbnphb7a" ("user_id"), + KEY "FK5llb4x2ixiwa75csgei7hbl5r" ("voucher_id"), + CONSTRAINT "FK5llb4x2ixiwa75csgei7hbl5r" FOREIGN KEY ("voucher_id") REFERENCES "voucher" ("voucher_id"), + CONSTRAINT "FK98wtp768dsh1cjpuiqbnphb7a" FOREIGN KEY ("user_id") REFERENCES "user" ("user_id") +) + +--- CREATE TABLE `voucher` --- +CREATE TABLE "voucher" ( + "voucher_id" bigint NOT NULL AUTO_INCREMENT, + "date_deleted" datetime DEFAULT NULL, + "discount" double NOT NULL, + "end_date" datetime NOT NULL, + "is_deleted" bit(1) DEFAULT NULL, + "key_voucher" varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + "number" int NOT NULL, + "start_date" datetime NOT NULL, + "status" enum('ACTIVE','EXPIRED') COLLATE utf8mb4_general_ci NOT NULL, + "post_id" bigint NOT NULL, + PRIMARY KEY ("voucher_id"), + UNIQUE KEY "UK9bucqlrk4o5cl79uaekv5620c" ("post_id"), + CONSTRAINT "FKavteowywmy7sa540q5uvjf48l" FOREIGN KEY ("post_id") REFERENCES "post" ("post_id") +) +""" \ No newline at end of file diff --git a/function/prompt/prompt_custom.py b/function/prompt/prompt_custom.py new file mode 100644 index 0000000000000000000000000000000000000000..c64d0e1e313fa81e7696cecfab59f616ca1f6125 --- /dev/null +++ b/function/prompt/prompt_custom.py @@ -0,0 +1,286 @@ +# Thêm thư mục gốc vào sys.path +import os,sys +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from function.prompt import prompt_selling as prompt_selling +from langchain_core.prompts.prompt import PromptTemplate + + +PROMPT_SUFFIX = """Only use the following tables: +{table_info} + +Question: {input} +""" + + +_DEFAULT_TEMPLATE = """ +Instructions for Generating SQL Queries in Python (PyMySQL-Compatible) + +Purpose: +Given a user input question, generate a syntactically correct SQL query with strict role enforcement, deletion restrictions, and data integrity checks. + +==================================================== +Role-Based Security & Enforcement (Mandatory) +==================================================== +1. **Role Validation**: + - Before generating any SQL query, validate the user's role and permissions. + - If the user’s role is unauthorized for the requested operation: + → Return an error: "ERROR: Unauthorized operation for role." + → DO NOT generate or execute any SQL query. + +2. **Table-Specific Rule Enforcement**: + - If the requested operation violates specific table rules (e.g., attempt to delete restricted data): + → Return an error: "ERROR: Operation not permitted on this table." + → DO NOT generate or execute any SQL query. + +==================================================== +Deletion Restriction (No DELETE Allowed) +==================================================== +- Deletion operations are strictly prohibited. +- Instead of DELETE: + → Use UPDATE to set: + `isdeleted = TRUE`, + `deletion_date = CURRENT_TIMESTAMP` +- Restoration: + → UPDATE to set: + `isdeleted = FALSE`, + `deletion_date = NULL` + +==================================================== +Data Integrity Requirements +==================================================== +3. **Required Fields for INSERT/UPDATE**: + - Always include: + `creation_date`, `update_date`, `isdeleted` + +4. **Schema Adherence**: + - Use only explicitly provided column names from schema. + - Never assume or guess column names. + - Wrap all table and column names with backticks (`). + +5. **SELECT Queries**: + - Do NOT use `SELECT *`. + - Specify only the necessary columns. + +6. **Formatting & Readability**: + - Ensure clean, readable formatting for SQL queries. + +7. **Invalid Operation Handling**: + - If an operation is invalid or not allowed: + → Return: "ERROR: Invalid or unauthorized operation." + +8. **ID Columns**: + - Never show or include the following IDs in the output: + → product, category, post, orders, shipment, payment IDs. + +9. **Current Year**: + - Always use current year as 2025. + +==================================================== +Safe UPDATE Query Execution +==================================================== +10. **Avoid Subquery Errors**: + - Never use subqueries in the WHERE clause that return multiple rows. + - Store result in a variable first, then use it in the UPDATE WHERE clause. + + ✅ Example: + ```sql + -- WRONG: + UPDATE `product_translation` + SET `pro_name` = 'Pink guava leaf tea' + WHERE `pro_id` = (SELECT `pro_id` FROM `product` WHERE `pro_name` = 'Trà ổi lá hồng'); + + -- CORRECT: + SET @pro_id_var = (SELECT `pro_id` FROM `product` WHERE `pro_name` = 'Trà ổi lá hồng' LIMIT 1); + UPDATE `product_translation` + SET `pro_name` = 'Pink guava leaf tea' + WHERE `pro_id` = @pro_id_var; + ``` + +==================================================== +Category Duplication Prevention (CRITICAL) +==================================================== +⚠️ Before INSERT or UPDATE on `category` table: +- Prevent duplicate `cate_name` (translated to Vietnamese if needed). + +✅ For INSERT: + 1. If language is Vietnamese, check directly in `category`. + 2. If language is English: + → Translate `cate_name` to Vietnamese. + → Check for duplicate in `category`. + 3. If duplicate exists: + → Return error: "DUPLICATE_CATEGORY_NAME" + → DO NOT execute INSERT. + +✅ For UPDATE: + 1. Translate `cate_name` to Vietnamese if needed. + 2. Check if any record (excluding current) has the same `cate_name`. + 3. If duplicate exists: + → Return error: "DUPLICATE_CATEGORY_NAME" + → DO NOT execute UPDATE. + +🚨 Validation must occur within SQL execution (no pre-checks or separate queries allowed). + +==================================================== +Output Format for Each Query +==================================================== +Question: + +SQLQuery: + + +SQLResult: + + +Answer: + +""" + +from function.gemini_response import get_table +from function.prompt import personalization +import importlib +import os +import pkgutil +from function.prompt.table import users,review,user_voucher,user_coin,orders_orderitem,cart_cartItem,category_categoryTranslation,product,post_postTranslation,shipment,favourite,absence,payments_shipment_group,group_order_members,group_orders,cart_cartitem_group,shipper_attendance,shipper_commission_detail,shipper_salary_summary +from function.prompt import prompt_detail_table + + +async def return_table(query): + data = await get_table.response_general(query) + print(data) + text = "" + text_schema = "" + + grouped_data = {} + + table_mapping = { + "user": ["user"], + "absence": ["absence"], + "cart_group":["cart_group","cart_item_group"], + "group_order":["group_orders","payments_group"], + "group_order_member":["group_order_members"], + "user_voucher": ["user_voucher"], + "category": ["category", "category_translation"], + "cart": ["cart", "cart_item"], + "orders": ["orders", "order_item", "payment"], + "favourite": ["favourite", "favourite_item"], + "post": ["post", "post_translation"], + "product": ["product", "product_translation"], + "shipment": ["shipment"], + "shipment_group": ["shipment_group"], + "shipper_attendance":["shipper_attendance"], + "shipper_commission_detail" : ["shipper_commission_detail"], + "shipper_salary_summary": ["shipper_salary_summary"], + "review": ["review"], + "user_coin": ["user_coin"], + } + + content_mapping = { + "user": users.prompt_user, + "user_voucher": user_voucher.prompt_user_voucher, + "category": category_categoryTranslation.prompt_category_management, + "cart": cart_cartItem.prompt_cart_management, + "cart_group": cart_cartitem_group.prompt_cart_group_management , + "orders": orders_orderitem.prompt, + "favourite": favourite.prompt_favourite_management, + "post": post_postTranslation.prompt_post_management, + "product": product.prompt_product_management, + "shipment": shipment.prompt_shipment, + "review": review.prompt_review, + "user_coin": user_coin.prompt_user_coint, + "shipper_salary_summary": shipper_salary_summary.prompt_commission_management, + "shipper_commission_detail": shipper_commission_detail.prompt_commission_management, + "shipper_attendance": shipper_attendance.prompt_shipper_attendance_management, + "shipment_group": payments_shipment_group.prompt_shipment, + "group_order_member": group_order_members.prompt_shipper_attendance_management, + "group_order": group_orders.prompt, + "absence": absence.prompt_absence_management, + + + } + + # Mapping bảng -> hàm lấy schema riêng biệt + + schema_mapping = { + "user": prompt_detail_table.prompt_users, + "user_voucher": prompt_detail_table.prompt_user_voucher, + "category": prompt_detail_table.prompt_categort, + "category_translation": prompt_detail_table.prompt_category_translation, + "cart": prompt_detail_table.prompt_cart, + "cart_item": prompt_detail_table.prompt_cart_item, + "orders":prompt_detail_table.prompt_orders, + "order_item": prompt_detail_table.prompt_order_item, + "payment": prompt_detail_table.prompt_payments, + "favourite": prompt_detail_table.prompt_favourite, + "favourite_item": prompt_detail_table.prompt_fav_item, + "post": prompt_detail_table.prompt_post, + "post_translation": prompt_detail_table.prompt_post_translation, + "product": prompt_detail_table.prompt_product, + "product_translation": prompt_detail_table.prompt_product_translation, + "shipment": prompt_detail_table.prompt_shipment, + "product_variants": prompt_detail_table.prompt_product_variants, + "review": prompt_detail_table.prompt_review, + "user_coin": prompt_detail_table.prompt_user_coin, + "absence": prompt_detail_table.prompt_absence, + "cart_group": prompt_detail_table.prompt_cart_group, + "cart_item_group": prompt_detail_table.prompt_cartitem_group, + "group_orders": prompt_detail_table.prompt_group_orders, + "payments_group": prompt_detail_table.prompt_payments_group, + "group_order_members":prompt_detail_table.prompt_group_orders_member, + "shipment_group":prompt_detail_table.prompt_shipment_group, + "shipper_attendance":prompt_detail_table.prompt_shipper_attendance, + "shipper_commission_detail":prompt_detail_table.prompt_shipper_commission_detail, + "shipper_salary_summary":prompt_detail_table.prompt_shipper_salary_summary + } + + # Gom nội dung prompt theo nhóm + for value in data: + for key, tables in table_mapping.items(): + if value in tables: + grouped_data[key] = content_mapping[key] + break + + # Ghép nội dung prompt + for idx, (table_name, content) in enumerate(grouped_data.items(), start=1): + text += f"{idx}. {table_name}:\n{content}\n\n" + + # Lấy schema riêng cho từng bảng + for idx, table_name in enumerate(data, start=1): + schema_text = schema_mapping.get(table_name) + if schema_text: + text_schema += f"{idx}. Bảng: {table_name}\n{schema_text}\n\n" + else: + text_schema += f"{idx}. Bảng: {table_name}\nKhông tìm thấy schema.\n\n" + + return text, text_schema + + + +from function.prompt import general_rule +from function.prompt import prompt_table,personalization +async def get_prompt_custom(query): + text, text_schema = await return_table(query) + _ROLE_TEMPLATE = f""" + **Roles and their associated permissions are central to ensuring secure, organized, and efficient database operations. Pay special attention to role-based rules and table-specific restrictions when constructing SQL queries: + {text}. \n + **General Rule:** {general_rule.prompt_rule}. + ** Vui lòng xem thêm mô tả các bảng dưới đây để xác định chính xác câu lệnh SQL cần dùng và phải hợp lý: {text_schema}. + - Không dùng % hoặc format() để "nhét" trực tiếp biến vào câu SQL. + - **Tuyệt đối Cấm dùng "%%s" để lấy giá trị từ biến** + - ** Hãy luôn luôn tránh lỗi cú pháp SQL khi trả về cho mình.** + - "- Tránh các lỗi như :\n" + " (1054, \"Unknown column 'oi.pro_id' in 'field list'\")\n . Luôn đảm bảo bạn không bao giờ bị lỗi này" + " (1054, \"Unknown column 'oi.note' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này\n" + " (1054, \"Unknown column 'oi.size' in 'field list'\") . Luôn đảm bảo bạn không bao giờ bị lỗi này \n" + " (1054, \"Unknown column 'c.is_deleted' in 'on clause'\"). Luôn đảm bảo bạn không bao giờ bị lỗi này\n" +Tham khảo thêm prompt cá nhân hóa: + - {personalization.prompt} +Tuyệt đối không trả về câu SQL nằm trong [],list điều này bị cấm tuyệt đối +""" + + PROMPT = PromptTemplate( + input_variables=["input", "table_info", "dialect", "top_k", "role","language"], + template=_ROLE_TEMPLATE + _DEFAULT_TEMPLATE + PROMPT_SUFFIX +) + return PROMPT +import asyncio +# print(asyncio.run(return_table("Liệt kê các voucher mà người dùng có user_id là 5 đang sở hữu"))) \ No newline at end of file diff --git a/function/prompt/prompt_detail_table.py b/function/prompt/prompt_detail_table.py new file mode 100644 index 0000000000000000000000000000000000000000..65a48c246733fb238ac95f1d247538198612bde4 --- /dev/null +++ b/function/prompt/prompt_detail_table.py @@ -0,0 +1,554 @@ +prompt_voucher = """ +-voucher +Đây là bảng chứa các mã khuyến mãi của từng người dùng. ++ voucher_id: mã Id của bảng voucher. Kiểu Integer + + key_voucher: mã khóa định danh cho từng voucher riêng biệt. + + number: số lượng voucher hiện có. Kiểu Interger + + start_date: Ngày bắt đầu của mã khuyến mãi. Kiểu dateTime + + end_date: Ngày kết thúc của mã khuyến mãi. + + status: Trạng thái của mã khuyến mãi(Là giá trị Enum gồm có: ACTIVE, EXPIRED, ) + + is_deleted: Kiểm tra xem voucher có bị xóa hay không. Kiểu giá Boolean + + date_deleted: Ngày xóa voucher. + + discount: Giá trị của khuyến mãi. Kiểu Double + + post_id: bài viết mà voucher đó liên quan +- Các mối quan hệ: + + Khóa ngoại liên kết với bảng post. + +""" +prompt_user_voucher = """ +user-voucher: +Đây là bảng thể hiện mối liên hệ giữa người dùng và voucher mà họ sở hữu +-Gồm có các thuộc tính: + + user_voucher_id: Mã Id của bảng user_voucher. Kiểu Integer + + status: trạng thái của voucher mà người dùng sở hữu. Kiểu Enum gồm có: INACTIVE, USED + + user_id: mã Id của người dùng sở hữu voucher. Kiểu Integer + + voucher_id: mã voucher của cửa hàng mà người dùng đã thu thập. Kiểu Integer. +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng voucher + +""" + +prompt_cart = """ +-cart +Đây là bảng chứa các giỏ hàng thuộc về từng người dùng cụ thể. +-Gồm có các thuộc tính: ++ cart_id: mã Id của định danh cho giỏ hàng. Kiểu Integer ++ status: trạng thái của giỏ hàng (Là giá trị Enum gồm có: COMPLETED, NEW, RESTORE ++ total_price: tổng tiền các sản phẩm có trong giỏ hàng. Kiểu Double ++ total_product: tổng số lượng sản phẩm có trong giỏ hàng. Kiểu Integer ++ user_id: mã Id của người dùng đang sở hữu giỏ hàng. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user +""" +prompt_cart_item = """ +-cart_item +Đây là bảng chứa các sản phẩm thuộc nằm trong một giỏ hàng cụ thể của người dùng +-Gồm có các thuộc tính: + + cart_item_id: mã Id của sản phẩm ở trong giỏ hàng. Kiểu Integer + + quantity: số lượng của sản phẩm đó trong giỏ hàng. Kiểu Integer + + total_price: tổng tiền của sản phẩm. Kiểu Double + + cart_id: mã Id của giỏ hàng mà sản phẩm đang thuộc về. Kiểu Integer + + pro_id: mã Id của sản phẩm trong cửa hàng. Kiểu Integer + + size: kích cỡ của sản phẩm (Là giá trị Enum gồm có: S,M,L) + + date_deleted: ngày sản phẩm bị xóa khỏi giỏ hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem sản phẩm có bị xóa khỏi giỏ hàng hay không. Kiểu giá trị Boolean + + note: Ghi chú. Kiểu String +-Các mối liên hệ: + + cart_id: Khóa ngoại liên kết với bảng cart + + pro_Id: Khóa ngoại liên kết với bảng product + +""" + +prompt_categort = """ +-category +Đây là bảng chứa các danh mục đồ uống có trong cửa hàng. +-Gồm có các thuộc tính: + + cate_id: Mã Id của danh mục đồ uống. Kiểu Integer + + cate_img: url hình ảnh tượng trưng cho danh mục. Kiểu String + + cate_name: tên danh mục đồ uống. Kiểu String + + date_created: ngày danh mục được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày danh mục bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật danh mục. Kiểu dateTime + + is_deleted: Kiểm tra xem danh mục có bị xóa hay không. Kiểu giá trị Boolean +""" + +prompt_category_translation=""" +-category_translation +Đây là bảng chứa các danh mục đồ uống đã được translate qua tiếng Anh. +-Gồm có các thuộc tính: + + cate_trans_id: Mã Id của danh mục đồ uống đã được translate. Kiểu Integer + + cate_name: tên danh mục đã được translate. Kiểu String + + date_created: ngày danh mục được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày danh mục bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật danh mục. Kiểu dateTime + + is_deleted: Kiểm tra xem danh mục có bị xóa hay không. Kiểu giá trị Boolean + + language_code: ngôn ngữ dùng để translate. Kiểu Enum gồm có: EN, VN + + cate_id: mã Id của danh mục ở bảng danh mục chưa translate. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng category +""" + +prompt_contact = """ +-contact +Đây là bảng chứa các thư liên hệ mà người dùng gửi về cửa hàng +-Gồm có các thuộc tính: + + contact_id: mã Id của thư liên hệ. Kiểu Integer + + create_date: ngày người dùng gửi liên hệ. Kiểu dateTime + + date_deleted: ngày liên hệ bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật liên hệ. Kiểu dateTime + + is_deleted: Kiểm tra xem liên hệ có bị xóa hay không. Kiểu giá trị Boolean + + description: nội dung thư liên hệ. Kiểu String + + email: email của người dùng gửi liên hệ. Kiểu String + + full_name: tên của người dùng gửi liên hệ. Kiểu String + + phone_number: số điện thoại của người dùng gửi liên hệ. Kiểu String + + status: trạng thái của liên hệ. Kiểu Enum gồm có: COMPLETED, WAITING +""" + +prompt_favourite = """ +-favourite +Đây là bảng chứa danh sách yêu thích tương ứng với từng người dùng +-Gồm có các thuộc tính: + + fav_id: Mã Id của danh sách yêu thích + + date_created: ngày người dùng tạo danh sách yêu thích. Kiểu dateTime + + date_deleted: ngày danh sách yêu thích bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật danh sách yêu thích. Kiểu dateTime + + is_deleted: Kiểm tra xem danh sách yêu thích có bị xóa hay không. Kiểu giá trị Boolean + + user_id: mã Id của người dùng sở hữu danh sách yêu thích. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng user + +""" + +prompt_fav_item = """ +-favourite_item: +Đây là bảng chứa các sản phẩm yêu thích nằm trong danh sách yêu thích của ngưởi dùng. +-Gồm có các thuộc tính: + + fav_item_id: mã Id sản phẩm được yêu thích. Kiểu Integer + + fav_id: mã Id danh sách yêu thích tương ứng chứa sản phẩm được yêu thích. Kiểu Integer + + pro_id: mã Id của sản phẩm tương ứng với sản phẩm đã được yêu thích. Kiểu Integer + + size: kích cỡ của sản phẩm được yêu thích. Kiểu Enum gồm có:S,M,L + + date_deleted: ngày sản phẩm được yêu thích bị xóa. Kiểu dateTime + + is_deleted: Kiểm tra xem sản phẩm được yêu thích có bị xóa hay không. Kiểu giá trị Boolean +-Các mối liên hệ: ++ Khóa ngooại liên kết tới bảng favourite + + Khóa ngoại liên kết tới bảng product +""" + +prompt_orders = """ +-orders +Đây là bảng chứa các đơn hàng của người dùng +-Gồm có các thuộc tính: + + order_id: Mã Id của đơn hàng trong bảng orders. Kiểu Integer. + + address: địa chỉ người đặt. Kiểu String. + + date_created: ngày đơn hàng được tạo. Kiểu dateTime + + date_deleted: ngày đơn hàng bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật đơn hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + delivery_date: thời gian giao hàng dự kiến. Kiểu dateTime + + delivery_fee: phí vận chuyển. Kiểu Double + + discount_price: số tiền được giảm giá. Kiểu Double + + note: ghi chú của đơn hàng. Kiểu Text + + order_date: ngày đặt đơn. Kiểu dateTime + + phone_number: số điện thoại nhận hàng. Kiểu String + + status: trạng thái của đơn hàng. Kiểu Enum gồm có: CANCELLED, CONFIRMED , WAITING + + total_price: tổng tiền các sản phẩm có trong đơn hàng. Kiểu Double + + user_id: mã Id của người dùng đặt đơn hàng. Kiểu Integer. + + voucher_id: mã Id voucher được áp dụng cho đơn hàng. Kiểu Integer + + cancel_reason: lí do hủy đơn. Kiểu Enum gồm có CHANGED_MY_MIND, DELIVERY_TOO_SLOW, FOUND_CHEAPER_ELSEWHERE + + date_canceled: ngày hủy đơn. Kiểu dateTime + + is_cancel_reason: kiểm tra xem đơn hàng có bị hủy với lí do nào chưa. Kiểu Boolean. + + point_coin_use: số coin mà người dùng áp vào đơn hàng. Kiểu Float +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng voucher + +""" +prompt_order_item = """ +-order_item +Đây là bảng chứa các sản phẩm có trong đơn hàng của người dùng +-Gồm có các thuộc tính: + + order_item_id: mã Id của sản phẩm trong bảng orders có trong đơn hàng. Kiểu Integer + + date_created: ngày sản phẩm được thêm vào đơn hàng. Kiểu dateTime + + date_deleted: ngày sản phẩm bị xóa khỏi đơn hàng . Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong đơn hàng. Kiểu dateTime + + is_deleted: Kiểm tra sản phẩm trong đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + quantity: số lượng của sản phẩm trong đơn hàng. Kiểu Integer + + total_price: tổng tiền của sản phẩm. Kiểu Double + + cart_id: mã Id của giỏ hàng chứa các sản phẩm. Kiểu Integer + + order_id: mã Id của đơn hàng chứa sản phẩm. Kiểu Integer. + + user_id: mã Id của người dùng đặt đơn. Kiểu Integer. +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng cart + + Khóa ngoại liên kết với bảng orders + + Khóa ngoại liên kết với bảng user +""" + +prompt_payments = """ +-payments + Đây là bảng chứa các thanh toán cho các đơn hàng của người dùng. +-Gồm có các thuộc tính: + + payment_id: mã Id thanh toán của bảng payments. Kiểu Integer. + + amount: tổng tiền cần thanh toán cho đơn hàng. Kiểu Double + + date_created: ngày tạo thanh toán cho đơn hàng. Kiểu dateTime + + date_deleted: ngày xóa thanh toán đơn hàng . Kiểu dateTime + + is_deleted: Kiểm tra thanh toán của đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + order_id_payment: mã Id của thanh toán khi sử dụng các cổng thanh toán bên thứ ba. Kiểu String + + payment_method: phương thức thanh toán đã lựa chọn cho đơn hàng. Kiểu Enum gồm có: CASH, CREDIT + + status: trạng thái thanh toán đơn hàng. Kiểu Enum gồm có: COMPLETED, FAILED , PENDING , REFUND + + order_id: mã Id của đơn hàng đang thanh toán. Kiểu Integer + + is_refunded: kiểm tra xem đơn hàng đã được hoàn tiền chưa. Kiểu Boolean + + date_refunded: ngày hoàn tiền cho đơn hàng. Kiểu dateTime + + link: url trang thanh toán bên thứ ba. Kiểu Text +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng orders +""" + +prompt_post = """ +-post +Đây là bảng chứa các bài viết quảng cáo của cửa hàng +-Gồm có các thuộc tính: + + post_id: Mã Id của bài viết trong bảng post. Kiểu Integer + + banner_url: url đường dẫn hình ảnh của bài viết. Kiểu String + + date_create: ngày tạo bài viết. Kiểu dateTime + + date_deleted: ngày bài viết bị xóa. Kiểu dateTime + + description: nội dung bài viết. Kiểu Text + + is_deleted: Kiểm tra bài viết có bị xóa hay không. Kiểu giá trị Boolean + + short_des: Mô tả ngắn cho bài viết. Kiểu String + + title: tiêu đề của bài viết. Kiểu String + + type: loại bài viết. Kiểu Enum gồm có: DISCOUNT, EVENT, NEW + + user_id: mã Id người tạo bài viết. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user +""" + +prompt_post_translation = """ +-post_translation +Đây là bảng chứa các bài viết quảng cáo của cửa hàng đã được translate qua ngôn ngữ khác. +-Gồm có các thuộc tính: ++ post_trans_id: Mã Id của bài viết đã được translate trong bảng post_translation. Kiểu Integer ++ date_create: ngày tạo bài viết translate. Kiểu dateTime + + date_deleted: ngày bài viết translate bị xóa. Kiểu dateTime + + description: nội dung bài viết đã được translate . Kiểu Text + + is_deleted: Kiểm tra bài viết đã được translate có bị xóa hay không. Kiểu giá trị Boolean + + language_code: ngôn ngữ dùng để translate. Kiểu Enum gồm có: EN, VN + + short_des: Mô tả ngắn đã được translate cho bài viết. Kiểu String + + title: tiêu đề đã được translate của bài viết. Kiểu String + + post_id: Mã Id của bài viết khi chưa translate ở bảng post. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user +""" + +prompt_price_history = """ +-price_history +Đây là bảng chứa lịch sử thay đổi giá của sản phâmr trong cửa hàng. +-Gồm có các thuộc tính: + + history_id: mã Id của bảng price_history. Kiểu Integer + + chang_reason: lí do thay đổi giá. Kiểu String + + date_changed: ngày thay đổi giá. Kiểu dateTime + + new_price: giá mới sau khi đổi. Kiểu Double + + ole_price: giá cũ khi chưa đổi. Kiểu Double + + var_id: mã Id biến thể của sản phẩm đã được thay đổi giá. Kiểu Integer +-Các mối liện hệ: + + Khóa ngoại liên kết với bảng product_variants +""" + +prompt_product = """ +-product +Đây là bảng chứa các sản phẩm có trong cửa hàng +-Gồm có các thuộc tính: + + pro_id: mã Id của sản phẩm. Kiểu Integer + + date_created: ngày sản phẩm được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày sản phẩm bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong cửa hàng. Kiểu dateTime + + description: mô tả sản phẩm. Kiểu Text + + is_deleted: Kiểm tra sản phẩm trong cửa hàng có bị xóa hay không. Kiểu giá trị Boolean + + list_pro_img: danh sách url hình ảnh của sản phẩm. Kiểu Text + + pro_name: tên sản phẩm. Kiểu String + + category_id: mã Id của danh mục chứa sản phẩm. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng category +""" + +prompt_product_translation = """ +-product_translation +Đây là bảng chứa các sản phẩm có trong cửa hàng đã được translate +-Gồm có các thuộc tính: + + pro_trans_id: mã Id của sản phẩm đã được translate trong bảng product_translation. Kiểu Integer + + date_created: ngày sản phẩm được translate. Kiểu dateTime + + date_deleted: ngày sản phẩm được translate bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm được translate. Kiểu dateTime + + description: mô tả sản phẩm đã được translate. Kiểu Text + + is_deleted: Kiểm tra sản phẩm được translate có bị xóa hay không. Kiểu giá trị Boolean + + language_ code: ngôn ngữ dùng để translate. Kiểu Enum gồm có: EN, VN + + pro_name: tên sản phẩm được translate. Kiểu String + + pro_id: mã Id của sản phẩm trong bảng product. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng product +""" + +prompt_product_variants = """ +-product_variants +Đây là bảng chứa các biến thể của sản phẩm có trong cửa hàng +-Gồm có các thuộc tính: + + var_id: mã Id của biến thể. Kiểu Integer + + date_created: ngày biến thể của sản phẩm được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày biến thể của sản phẩm bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật biến thể của sản phẩm trong cửa hàng. Kiểu dateTime + + is_deleted: Kiểm tra biến thể của sản phẩm trong cửa hàng có bị xóa hay không. Kiểu giá trị Boolean + + price: giá biến thể của sản phẩm. Kiểu Double + + size: kích cỡ của biến thể sản phẩm. Kiểu Enum gồm có: S,M,L + + stock: số lượng tồn kho của biến thể sản phẩm. Kiểu Integer + + pro_id: mã Id của sản phẩm. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng product +""" + +prompt_review = """ +-review +Đây là bảng chứa các đánh giá về sản phẩm có trong cửa hàng +-Gồm có các thuộc tính: + + review_id: mã Id của đánh giá. Kiểu Integer + + content: nội dung đánh giá. Kiểu Text + + date_created: ngày tạo đánh giá. Kiểu dateTime + + date_deleted: ngày đánh giá bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật đánh giá. Kiểu dateTime + + is_deleted: Kiểm tra đánh giá có bị xóa hay không. Kiểu giá trị Boolean + + rating_star: số lượng sao đánh giá. Kiểu Integer + + pro_id: mã Id sản phẩm được đánh giá. Kiểu Integer + + user_id: mã Id của người dùng viết đánh giá sản phẩm. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng product + + Khóa ngoại liên kết với bảng user +""" + +prompt_shipment = """ +-shipment +Đây là bảng chứa các vận đơn +-Gồm có các thuộc tính: + + shipment_id: mã Id vận đơn. Kiểu Integer + + date_created: ngày tạo vận đơn. Kiểu dateTime + + date_deleted: ngày vận đơn bị xóa. Kiểu dateTime + + date_delivered: thời gian giao hàng dự kiến cho vận đơn. Kiểu dateTime + + date_shipped: thời gian giao hàng thành công. Kiểu dateTime + + date_canceled: thời gian hủy vận đơn. Kiểu dateTime + + is_deleted: Kiểm tra vận đơn có bị xóa hay không. Kiểu giá trị Boolean + + status: trạng thái vận đơn. Kiểu Enum gồm có: CANCELLED, SHIPPING, SUCCESS, WAITING + + payment_id: mã thanh toán của đơn hàng. Kiểu Integer + + user_id: mã Id của nhân viên giao hàng. Kiểu Integer + + distance: khoảng cách giao hàng. Kiểu Double + + note: ghi chú cho đơn hàng khi giao hàng. Kiểu String +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng payments + + Khóa ngoại liên kết với bảng user + +""" + +prompt_users = """ +-user +Đây là bảng chứa các thông tin người dùng trong cửa hàng +-Gồm có các thuộc tính: + + user_id: mã Id của sản phẩm. Kiểu Integer + + date_created: ngày đăng ký tài khoản. Kiểu dateTime + + date_deleted: ngày tài khoản bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật thông tin tài khoản. Kiểu dateTime + + avatar: url hình ảnh đại diện của tài khoản. Kiểu String + + is_deleted: Kiểm tra tài khoản người dùng có bị xóa hay không. Kiểu giá trị Boolean + + birthdate: ngày tháng năm sinh của người dùng. Kiểu dateTime + + city: địa chỉ thành phố của người dùng. Kiểu String + + district: địa chỉ quận/huyện của người dùng. Kiểu String + + email: địa chỉ email của người dùng. Kiểu String + + full_name: tên đầy đủ người dùng. Kiểu String + + password: mật khẩu tài khoản của người dùng. Kiểu String + + phone_number: số điện thoại của người dùng. Kiểu String + + role: vai trò của tài khoản. Kiểu Enum gồm có: ADMIN, CUSTOMER, SHIPPER + + sex: giới tính của người dùng. Kiểu Enum gồm có: FEMALE, MALE, OTHER + + street: địa chỉ đường của người dùng. Kiểu String + + type: loại đăng nhập của tài khoản, Kiểu Enum gồm có: BASIC, BOTH, EMAIL + + username: tên đăng nhập của tài khoản. Kiểu String + + ward: địa chỉ xã/phường của người dùng. Kiểu String +""" + +prompt_user_coin = """ +-user_coin +Đây là chứa các coin mà người dùng tích lũy được +-Gồm có các thuộc tính: + +_user_coin_id: mã Id của túi coin của người dùng. Kiểu Integer + + point_coin: số soin mà người dùng tích được. Kiểu Float + + user_id: mã Id của người dùng sở hữu coin. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user +""" + +prompt_absence = """Đây là bảng chứa các đơn xin nghỉ của shipper +- Gồm có các thuộc tính: ++ request_id: mã Id của bảng absence_request. Kiểu Integer ++ end_date: ngày kết thúc nghỉ phép. Kiểu dateTime ++ reason: lí do xin nghỉ. Kiểu Text ++ start_date: ngày bắt đầu nghỉ phép. Kiểu dateTime ++ status: trạng thái đơn xin nghỉ phép. Kiểu Enum gồm có APPROVED, REJECTED, WAITING ++ user_id: mã id của nhân viên xin nghỉ. Kiểu Integer +- Các mối quan hệ: + + Khóa ngoại liên kết với bảng user. + """ +prompt_shipper_salary_summary = """Đây là bảng tổng kết lương hàng tháng của shipper +-Gồm có các thuộc tính: + + id: mã tổng kết lương. Kiểu Integer + + approved_leave_days: số ngày nghỉ được duyệt. Kiểu Integer + + base_salary: lương cơ bản. Kiểu Decimal(12,2) + + commission: tổng hoa hồng. Kiểu Decimal(12,2) + + created_at: thời điểm tạo bản ghi. Kiểu DateTime(6) + + date_deleted: thời điểm xóa bản ghi. Kiểu DateTime + + is_deleted: cờ xác định đã xóa hay chưa. Kiểu Boolean + + month: tháng tính lương. Kiểu Integer + + note: ghi chú. Kiểu Text + + total_orders: tổng số đơn hàng. Kiểu Integer + + total_salary: tổng thu nhập. Kiểu Decimal(12,2) + + updated_at: thời điểm cập nhật. Kiểu DateTime(6) + + working_days: số ngày làm việc. Kiểu Integer + + year: năm tính lương. Kiểu Integer + + user_id: mã ID của shipper. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user +""" +prompt_shipper_commission_detail = """Đây là bảng chứa chi tiết hoa hồng hàng ngày của shipper +-Gồm có các thuộc tính: + + id: mã định danh hoa hồng. Kiểu Integer + + bonus: tiền thưởng thêm. Kiểu Double + + commission_date: ngày tính hoa hồng. Kiểu Date + + daily_commission: hoa hồng trong ngày. Kiểu Decimal(12,2) + + note: ghi chú. Kiểu Text + + order_count: số đơn hàng giao trong ngày. Kiểu Integer + + user_id: mã ID của shipper. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user +""" +prompt_shipper_attendance = """Đây là bảng quản lý chấm công của shipper +-Gồm có các thuộc tính: + + id: mã định danh chấm công. Kiểu Integer + + attendance_date: ngày chấm công. Kiểu Date + + check_in_time: thời gian check-in của shipper. Kiểu dateTime + + created_at: thời điểm chấm công. Kiểu dateTime + + is_present: kiểm tra có mặt hay không. Kiểu Boolean + + note: ghi chú liên quan đến chấm công. Kiểu Text + + status: trạng thái chấm công. Kiểu Enum gồm các giá trị: ABSENT, LATE, NONE, ON_LEAVE, ON_TIME + + updated_at: thời điểm cập nhật gần nhất. Kiểu DateTime(6) + + user_id: mã ID của shipper. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user +""" +prompt_shipment_group = """Đây là bảng chứa các vận đơn +-Gồm có các thuộc tính: + + shipment_id: mã Id vận đơn của đơn hàng nhóm. Kiểu Integer + + date_canceled: thời gian hủy vận đơn. Kiểu dateTime + + date_created: ngày tạo vận đơn. Kiểu dateTime + + date_deleted: ngày vận đơn bị xóa. Kiểu dateTime + + date_delivered: thời gian giao hàng dự kiến cho vận đơn. Kiểu dateTime + + date_shipped: thời gian giao hàng thành công. Kiểu dateTime + + distance: khoảng cách giao hàng. Kiểu Double + + is_deleted: Kiểm tra vận đơn có bị xóa hay không. Kiểu giá trị Boolean + + note: ghi chú cho đơn hàng khi giao hàng. Kiểu String + + status: trạng thái vận đơn. Kiểu Enum gồm có: CANCELLED, SHIPPING, SUCCESS, WAITING + + payment_id: mã thanh toán của đơn hàng nhóm. Kiểu Integer + + user_id: mã Id của nhân viên giao hàng. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng payments_group + + Khóa ngoại liên kết với bảng user +""" +prompt_payments_group = """Đây là bảng chứa các thanh toán cho các đơn hàng nhóm. +-Gồm có các thuộc tính: + + payment_id: mã Id thanh toán của bảng payments_group. Kiểu Integer. + + amount: tổng tiền cần thanh toán cho đơn hàng nhóm. Kiểu Double + + date_created: ngày tạo thanh toán cho đơn hàng nhóm. Kiểu dateTime + + date_deleted: ngày xóa thanh toán đơn hàng nhóm . Kiểu dateTime + + date_refunded: ngày hoàn tiền cho đơn hàng nhóm . Kiểu dateTime + + discount_percent: phần trăm giảm giá cho đơn hàng nhóm. Kiểu Double + + is_deleted: Kiểm tra thanh toán của đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + is_refunded: kiểm tra xem đơn hàng đã được hoàn tiền chưa. Kiểu Boolean + + link: url trang thanh toán bên thứ ba. Kiểu Text + + order_id_payment: mã Id của thanh toán khi sử dụng các cổng thanh toán bên thứ ba. Kiểu String + + payment_method: phương thức thanh toán đã lựa chọn cho đơn hàng. Kiểu Enum gồm có: CASH, CREDIT + + status: trạng thái thanh toán đơn hàng. Kiểu Enum gồm có: COMPLETED, FAILED , PENDING , REFUND + + group_order_id: mã Id của đơn hàng nhóm đang thanh toán. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng group_orders +""" +prompt_group_orders= """Đây là bảng chứa các thông tin về các nhóm đặt hàng +-Gồm có các thuộc tính: + + group_order_id: mã Id của nhóm đặt hàng. Kiểu Integer + + address: địa chỉ giao hàng cho đơn nhóm. Kiểu String + + code: mã tham giao vào đơn nhóm. Kiểu Integer ++ date_created: ngày tạo nhóm đặt hàng. Kiểu dateTime + + date_deleted: ngày xóa nhóm đặt hàng. Kiểu dateTime + + date_updated: ngày cập nhật thông tin trong nhóm đặt hàng. Kiểu dateTime + + deadline_payment: giới hạn thời gian tồn tại của nhóm đặt hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem nhóm có bị xóa hay không. Kiểu giá trị Boolean + + is_flexible_payment: Kiểm tra xem có thể thanh toán nhiều hình thức không. Kiểu Boolean + + link: đường dẫn để tham gia nhóm đặt hàng. Kiểu String ++ note: Ghi chú. Kiểu Text ++ order_date: ngày đặt hàng. Kiểu dateTime + + name_group: tên nhóm đặt hàng. Kiểu String + + status: trạng thái của nhóm đặt hàng. Kiểu Enum gồm có: CANCELED, CHECKOUT, COMPLETED, CREATED, SHOPPING + + total_price: tổng tiền của nhóm đặt hàng. Kiểu Double + + total_quantity: tổng số lượng sản phẩm có trong nhóm. Kiểu Integer + + type_bill: loại thanh toán cho nhóm đặt hàng. Kiểu Enum gồm có: PAY_FOR_ALL, SPLIT_BILL_WITH_ALL + + type_payment: hình thức thanh toán của nhóm đặt hàng. Kiểu Enum gồm có: CASH, MOMO, NONE, PAYOS, VNPAY, ZALO ++ user_id: mã Id của trưởng nhóm. Kiểu Integer +-Các mối liên hệ: ++ Khóa ngoại liên kết với bảng user +""" +prompt_group_orders_member = """Đây là bảng chứa các thành viên trong một nhóm đặt hàng +-Gồm có các thuộc tính: + + member_id: mã Id của thành viên ở trong nhóm. Kiểu Integer + + amount: tổng tiền sản phẩm thành viên đã thêm vào đơn nhóm. Kiểu Double + + quantity: số lượng của sản phẩm thành viên đã thêm vào đơn nhóm. Kiểu Integer + + status: trạng thái của nhóm đặt hàng. Kiểu Enum gồm có: CANCELED, CHECKOUT, COMPLETED, CREATED, SHOPPING + + type_payment: hình thức thanh toán của thành viên trong nhóm đặt hàng. Kiểu Enum gồm có: CASH, MOMO, NONE, PAYOS, VNPAY, ZALO + + cart_id: mã Id của giỏ hàng trong đơn nhóm mà sản phẩm đang thuộc về. Kiểu Integer + + group_order_id: mã Id của nhóm đặt hàng đang tham gia. Kiểu Integer + + user_id: mã Id của người dùng. Kiểu Integer + + date_created: ngày tham gia nhóm đặt hành. Kiểu dateTime + + date_deleted: ngày bị xóa khỏi nhóm đặt hàng. Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong nhóm đặt hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem thành viên trong nhóm có bị xóa hay không. Kiểu giá trị Boolean + + is_leader: Kiểm tra xem thành viên có phải trưởng nhóm không. Kiểu Boolean + + is_paid: kiểm tả xem thành viên đã thanh toán chưa. Kiểu Boolean + + note: Ghi chú. Kiểu Text +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng group_orders + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng cart_group +""" +prompt_cart_group = """Đây là bảng chứa các giỏ hàng phụ thuộc về từng người dùng cụ thể trong 1 đơn đặt han. +-Gồm có các thuộc tính: ++ cart_id: mã Id của định danh cho giỏ hàng. Kiểu Integer ++ total_price: tổng tiền các sản phẩm có trong giỏ hàng. Kiểu Double ++ total_product: tổng số lượng sản phẩm có trong giỏ hàng. Kiểu Integer ++ user_id: mã Id của người dùng đang là trưởng nhóm. Kiểu Integer ++ member_id: mã id của người dùng sở hữu giỏ hàng. Kiểu Integer ++ date_created: ngày tạo giỏ hàng. Kiểu dateTime + + date_deleted: ngày giỏ hàng bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật giỏ hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem giỏ hàng có bị xóa hay không. Kiểu giá trị Boolean +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user ++ Khóa ngoại liên kết với bảng group_order_members +""" +prompt_cartitem_group = """Đây là bảng chứa các sản phẩm thuộc nằm trong một giỏ hàng cụ thể của người dùng trong một đơn hàng nhóm +-Gồm có các thuộc tính: + + cart_item_id: mã Id của sản phẩm ở trong giỏ hàng. Kiểu Integer + + quantity: số lượng của sản phẩm đó trong giỏ hàng. Kiểu Integer + + total_price: tổng tiền của sản phẩm. Kiểu Double + + cart_id: mã Id của giỏ hàng trong đơn nhóm mà sản phẩm đang thuộc về. Kiểu Integer + + pro_id: mã Id của sản phẩm trong cửa hàng. Kiểu Integer + + size: kích cỡ của sản phẩm (Là giá trị Enum gồm có: S,M,L) + + date_created: ngày thêm sản phẩm vào giỏ hàng. Kiểu dateTime + + date_deleted: ngày sản phẩm trong giỏ hàng bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong giỏ hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem sản phẩm trong giỏ hàng có bị xóa hay không. Kiểu giá trị Boolean ++ item_price: giá của sản phẩm. Kiểu Double + + note: Ghi chú. Kiểu String +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng cart_group + + Khóa ngoại liên kết với bảng product_variant +""" diff --git a/function/prompt/prompt_main.py b/function/prompt/prompt_main.py new file mode 100644 index 0000000000000000000000000000000000000000..b6ba55e57763ecb2ee4d49796874d60649f49d97 --- /dev/null +++ b/function/prompt/prompt_main.py @@ -0,0 +1,280 @@ +import function.prompt.prompt_selling as prompt_selling +from langchain_core.prompts.prompt import PromptTemplate + + +PROMPT_SUFFIX = """Only use the following tables: +{table_info} + +Question: {input} +""" + +_DEFAULT_TEMPLATE = """Given an input question, follow these instructions to create a syntactically correct SQL query that will work with PyMySQL. Adhere strictly to the following rules and highlight the importance of roles in enforcing security and data integrity: +1. **Role-Based Enforcement:** Always validate the role and permissions of the user before performing any SQL operation. + - If the user's role does not permit the requested operation, immediately return an error and do not execute the SQL query. + - If the operation violates table-specific rules (e.g., attempting to delete records from a restricted table), return an error with a clear notification. + +2. **Deletion Restriction:** No `DELETE` operations are allowed for any role on any table. Instead: + - Use `UPDATE` statements to set `isdeleted = TRUE` and `deletion_date = CURRENT_TIMESTAMP` to mark a record as deleted. + - For restoration, update `isdeleted = FALSE` and clear `deletion_date`. +3. Generate a valid SQL query strictly adhering to the schema provided in the table information. +4. When adding or updating records, ensure all required fields (e.g., `creation_date`, `update_date`, `isdeleted`) are included. +5. Use only explicitly mentioned column names from the schema. Do not assume or guess any column names. +6. Use backticks (`) for all table and column names to ensure MySQL compatibility. +7. Avoid using `SELECT *`; specify only the necessary columns for clarity and efficiency. +8. Always ensure proper formatting and readability of the query. +9. Return an appropriate error if an invalid or unauthorized operation is attempted. +10. Please no show Id product, category, post, orders, shipment, payment. +11. Year present is 2025. +12. When performing an UPDATE operation in MySQL, always ensure that subqueries used in the WHERE clause return a single row. Failure to do so can cause "Error Code 1242: Subquery returns more than 1 row" or "Error Code 1093: You can't specify target table in FROM clause." +## ✅ Steps to Ensure Safe Updates: + +### **1. Store `pro_id` in a Variable Before Using It in `WHERE`** +Instead of writing: +```sql +UPDATE product_translation +SET pro_name = 'Pink guava leaf tea' +WHERE pro_id = (SELECT pro_id FROM product WHERE pro_name = 'Trà ổi lá hồng'); + +13. Important Reminder Prompt +"⚠️ IMPORTANT: Before executing an INSERT or UPDATE operation on the category table, ensure that no duplicate cate_name exists. + +For INSERT: If the language is Vietnamese, check directly in category. If the language is English, first translate cate_name to Vietnamese before checking. If a duplicate exists, return an error (DUPLICATE_CATEGORY_NAME) and stop execution immediately. +For UPDATE: Ensure that no other records (excluding the current one) have the same translated cate_name. If a duplicate exists, return an error (DUPLICATE_CATEGORY_NAME) and prevent the update. +This validation must be enforced within a single SQL execution, ensuring that if a duplicate is found, the operation is stopped immediately—no separate pre-checks should be performed. +### Example Usage: + +✅ **Checking Before Insert** +1. Attempt to insert "Beverages" (English). +2. Translate it to "Đồ uống" (Vietnamese). +3. If "Đồ uống" already exists, return "DUPLICATE_CATEGORY_NAME" and stop execution. +4. If not, insert "Đồ uống" into the category table. + +✅ **Checking Before Update** +1. Attempt to update "Beverages" (English) to a new category name. +2. Translate it to "Đồ uống" (Vietnamese). +3. Check if any other record (excluding the one being updated) has the same translated name. +4. If a duplicate is found, return "DUPLICATE_CATEGORY_NAME" and prevent the update. +🚨 This is a critical requirement and must not be skipped. Ensure strict enforcement to maintain data integrity." + +The following **roles** and their permissions dictate all SQL operations and play a **crucial role** in maintaining data security and integrity: +Structure your output in the following format: +Question: Question here +SQLQuery: SQL Query to run +SQLResult: Result of the SQLQuery +Answer: Final answer here + +""" + +_ROLE_TEMPLATE = f"""Roles and their associated permissions are central to ensuring secure, organized, and efficient database operations. Pay special attention to role-based rules and table-specific restrictions when constructing SQL queries: + +1. **cart:** + - **Admin Role:** Can create new carts for any user. + - **User/Shipper Roles:** Can edit existing carts with status 'NEW' and modify items within them. + - **Deletion:** No deletion allowed. Mark carts as deleted using `isdeleted` and `deletion_date`. + +2. **cart_item:** + - Default quantity is set to 1 if not specified. + - If the user's role does not permit the requested operation, immediately return an error and do not execute the SQL query. + - Check product variant stock before adding items. + - If a product variant is marked as `isdeleted`, do not add it and return an error. + - Users can only update items in carts they own. + - **Deletion:** No deletion allowed. Update `isdeleted` and `deletion_date`. + + +3. **category,category_translation:** + - **Admin Role:** Can create or update categories but cannot delete them. Attempting to delete must return an error. Khi thực hiện quyền xóa vui lòng xóa các sản phẩm(Vui lòng khôi phục các biến thể liên quan) liên quan đến category đó. Hoặc khi khôi phục thì khôi phục lại các sản phẩm liên quan(Vui lòng khôi phục các biến thể liên quan). If add record must translate cate name English and save category translation. + - - If the user's role does not permit the requested operation, immediately return an error and do not execute the SQL query. + - Important Reminder Prompt: + + "⚠️ IMPORTANT: Before executing an INSERT or UPDATE operation on the category table, ensure that no duplicate cate_name exists. + + + For INSERT: If the language is Vietnamese, check directly in category. If the language is English, first translate cate_name to Vietnamese before checking. If a duplicate exists, return an error (DUPLICATE_CATEGORY_NAME) and stop execution immediately. + + For UPDATE: Ensure that no other records (excluding the current one) have the same translated cate_name. If a duplicate exists, return an error (DUPLICATE_CATEGORY_NAME) and prevent the update. + + This validation must be enforced within a single SQL execution, ensuring that if a duplicate is found, the operation is stopped immediately—no separate pre-checks should be performed. + ### Example Usage: + - - ** Cách tiếp cận: + Chỉ ADMIN mới có quyền thêm (INSERT) hoặc cập nhật (UPDATE). + Nếu vi phạm, trả về lỗi "ERROR: Unauthorized modification of another user's information." trong câu check SQL. + +✅ **Checking Before Insert** +1. Attempt to insert "Beverages" (English). +2. Translate it to "Đồ uống" (Vietnamese). +3. If "Đồ uống" already exists, return "DUPLICATE_CATEGORY_NAME" and stop execution. +4. If not, insert "Đồ uống" into the category table. + +✅ **Checking Before Update** +1. Attempt to update "Beverages" (English) to a new category name. +2. Translate it to "Đồ uống" (Vietnamese). +3. Check if any other record (excluding the one being updated) has the same translated name. +4. If a duplicate is found, return "DUPLICATE_CATEGORY_NAME" and prevent the update. +🚨 This is a critical requirement and must not be skipped. Ensure strict enforcement to maintain data integrity." + - Insert: "When inserting a new category, follow this structured translation process: + + please convert cate_name any to vietnamese before save cate_name to table category + + If the current language is Vietnamese, insert the category with cate_name translated into Vietnamese. After insertion, create the category translation with cate_name translated into English. + + If the current language is English, insert the category with cate_name translated into Vietnamese. After insertion, create the category translation with cate_name translated back into English." + - Update: "When updating an existing category, ensure the correct record is updated and follow this structured translation process: + + If the current language is Vietnamese, update the category with cate_name translated into Vietnamese. After updating, update the category translation with cate_name translated into English. + + If the current language is English, update the category with cate_name translated into Vietnamese. After updating, update the category translation with cate_name translated back into English." + - **User/Shipper Roles:** Only view permissions. Attempting to create, update, or delete must return an error. +4. **contact:** + - All users can add contact information with a default status of 'waiting'. + - **Deletion:** Not allowed. Use `isdeleted` and `deletion_date` instead. + +5. **favourite:** + - Users can create a favourite list if it does not already exist for their user ID. + - Deleting a favourite list also deletes all associated favourite items logically (set `isdeleted` and `deletion_date`). + +6. **favourite_item:** + - Users/Admin/Shipper can add items to their favourites unless the product ID and size already exist. + - Users/Admin/Shipper can remove items from their favourites but only for their own records. + - **Deletion:** Logical deletion only (`isdeleted` and `deletion_date`). + + +7. **OrderItem, Orders, Payment: + - User/Shipper Roles: No insert, update,modify or delete operations. Any request insert, update,modify or delete must return an immediate error with a clear message (e.g., "ERROR: User does not have permission to perform this action"). + - No insert, update, or delete operations are allowed for any role except role Admin. Attempting such operations must return an error. + - Select and join queries are permitted. + +8. **OTP:** + - No SQL operations are allowed for this table. + +9. **post,post_translation:** + - **User/Shipper Roles:** Can only view posts. + - **Admin Role:** Can create or update posts,post_translation but cannot delete them. + - - ** Cách tiếp cận: + Chỉ ADMIN mới có quyền thêm (INSERT) hoặc cập nhật (UPDATE). + Nếu vi phạm, trả về lỗi "ERROR: Unauthorized modification of another user's information." trong câu check SQL. + +10. **product, product_variants,product_translation:** + - **Admin Role:** Can add or update records. Price updates must be logged in the price history.If add record must translate field name product, descrip to English and save product translation. Records cannot be deleted. + - - If the user's role does not permit the requested operation, immediately return an error and do not execute the SQL query. + - Insert:"When inserting a new product, follow this structured translation process: + + please convert pro_name any to vietnamese before save pro_name to table product + + Before inserting a new product, check if the pro_name already exists in the database. If the current language is Vietnamese, check directly in the product table.If the current language is English, first translate pro_name to Vietnamese and then check in the product table. + + If the current language is Vietnamese, insert the product with pro_name and description translated into Vietnamese. After insertion, create the product translation with pro_name and description translated into English. + + If the current language is English, insert the product with pro_name and description translated into Vietnamese. After insertion, create the product translation with pro_name and description translated back into English." + - Update: "When updating an existing product, ensure the correct record is updated and follow this structured translation process: + + Before updating an existing product, ensure that no other records (excluding the current one) have the same translated pro_name. If a duplicate is found, return a DUPLICATE_NAME warning and prevent the update. + + If the current language is Vietnamese, update the product with pro_name and description translated into Vietnamese. After updating, update the product translation with pro_name and description translated into English. + + If the current language is English, update the product with pro_name and description translated into Vietnamese. After updating, update the product translation with pro_name and description translated back into English." + - **User Roles:** Can view products and prices but cannot add or update records. + - - ** Cách tiếp cận: + Chỉ ADMIN mới có quyền thêm (INSERT) hoặc cập nhật (UPDATE). + Nếu vi phạm, trả về lỗi "ERROR: Unauthorized modification of another user's information." trong câu check SQL. + +11. **users:** + - **Admin Role:** Can view information of all users. + - Before processing the request, verify if the requested information matches the authenticated user's identity. If there is a mismatch, return an immediate error: 'ERROR: Unauthorized access to another user's information. + - **Deletion:** No deletion allowed. + - Người dùng có thể truy vấn thông tin bằng email hoặc số điện thoại. + Nếu vai trò là ADMIN, trả về thông tin của bất kỳ người dùng nào. + Nếu vai trò là USER hoặc SHIPPER, chỉ cho phép truy vấn thông tin của chính họ. + Nếu người dùng cố gắng truy vấn thông tin của người khác, hệ thống trả về lỗi: "ERROR: Unauthorized access to another user's information." + +12. **token:** + - No operations are allowed for this table. + +13. **voucher:** + - **Admin Role:** Has full permissions, including viewing, updating, and managing records for all users. + - **User/Shipper Roles:** Can view or manage records they own but cannot access or modify records belonging to other users. + - **Deletion:** Logical deletion only. + +14. **user_voucher:** + + - 14.1. **User/Shipper Roles:** + - - If a User/Shipper role attempts to access or query personal information such as name, phone, or email, the system must first check if these identifiers match the data of the requesting user. If no match is found, the user is not permitted to access or modify any personal information and an error must be returned immediately, such as: "ERROR: Unauthorized access to personal information." + - Users/Shippers can only view or manage records they own (i.e., vouchers explicitly associated with their `user_id`). + - They cannot view or access vouchers belonging to other users under any circumstances. + - **Validation for Indirect Identifiers:** + - If the query involves indirect user-identifying information (e.g., `phone`, `email`, `name`), the system must: + 1. Perform a preliminary query to validate ownership of the provided identifier: + - Example for email: `SELECT * FROM users WHERE email = provided_email AND user_id = current_user_id`. + - Example for phone: `SELECT * FROM users WHERE phone = provided_phone AND user_id = current_user_id`. + 2. If no match is found during validation, immediately return an error, without proceeding further: + ``` + "ERROR: User does not have permission to access this voucher information." + ``` + 3. If validation passes, construct the voucher query restricted to the user's records: + - Example: `SELECT * FROM user_voucher WHERE user_id = current_user_id`. + - Any unauthorized attempt must return an immediate error with a clear message: + `"ERROR: User does not have permission to perform this action."` + + - 14.2. **Admin Role:** + - Admin can view, access, and modify all records, including vouchers owned by other users. + - No restrictions are applied to the Admin role. + + - 14.3. **Deletion Policy:** + - Only logical deletion is allowed (e.g., marking a record as "deleted" rather than physically removing it). + - Users cannot view records marked as deleted (`isdeleted = TRUE` must be excluded in queries). + - Example for logical deletion: + - Mark as deleted: `UPDATE user_voucher SET isdeleted = TRUE, deletion_date = CURRENT_TIMESTAMP WHERE id = voucher_id`. + - Restore: `UPDATE user_voucher SET isdeleted = FALSE, deletion_date = NULL WHERE id = voucher_id`. + + +15. **Shipment:** + - **Shipper Role:** + - Shippers can update the status of shipments, but only under the following conditions: + 1. **Status Update to SUCCESS:** + - If the shipment status is updated to `SUCCESS`: + - Check the corresponding payment method for the shipment: + - If the payment method is `CASH`, update the payment id status to `COMPLETED`. + 2. **Status Update to CANCELLED:** + - If the shipment status is updated to `CANCELLED`: + - Update the `datecancel` field in the `Shipment` table with the current timestamp. + - Then, proceed with the following checks for the payment: + - If the payment method is `CASH`, update the payment status to `FAILED`. + - If the payment method is not `CASH`, update the payment status to `REFUND`. + - For both cases, update the corresponding `Order` table: + - Set the order status to `CANCELLED`. + - Update the `cancel_date` field in the `Order` table with the current timestamp. + - Any other roles (e.g., Admin, User) do not have permission to update shipment statuses. Attempting such actions must return an error. + - If the shipment status is already CANCELLED, SUCCESS, or SHIPPING, it cannot be changed back to any of these statuses: + + SUCCESS cannot revert to CANCELLED or SHIPPING. + + CANCELLED cannot revert to SUCCESS or SHIPPING. + - If a shipper attempts to change the status of a shipment they do not own, it should return a clear error: "ERROR: Unauthorized to update shipment status for this shipment." + - **General Rule for Shipment:** + - No `INSERT` or `DELETE` operations are allowed for any role. + - Only `SELECT` and `JOIN` queries are permitted for roles other than Shipper. + - Any unauthorized attempt to update shipment information must return a clear error, e.g., "ERROR: Role not authorized to update shipment status." +16. **General Rule:** + - Please ensure not show infromation id, is_deleted, data_deleted, update_date, create_date + - **"You are an expert in SQL specializing in product recommendations. Your task is to optimize SQL queries to efficiently retrieve relevant product information by joining appropriate tables. Please provide add size and price detail(add images) Given a user question, you must generate an optimized SQL query while ensuring correct syntax. Examples of user questions include: + + Which products are selling the best? + + What products currently have the best ratings? + + What are the trending products this month? + + Make sure the queries are optimized for performance and return results efficiently."** + - {prompt_selling.PROMPT_BEST_SELLING} + + Consider different types of queries, such as: Best-selling products. Highest-rated products,Trending products for the current month. +Your response should return a fully functional SQL query optimized for performance and correctness."** + - Roles are strictly enforced to ensure secure and reliable operations. + - Please ensure strict role management. + - When performing an UPDATE operation in MySQL, if the target table is also referenced in the FROM clause, avoid direct self-reference to prevent Error Code: 1093. Always follow these structured steps: + ** 1. **For UPDATE operations(With table product, category):** + + Use a **subquery with a derived table (temporary alias)** instead of directly referencing the same table. + + Example: Store the pro_id of the product to be updated in a variable + SET @pro_id_to_update = (SELECT pro_id FROM product WHERE pro_name = 'Trà ổi lá hồng'); + + - Alternatively, store the intermediate result in a **variable** before performing the update. + - No `DELETE` operations are allowed for any role on any table. Use logical deletion (`isdeleted` and `deletion_date`) instead. + - No DELETE operations are allowed for any role on any table. Instead, use logical deletion (isdeleted and deletion_date). When performing a restore, ensure that data is restored following the same logic. Specifically: + + When a category (category) is marked as deleted, all related products (product), product_translation and product variants (product_variants) must also be marked as deleted. + + When a product (product) is deleted, its related product variants,product translation (product_variants) must also be updated accordingly. + + When a product variant (product_variants) is deleted, related entries in the cart (cart_item) and favorite list (favourite_item) must be updated as well. + + When deleting a post, mark related vouchers as deleted and update the status of users who own those vouchers to USED. Additionally, update the corresponding records in the post_translation table. If the post is restored, reverse the process by reactivating the vouchers, restoring the users' voucher status, and updating the post_translation table accordingly. + + When restoring data, ensure that all related records are restored following the same logical approach. + - When calculating revenue, refer to the orders table but only include orders where the payment was successful and no refunds have been issued. + - Before processing the request, verify if the requested information matches the authenticated user's identity. If there is a mismatch, return an immediate error in syntax SQL: 'ERROR: Unauthorized access to another user's information. + - Users without the 'ADMIN' role cannot access, view or modify records of other users. + - Users without the 'Admin' role cannot add or modify or delete records in the following tables: categories, orders, shipments, products, voucher, and product_variants. Any unauthorized attempt must return an immediate error with a clear message (e.g., "ERROR: User does not have permission to perform this action"). + - When processing a question related to another person's information, such as through their ID, email, phone number, or full name, ensure that the SQL query first checks whether the person asking the question is authorized to access the requested information. Specifically: + + + If the query involves accessing another person's details, first execute a SQL command to verify that the requester is querying their own information or has appropriate permissions to view the target data. + + If the verification SQL command detects that the requester is attempting to access unauthorized information, the system must stop further execution and return an error indicating that access is not allowed. + + Only proceed with the original query if the initial authorization check confirms that the requester is allowed to access the requested data. + + Make sure the SQL query implements this flow and includes robust error handling to address potential issues or security violations. Provide the SQL query, clearly incorporating this authorization mechanism. +""" + +PROMPT = PromptTemplate( + input_variables=["input", "table_info", "dialect", "top_k", "role","language"], + template=_ROLE_TEMPLATE + _DEFAULT_TEMPLATE + PROMPT_SUFFIX +) diff --git a/function/prompt/prompt_selling.py b/function/prompt/prompt_selling.py new file mode 100644 index 0000000000000000000000000000000000000000..ce7178ca5b005c4e0150e7a933f6120fe8a98c16 --- /dev/null +++ b/function/prompt/prompt_selling.py @@ -0,0 +1,82 @@ +PROMPT_BEST_SELLING = """ +"You are an expert in SQL specializing in product recommendations and performance optimization. Your goal is to generate highly optimized SQL queries that efficiently retrieve product information, ensuring **accuracy, relevance, and fast execution**. + +🚀 **Key Considerations for Query Construction** 🚀 + +## 1️⃣ **Understanding Query Context to Choose the Right Data Source** +To generate an accurate SQL query, first determine the **correct data source** based on the user's question. The approach varies depending on **keywords and intent**: + +- **If the question relates to**: + 🔹 *"bán chạy"*, *"được mua nhiều"*, *"sản phẩm có nhiều người mua"*, *"bestseller"*, *"đã bán"* + ➜ **Use sales data** (`order`, `order_item`, `payment`) and ensure: + ✅ Orders are only counted if `order.status = 'confirmed'`. + ✅ Payments must be successfully processed (`payment.payment_status = 'successful'`). + ✅ Use `SUM(order_item.quantity)` to rank products by total units sold. + ✅ Optionally, calculate total revenue (`price × quantity sold`). + +- **If the question is about trending or highly rated products**: + 🔹 *"sản phẩm phổ biến"*, *"xu hướng"*, *"nhiều người quan tâm"*, *"được đánh giá cao"*, *"rating tốt"* + ➜ **Use recent purchase trends and reviews**: + ✅ Filter by recent `completed` orders to detect popularity. + ✅ Prioritize products with high `review.rating`. + ✅ Use `COUNT(DISTINCT order.user_id)` to measure unique buyers. + +- **If the question includes time-related words (e.g., 'today', 'hôm nay')**: + 🔹 *"hôm nay"*, *"ngày nay"*, *"gợi ý sản phẩm hôm nay"* + ➜ **Consider two possible responses**: + ✅ **New product arrivals**: Fetch from the `product` table where `created_at = CURRENT_DATE()`. + ✅ **Recently trending items**: Find products that had `completed` sales today. + ➜ Randomly pick one of the two approaches to diversify results. + +- **If the question asks for general product recommendations**: + 🔹 *"gợi ý sản phẩm"*, *"tôi nên mua gì"*, *"sản phẩm nào tốt"*, *"sản phẩm đáng mua"* + ➜ **Use a mix of logic** to generate a diverse recommendation list: + ✅ Sometimes prioritize **best-selling** products. + ✅ Sometimes prioritize **top-rated** products. + ✅ Sometimes recommend **low-stock items** (products with limited quantity left). + ✅ Sometimes suggest **new arrivals** (recently added products). + ✅ Randomize logic to ensure varied recommendations. + +--- + +## 2️⃣ **Table Relationships for Query Construction** +🔹 The `order_item` table contains `cart_id`, but does **not directly reference products**. +🔹 To retrieve product details: + ➜ **Step 1**: Join `order_item` with `cart` using `cart_id`. + ➜ **Step 2**: Join `cart` with `cart_item`, which contains the actual `product_id` (`proId`). + ➜ **Step 3**: Join `cart_item` with the `product` table to retrieve **product name, size, price, and image**. + +--- + +## 3️⃣ **Query Optimization Techniques** +⚡ **Ensure proper indexing** on frequently used columns (`cart_id`, `product_id`, `order_id`). +⚡ **Minimize unnecessary joins** while retrieving only relevant fields. +⚡ **Use GROUP BY and aggregate functions (`SUM`, `COUNT`)** for performance. +⚡ **Filter queries efficiently** using `WHERE` conditions (`WHERE created_at >= CURDATE() - INTERVAL 30 DAY` for trending). + +--- + +## 4️⃣ **Example SQL Queries to Generate** +✅ **Retrieve best-selling products** based on total quantity sold (only from completed and paid orders). +✅ **Fetch top-rated products** based on customer reviews. +✅ **Identify trending products for the current month (filter by recent completed orders).** +✅ **Get products with the highest revenue (`price × quantity sold`).** +✅ **Recommend products based on today’s trends or newly released items.** +✅ **Suggest diverse product lists based on sales, reviews, and stock availability.** + +--- + +## 🎯 **Query Output Requirements** +- **Must return:** `product_name`, `size`, `price`, `image_url`. +- **Ensure correct SQL syntax** to avoid execution errors. +- **Optimize for efficiency while maintaining accuracy.** + +✨ **Additional Logic:** +- If the user’s query contains **keywords related to sales**, ensure only valid orders are counted. +- If the query includes **'today'**, prioritize either **new arrivals** or **recently trending items**. +- The query should be optimized to return results **quickly and accurately**, maintaining correct SQL syntax." + + + + +""" \ No newline at end of file diff --git a/function/prompt/prompt_syntax_insert.py b/function/prompt/prompt_syntax_insert.py new file mode 100644 index 0000000000000000000000000000000000000000..b1f9c99418afc141dde51543e9155400a835c9fc --- /dev/null +++ b/function/prompt/prompt_syntax_insert.py @@ -0,0 +1,192 @@ +import os, sys +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) +sys.path.append(project_root) +from support import get_key +api_key = get_key.get_random_api_key() +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + + + + + +def is_insert_related_to_product_category_variant(user_question: str) -> bool: + import google.generativeai as genai + import time + from retrying import retry + + + # Lấy key và cấu hình + api_key = get_key.get_random_api_key() + genai.configure(api_key=api_key) + + generation_config = { + "temperature": 0.3, + "top_p": 0.9, + "top_k": 20, + "max_output_tokens": 1024, + "response_mime_type": "text/plain", + } + + model = genai.GenerativeModel( + model_name="gemini-2.0-flash-thinking-exp-01-21", + generation_config=generation_config, + ) + + # Prompt hệ thống + system_prompt = f""" +Bạn là một bộ phân loại ý định (intent classifier). + +Nhiệm vụ của bạn là kiểm tra xem **câu hỏi của người dùng có liên quan đến việc thêm mới (INSERT) các thực thể sau đây không**: +1. Thêm danh mục (category), cập nhật(chỉnh sửa) danh mục (update category) +2. Thêm sản phẩm (product), Cập nhật(chỉnh sửa) sản phẩm (update product) +3. Thêm biến thể sản phẩm (product variants), Cập nhật(chỉnh sửa) biến thể sản phẩm (update product variants) + +--- + +### Câu hỏi người dùng: +{user_question} + +--- + +### ✅ Kết quả mong muốn: +- Nếu câu hỏi có yêu cầu thêm danh mục, sản phẩm, hoặc biến thể sản phẩm → Trả về: `True` +- Nếu không liên quan đến hành động thêm → Trả về: `False` +- Tuyệt đối **không giải thích thêm**, chỉ trả về đúng `True` hoặc `False` +""" + + @retry(stop_max_attempt_number=3, wait_fixed=1000) # 3 lần, cách nhau 1s + def try_classify(): + chat_session = model.start_chat(history=[{"role": "user", "parts": [system_prompt]}]) + response = chat_session.send_message("Trả lời True hoặc False.") + + # Check nội dung phản hồi + if response.candidates and response.candidates[0].parts: + result = response.text.strip().lower() + if result.startswith("true"): + return True + elif result.startswith("false"): + return False + raise ValueError("Không có phản hồi hợp lệ từ Gemini") + + try: + return try_classify() + except Exception as e: + print(f"❌ Lỗi sau 3 lần thử: {e}") + return False + + + +def filter_syntax_sql(input, schema_sql, query): + import google.generativeai as genai + api_key = get_key.get_random_api_key() + genai.configure(api_key=api_key) + generation_config = { + "temperature": 0.5, + "top_p": 0.95, + "top_k": 40, + "max_output_tokens": 8192, + "response_mime_type": "text/plain", + } + + model = genai.GenerativeModel( + model_name="gemini-2.5-flash-lite-preview-06-17", + generation_config=generation_config, + ) + + chat_session = model.start_chat( + history=[ + { + "role": "user", + "parts": [ + f""" +Bạn là một chuyên gia SQL. Nhiệm vụ của bạn là **kiểm tra xem câu lệnh SQL dưới đây có hợp lệ và đáp ứng đúng yêu cầu hay không**, dựa trên: + +1. ✅ Câu hỏi truy vấn từ người dùng +2. ✅ Cấu trúc schema được cung cấp + +--- + +### 🧩 Câu hỏi từ người dùng: +{query} + +--- + +### 🧾 Câu lệnh SQL cần kiểm tra: +{input} + +--- + +### 🧱 Schema hệ thống (phải tuân thủ theo, không được phép bỏ qua): +{schema_sql} + +--- + +### 📌 Yêu cầu kiểm tra bắt buộc: + +1. **Câu SQL phải phù hợp với yêu cầu người dùng**. + - SQL trả về phải thể hiện đúng mục đích của câu hỏi (ví dụ thêm, sửa, truy vấn thông tin nào đó). +2. **Câu SQL phải đúng cú pháp MySQL**. Không có lỗi cú pháp, sai từ khóa, hay sai định dạng. +3. **SQL phải tuân thủ đúng schema**: + - Tên bảng, tên cột phải đúng như mô tả trong schema. + - Không truy vấn vào bảng không tồn tại. +4. **Đảm bảo tính đầy đủ nghiệp vụ**: + - Nếu INSERT vào `product`, thì **bắt buộc phải có** INSERT vào `product_translation`. + - Nếu INSERT vào `category`, thì **bắt buộc phải có** INSERT vào `category_translation`. +5. **Phòng chống dữ liệu trùng lặp**: + - Trước khi thêm `product`, phải kiểm tra `pro_name` đã tồn tại hay chưa. + - Trước khi thêm `category`, phải kiểm tra `cate_name` đã tồn tại hay chưa. +6. **Không được sử dụng các khối IF...THEN...ELSE** trong SQL — hãy dùng logic SQL đơn giản, rõ ràng. +7. Nếu câu SQL **trống**, hoặc không rõ ràng, hoặc có vấn đề cú pháp/logic, hãy trả về `False`. +8. Cú pháp Insert cho Product phải kèm theo danh mục hàng hóa mà họ muốn thêm vào. Không được thêm sản phẩm mà không có danh mục hàng hóa. +9. Khi Insert Product Variants thì phải kèm theo Product ID mà họ muốn thêm vào. Không được thêm biến thể sản phẩm mà không có ID sản phẩm. Check kĩ càng liệu rằng việc thêm product_variants có hợp lệ hay không. Có kèm theo size của biến thể đó hay không. Cú pháp khi tạo cũng cần check xem liệu là biến thể cho sản phẩm đó với siz eddos có tồn tại hay không. +10. Khi cập nhật Category thì phải kiểm tra xem liệu là tên mà họ muốn thay đổi có tồn tại hay không.ArithmeticError +11. Khi cập nhật Product Variants phải kiểm tra xem liệu biến thể cập nhật có giống cũ hay không, hay tạo một biến thể khác ngoài ra phải check xem số lượng có lớn hơn 0 hay không. + +--- + +### 🎯 Kết quả mong đợi: +- Trả về **duy nhất** một từ: `True` nếu câu SQL hợp lệ và đúng như các yêu cầu trên. +- Trả về `False` nếu **bất kỳ yêu cầu nào ở trên không đạt**. + +**Không** được trả lời thêm bất kỳ thông tin nào khác ngoài `True` hoặc `False`. +## 🎓 Few-shot Examples: + +### ✅ Ví dụ 1: +**Câu hỏi**: Thêm sản phẩm "Trà xanh" vào danh mục "Trà truyền thống". Trả về True + +**SQL**: +```sql +SELECT @category_id := `cate_id` FROM `category` WHERE `cate_name` = 'Trà truyền thống' AND `is_deleted` = 0; +SELECT @existing_product_id := `pro_id` FROM `product` WHERE `pro_name` = 'Trà xanh' AND `is_deleted` = 0; +INSERT INTO `product` (`pro_name`, `description`, `category_id`, `date_created`, `date_updated`, `is_deleted`, `list_pro_img`) +SELECT 'Trà xanh', 'Trà xanh thơm mát', @category_id, NOW(), NOW(), 0, '' WHERE @existing_product_id IS NULL; +INSERT INTO `product_translation` (`pro_id`, `pro_name`, `description`, `language_code`, `date_created`, `date_updated`, `is_deleted`) +VALUES (LAST_INSERT_ID(), 'Green Tea', 'Cool green tea', 'EN', NOW(), NOW(), 0); + +### ✅ Ví dụ 2: +**Câu hỏi**: Thêm sản phẩm Trà sữa dâu vào danh mục Trà sữa. + +SQL: +SELECT `cate_id` FROM `category` WHERE `cate_name` = 'Trà sữa' AND `is_deleted` = 0; +Kết quả mong muốn: False +⛔ Lý do: Chỉ kiểm tra danh mục, chưa thực hiện thêm sản phẩm hoặc translation. + + +**Vui lòng chỉ trả về kết quả là True hoặc False, không thêm bất kỳ thông tin nào khác.** + +""" + ], + } + ] + ) + + response = chat_session.send_message("Câu SQL trên hợp lệ hay không?. **Vui lòng chỉ trả về kết quả là True hoặc False, không thêm bất kỳ thông tin nào khác.**") + result = response.text.strip().lower() + + if result.startswith("true"): + return True + elif result.startswith("false"): + return False + else: + return False \ No newline at end of file diff --git a/function/prompt/prompt_table.py b/function/prompt/prompt_table.py new file mode 100644 index 0000000000000000000000000000000000000000..8a3018db0b50ff11ba041cb4ab4eac4e2296e033 --- /dev/null +++ b/function/prompt/prompt_table.py @@ -0,0 +1,640 @@ +prompt_table = """ +-voucher +Đây là bảng chứa các mã khuyến mãi của từng người dùng. +- Gồm có các thuộc tính: + + voucher_id: mã Id của bảng voucher. Kiểu Integer + + key_voucher: mã khóa định danh cho từng voucher riêng biệt. + + number: số lượng voucher hiện có. Kiểu Interger + + start_date: Ngày bắt đầu của mã khuyến mãi. Kiểu dateTime + + end_date: Ngày kết thúc của mã khuyến mãi. + + status: Trạng thái của mã khuyến mãi(Là giá trị Enum gồm có: ACTIVE, EXPIRED, ) + + is_deleted: Kiểm tra xem voucher có bị xóa hay không. Kiểu giá Boolean + + date_deleted: Ngày xóa voucher. + + discount: Giá trị của khuyến mãi. Kiểu Double + + post_id: bài viết mà voucher đó liên quan +- Các mối quan hệ: + + Khóa ngoại liên kết với bảng orders. + + Khóa ngoại liên kết với bảng user_voucher. + +-user_voucher +Đây là bảng thể hiện mối liên hệ giữa người dùng và voucher mà họ sở hữu +-Gồm có các thuộc tính: + + user_voucher_id: Mã Id của bảng user_voucher. Kiểu Integer + + status: trạng thái của voucher mà người dùng sở hữu. Kiểu Enum gồm có: INACTIVE, USED + + user_id: mã Id của người dùng sở hữu voucher. Kiểu Integer + + voucher_id: mã voucher của cửa hàng mà người dùng đã thu thập. Kiểu Integer. +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng voucher + +-cart +Đây là bảng chứa các giỏ hàng thuộc về từng người dùng cụ thể. +-Gồm có các thuộc tính: ++ cart_id: mã Id của định danh cho giỏ hàng. Kiểu Integer ++ status: trạng thái của giỏ hàng (Là giá trị Enum gồm có: COMPLETED, NEW, RESTORE ++ total_price: tổng tiền các sản phẩm có trong giỏ hàng. Kiểu Double ++ total_product: tổng số lượng sản phẩm có trong giỏ hàng. Kiểu Integer ++ user_id: mã Id của người dùng đang sở hữu giỏ hàng. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +-cart_item +Đây là bảng chứa các sản phẩm thuộc nằm trong một giỏ hàng cụ thể của người dùng +-Gồm có các thuộc tính: + + cart_item_id: mã Id của sản phẩm ở trong giỏ hàng. Kiểu Integer + + quantity: số lượng của sản phẩm đó trong giỏ hàng. Kiểu Integer + + total_price: tổng tiền của sản phẩm. Kiểu Double + + cart_id: mã Id của giỏ hàng mà sản phẩm đang thuộc về. Kiểu Integer + + pro_id: mã Id của sản phẩm trong cửa hàng. Kiểu Integer + + size: kích cỡ của sản phẩm (Là giá trị Enum gồm có: S,M,L) + + date_deleted: ngày sản phẩm bị xóa khỏi giỏ hàng. Kiểu dateTime + + is_delete: Kiểm tra xem sản phẩm có bị xóa khỏi giỏ hàng hay không. Kiểu giá trị Boolean + + note: Ghi chú. Kiểu String +-Các mối liên hệ: + + cart_id: Khóa ngoại liên kết với bảng cart + + pro_Id: Khóa ngoại liên kết với bảng product + +-category +Đây là bảng chứa các danh mục đồ uống có trong cửa hàng. +-Gồm có các thuộc tính: + + cate_id: Mã Id của danh mục đồ uống. Kiểu Integer + + cate_img: url hình ảnh tượng trưng cho danh mục. Kiểu String + + cate_name: tên danh mục đồ uống. Kiểu String + + date_created: ngày danh mục được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày danh mục bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật danh mục. Kiểu dateTime + + is_deleted: Kiểm tra xem danh mục có bị xóa hay không. Kiểu giá trị Boolean + +-category_translation +Đây là bảng chứa các danh mục đồ uống đã được translate qua tiếng Anh. +-Gồm có các thuộc tính: + + cate_trans_id: Mã Id của danh mục đồ uống đã được translate. Kiểu Integer + + cate_name: tên danh mục đã được translate. Kiểu String + + date_created: ngày danh mục được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày danh mục bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật danh mục. Kiểu dateTime + + is_deleted: Kiểm tra xem danh mục có bị xóa hay không. Kiểu giá trị Boolean + + language_code: ngôn ngữ dùng để translate. Kiểu Enum gồm có: EN, VN + + cate_id: mã Id của danh mục ở bảng danh mục chưa translate. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng category + +-contact +Đây là bảng chứa các thư liên hệ mà người dùng gửi về cửa hàng +-Gồm có các thuộc tính: + + contact_id: mã Id của thư liên hệ. Kiểu Integer + + create_date: ngày người dùng gửi liên hệ. Kiểu dateTime + + date_deleted: ngày liên hệ bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật liên hệ. Kiểu dateTime + + is_deleted: Kiểm tra xem liên hệ có bị xóa hay không. Kiểu giá trị Boolean + + description: nội dung thư liên hệ. Kiểu String + + email: email của người dùng gửi liên hệ. Kiểu String + + full_name: tên của người dùng gửi liên hệ. Kiểu String + + phone_number: số điện thoại của người dùng gửi liên hệ. Kiểu String + + status: trạng thái của liên hệ. Kiểu Enum gồm có: COMPLETED, WAITING + +-favourite +Đây là bảng chứa danh sách yêu thích tương ứng với từng người dùng +-Gồm có các thuộc tính: + + fav_id: Mã Id của danh sách yêu thích + + date_created: ngày người dùng tạo danh sách yêu thích. Kiểu dateTime + + date_deleted: ngày danh sách yêu thích bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật danh sách yêu thích. Kiểu dateTime + + is_deleted: Kiểm tra xem danh sách yêu thích có bị xóa hay không. Kiểu giá trị Boolean + + user_id: mã Id của người dùng sở hữu danh sách yêu thích. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng user + +-favourite_item: +Đây là bảng chứa các sản phẩm yêu thích nằm trong danh sách yêu thích của ngưởi dùng. +-Gồm có các thuộc tính: + + fav_item_id: mã Id sản phẩm được yêu thích. Kiểu Integer + + fav_id: mã Id danh sách yêu thích tương ứng chứa sản phẩm được yêu thích. Kiểu Integer + + pro_id: mã Id của sản phẩm tương ứng với sản phẩm đã được yêu thích. Kiểu Integer + + size: kích cỡ của sản phẩm được yêu thích. Kiểu Enum gồm có:S,M,L + + date_deleted: ngày sản phẩm được yêu thích bị xóa. Kiểu dateTime + + is_deleted: Kiểm tra xem sản phẩm được yêu thích có bị xóa hay không. Kiểu giá trị Boolean +-Các mối liên hệ: ++ Khóa ngooại liên kết tới bảng favourite + + Khóa ngoại liên kết tới bảng product + +-map_directions +Đây là bảng chứa thông tin về tuyến đường giao hàng +-Gồm có các thuộc tính: + + map_direction_id: mã Id của bảng map_directions. Kiểu Long + + created_at: thời gian tạo thông tin tuyến đường. Kiểu dateTime ++ deleted_at: thời gian xóa thông tin tuyến đường. Kiểu dateTime ++ is_deleted: Kiểm tra xem thông tin tuyến đường có bị xóa hay không. Kiểu giá trị Boolean + + latitude_end: vĩ độ điểm đến. Kiểu Double. + + latitude_start: vĩ độ điểm đi. Kiểu Double. + + longitude_end: kinh độ điểm đến. Kiểu Double + + longitude_start: kinh độ điểm đi. Kiểu Double + + overview_polyline: dữ liệu mã hóa của tuyến đường. Kiểu Text + + shipment_id: mã Id của đơn hàng vận chuyển. Kiểu Integer +-Các mối liên hệ: ++ Khóa ngoại liên kết với bảng shipment + +-notification +Đây là bảng chứa thông báo mà người dùng nhận được +-Gồm có các thuộc tính: + + notifi_id: mã Id của thông báo. Kiểu Integer. + + is_read: kiểm tra xem thông báo đã được đọc hay chưa. Kiểu Boolean + + message: nội dung thông báo. Kiểu String + + shipment_id: mã Id của đơn hàng vận chuyển. Kiểu Integer + + time: thời gian tạo thông báo. Kiểu dateTime + + user_id: mã Id người dùng nhận thông báo. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng user +-orders +Đây là bảng chứa các đơn hàng của người dùng +-Gồm có các thuộc tính: + + order_id: Mã Id của đơn hàng trong bảng orders. Kiểu Integer. + + address: địa chỉ người đặt. Kiểu String. + + date_created: ngày đơn hàng được tạo. Kiểu dateTime + + date_deleted: ngày đơn hàng bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật đơn hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + delivery_date: thời gian giao hàng dự kiến. Kiểu dateTime + + delivery_fee: phí vận chuyển. Kiểu Double + + discount_price: số tiền được giảm giá. Kiểu Double + + note: ghi chú của đơn hàng. Kiểu Text + + order_date: ngày đặt đơn. Kiểu dateTime + + phone_number: số điện thoại nhận hàng. Kiểu String + + status: trạng thái của đơn hàng. Kiểu Enum gồm có: CANCELLED, CONFIRMED , WAITING + + total_price: tổng tiền các sản phẩm có trong đơn hàng. Kiểu Double + + user_id: mã Id của người dùng đặt đơn hàng. Kiểu Integer. + + voucher_id: mã Id voucher được áp dụng cho đơn hàng. Kiểu Integer + + cancel_reason: lí do hủy đơn. Kiểu Enum gồm có CHANGED_MY_MIND, DELIVERY_TOO_SLOW, FOUND_CHEAPER_ELSEWHERE + + date_canceled: ngày hủy đơn. Kiểu dateTime + + is_cancel_reason: kiểm tra xem đơn hàng có bị hủy với lí do nào chưa. Kiểu Boolean. + + point_coin_use: số coin mà người dùng áp vào đơn hàng. Kiểu Float +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng voucher + +-order_item +Đây là bảng chứa các sản phẩm có trong đơn hàng của người dùng +-Gồm có các thuộc tính: + + order_item_id: mã Id của sản phẩm trong bảng orders có trong đơn hàng. Kiểu Integer + + date_created: ngày sản phẩm được thêm vào đơn hàng. Kiểu dateTime + + date_deleted: ngày sản phẩm bị xóa khỏi đơn hàng . Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong đơn hàng. Kiểu dateTime + + is_deleted: Kiểm tra sản phẩm trong đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + quantity: số lượng của sản phẩm trong đơn hàng. Kiểu Integer + + total_price: tổng tiền của sản phẩm. Kiểu Double + + cart_id: mã Id của giỏ hàng chứa các sản phẩm. Kiểu Integer + + order_id: mã Id của đơn hàng chứa sản phẩm. Kiểu Integer. + + user_id: mã Id của người dùng đặt đơn. Kiểu Integer. +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng cart + + Khóa ngoại liên kết với bảng orders + + Khóa ngoại liên kết với bảng user + +-payments + Đây là bảng chứa các thanh toán cho các đơn hàng của người dùng. +-Gồm có các thuộc tính: + + payment_id: mã Id thanh toán của bảng payments. Kiểu Integer. + + amount: tổng tiền cần thanh toán cho đơn hàng. Kiểu Double + + date_created: ngày tạo thanh toán cho đơn hàng. Kiểu dateTime + + date_deleted: ngày xóa thanh toán đơn hàng . Kiểu dateTime + + is_deleted: Kiểm tra thanh toán của đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + order_id_payment: mã Id của thanh toán khi sử dụng các cổng thanh toán bên thứ ba. Kiểu String + + payment_method: phương thức thanh toán đã lựa chọn cho đơn hàng. Kiểu Enum gồm có: CASH, CREDIT + + status: trạng thái thanh toán đơn hàng. Kiểu Enum gồm có: COMPLETED, FAILED , PENDING , REFUND + + order_id: mã Id của đơn hàng đang thanh toán. Kiểu Integer + + is_refunded: kiểm tra xem đơn hàng đã được hoàn tiền chưa. Kiểu Boolean + + date_refunded: ngày hoàn tiền cho đơn hàng. Kiểu dateTime + + link: url trang thanh toán bên thứ ba. Kiểu Text +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng orders + +-post +Đây là bảng chứa các bài viết quảng cáo của cửa hàng +-Gồm có các thuộc tính: + + post_id: Mã Id của bài viết trong bảng post. Kiểu Integer + + banner_url: url đường dẫn hình ảnh của bài viết. Kiểu String + + date_create: ngày tạo bài viết. Kiểu dateTime + + date_deleted: ngày bài viết bị xóa. Kiểu dateTime + + description: nội dung bài viết. Kiểu Text + + is_deleted: Kiểm tra bài viết có bị xóa hay không. Kiểu giá trị Boolean + + short_des: Mô tả ngắn cho bài viết. Kiểu String + + title: tiêu đề của bài viết. Kiểu String + + type: loại bài viết. Kiểu Enum gồm có: DISCOUNT, EVENT, NEW + + user_id: mã Id người tạo bài viết. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +-post_translation +Đây là bảng chứa các bài viết quảng cáo của cửa hàng đã được translate qua ngôn ngữ khác. +-Gồm có các thuộc tính: ++ post_trans_id: Mã Id của bài viết đã được translate trong bảng post_translation. Kiểu Integer ++ date_create: ngày tạo bài viết translate. Kiểu dateTime + + date_deleted: ngày bài viết translate bị xóa. Kiểu dateTime + + description: nội dung bài viết đã được translate . Kiểu Text + + is_deleted: Kiểm tra bài viết đã được translate có bị xóa hay không. Kiểu giá trị Boolean + + language_code: ngôn ngữ dùng để translate. Kiểu Enum gồm có: EN, VN + + short_des: Mô tả ngắn đã được translate cho bài viết. Kiểu String + + title: tiêu đề đã được translate của bài viết. Kiểu String + + post_id: Mã Id của bài viết khi chưa translate ở bảng post. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +-price_history +Đây là bảng chứa lịch sử thay đổi giá của sản phâmr trong cửa hàng. +-Gồm có các thuộc tính: + + history_id: mã Id của bảng price_history. Kiểu Integer + + chang_reason: lí do thay đổi giá. Kiểu String + + date_changed: ngày thay đổi giá. Kiểu dateTime + + new_price: giá mới sau khi đổi. Kiểu Double + + ole_price: giá cũ khi chưa đổi. Kiểu Double + + var_id: mã Id biến thể của sản phẩm đã được thay đổi giá. Kiểu Integer +-Các mối liện hệ: + + Khóa ngoại liên kết với bảng product_variants + +-product +Đây là bảng chứa các sản phẩm có trong cửa hàng +-Gồm có các thuộc tính: + + pro_id: mã Id của sản phẩm. Kiểu Integer + + date_created: ngày sản phẩm được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày sản phẩm bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong cửa hàng. Kiểu dateTime + + description: mô tả sản phẩm. Kiểu Text + + is_deleted: Kiểm tra sản phẩm trong cửa hàng có bị xóa hay không. Kiểu giá trị Boolean + + list_pro_img: danh sách url hình ảnh của sản phẩm. Kiểu Text + + pro_name: tên sản phẩm. Kiểu String + + category_id: mã Id của danh mục chứa sản phẩm. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng category + +-product_translation +Đây là bảng chứa các sản phẩm có trong cửa hàng đã được translate +-Gồm có các thuộc tính: + + pro_trans_id: mã Id của sản phẩm đã được translate trong bảng product_translation. Kiểu Integer + + date_created: ngày sản phẩm được translate. Kiểu dateTime + + date_deleted: ngày sản phẩm được translate bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm được translate. Kiểu dateTime + + description: mô tả sản phẩm đã được translate. Kiểu Text + + is_deleted: Kiểm tra sản phẩm được translate có bị xóa hay không. Kiểu giá trị Boolean + + language_ code: ngôn ngữ dùng để translate. Kiểu Enum gồm có: EN, VN + + pro_name: tên sản phẩm được translate. Kiểu String + + pro_id: mã Id của sản phẩm trong bảng product. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng product + +-product_variants +Đây là bảng chứa các biến thể của sản phẩm có trong cửa hàng +-Gồm có các thuộc tính: + + var_id: mã Id của biến thể. Kiểu Integer + + date_created: ngày biến thể của sản phẩm được thêm vào cửa hàng. Kiểu dateTime + + date_deleted: ngày biến thể của sản phẩm bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật biến thể của sản phẩm trong cửa hàng. Kiểu dateTime + + is_deleted: Kiểm tra biến thể của sản phẩm trong cửa hàng có bị xóa hay không. Kiểu giá trị Boolean + + price: giá biến thể của sản phẩm. Kiểu Double + + size: kích cỡ của biến thể sản phẩm. Kiểu Enum gồm có: S,M,L + + stock: số lượng tồn kho của biến thể sản phẩm. Kiểu Integer + + pro_id: mã Id của sản phẩm. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng product + +-review +Đây là bảng chứa các đánh giá về sản phẩm có trong cửa hàng +-Gồm có các thuộc tính: + + review_id: mã Id của đánh giá. Kiểu Integer + + content: nội dung đánh giá. Kiểu Text + + date_created: ngày tạo đánh giá. Kiểu dateTime + + date_deleted: ngày đánh giá bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật đánh giá. Kiểu dateTime + + is_deleted: Kiểm tra đánh giá có bị xóa hay không. Kiểu giá trị Boolean + + rating_star: số lượng sao đánh giá. Kiểu Integer + + pro_id: mã Id sản phẩm được đánh giá. Kiểu Integer + + user_id: mã Id của người dùng viết đánh giá sản phẩm. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng product + + Khóa ngoại liên kết với bảng user + +-shipment +Đây là bảng chứa các vận đơn +-Gồm có các thuộc tính: + + shipment_id: mã Id vận đơn. Kiểu Integer + + date_created: ngày tạo vận đơn. Kiểu dateTime + + date_deleted: ngày vận đơn bị xóa. Kiểu dateTime + + date_delivered: thời gian giao hàng dự kiến cho vận đơn. Kiểu dateTime + + date_shipped: thời gian giao hàng thành công. Kiểu dateTime + + date_canceled: thời gian hủy vận đơn. Kiểu dateTime + + is_deleted: Kiểm tra vận đơn có bị xóa hay không. Kiểu giá trị Boolean + + status: trạng thái vận đơn. Kiểu Enum gồm có: CANCELLED, SHIPPING, SUCCESS, WAITING + + payment_id: mã thanh toán của đơn hàng. Kiểu Integer + + user_id: mã Id của nhân viên giao hàng. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng payments + + Khóa ngoại liên kết với bảng user + +-step_details +Đây là bảng chứa các mô tả chi tiết về tuyến đường giao hàng +-Gồm có các thuộc tính: + + step_id: mã Id của bước đi trong bảng step_details. Kiểu Long + + distance_text: độ dài của quãng đường cần đi trong bước này. Kiểu String + + duration_text: thời gian của quãng đường cần đi trong bước này. Kiểu String + + instruction: hướng dẫn của quãng đường cần đi trong bước này. Kiểu Text + + latitude: vĩ độ điểm. Kiểu Double. + + longitude: kinh độ điểm. Kiểu Double. + + map_direction_id: mã Id của bảng map_directions. Kiểu Long +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng map_directions + +-user +Đây là bảng chứa các thông tin người dùng trong cửa hàng +-Gồm có các thuộc tính: + + user_id: mã Id của sản phẩm. Kiểu Integer + + date_created: ngày đăng ký tài khoản. Kiểu dateTime + + date_deleted: ngày tài khoản bị xóa khỏi cửa hàng . Kiểu dateTime + + date_updated: ngày cập nhật thông tin tài khoản. Kiểu dateTime + + avatar: url hình ảnh đại diện của tài khoản. Kiểu String + + is_deleted: Kiểm tra tài khoản người dùng có bị xóa hay không. Kiểu giá trị Boolean + + birthdate: ngày tháng năm sinh của người dùng. Kiểu dateTime + + city: địa chỉ thành phố của người dùng. Kiểu String + + district: địa chỉ quận/huyện của người dùng. Kiểu String + + email: địa chỉ email của người dùng. Kiểu String + + full_name: tên đầy đủ người dùng. Kiểu String + + password: mật khẩu tài khoản của người dùng. Kiểu String + + phone_number: số điện thoại của người dùng. Kiểu String + + role: vai trò của tài khoản. Kiểu Enum gồm có: ADMIN, CUSTOMER, SHIPPER + + sex: giới tính của người dùng. Kiểu Enum gồm có: FEMALE, MALE, OTHER + + street: địa chỉ đường của người dùng. Kiểu String + + type: loại đăng nhập của tài khoản, Kiểu Enum gồm có: BASIC, BOTH, EMAIL + + username: tên đăng nhập của tài khoản. Kiểu String + + ward: địa chỉ xã/phường của người dùng. Kiểu String + +-user_chat +Đây là bảng chứa các đoạn chat của người dùng với chatbot của cửa hàng +-Gồm có các thuộc tính: + + user_chat_id: mã Id của đoạn chat. Kiểu Integer + + date_created: ngày tạo đoạn chat. Kiểu dateTime + + date_deleted: ngày đoạn chat bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật đoạn chat. Kiểu dateTime + + chat_name: tên đoạn chat. Kiểu String + + is_deleted: Kiểm tra đoạn chat có bị xóa hay không. Kiểu giá trị Boolean + + id_mongo_db: mã Id đoạn chat được lưu trên mogodb. Kiểu String + + user_id: mã Id của người dùng tạo đoạn chat. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +-user_coin +Đây là chứa các coin mà người dùng tích lũy được +-Gồm có các thuộc tính: + +_user_coin_id: mã Id của túi coin của người dùng. Kiểu Integer + + point_coin: số soin mà người dùng tích được. Kiểu Float + + user_id: mã Id của người dùng sở hữu coin. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +- absence_request +Đây là bảng chứa các đơn xin nghỉ của shipper +- Gồm có các thuộc tính: ++ request_id: mã Id của bảng absence_request. Kiểu Integer ++ end_date: ngày kết thúc nghỉ phép. Kiểu dateTime ++ reason: lí do xin nghỉ. Kiểu Text ++ start_date: ngày bắt đầu nghỉ phép. Kiểu dateTime ++ status: trạng thái đơn xin nghỉ phép. Kiểu Enum gồm có APPROVED, REJECTED, WAITING ++ user_id: mã id của nhân viên xin nghỉ. Kiểu Integer +- Các mối quan hệ: + + Khóa ngoại liên kết với bảng user. + +- cart_group +Đây là bảng chứa các giỏ hàng phụ thuộc về từng người dùng cụ thể trong 1 đơn đặt han. +-Gồm có các thuộc tính: ++ cart_id: mã Id của định danh cho giỏ hàng. Kiểu Integer ++ total_price: tổng tiền các sản phẩm có trong giỏ hàng. Kiểu Double ++ total_product: tổng số lượng sản phẩm có trong giỏ hàng. Kiểu Integer ++ user_id: mã Id của người dùng đang là trưởng nhóm. Kiểu Integer ++ member_id: mã id của người dùng sở hữu giỏ hàng. Kiểu Integer ++ date_created: ngày tạo giỏ hàng. Kiểu dateTime + + date_deleted: ngày giỏ hàng bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật giỏ hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem giỏ hàng có bị xóa hay không. Kiểu giá trị Boolean +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user ++ Khóa ngoại liên kết với bảng group_order_members + +- cart_item_group +Đây là bảng chứa các sản phẩm thuộc nằm trong một giỏ hàng cụ thể của người dùng trong một đơn hàng nhóm +-Gồm có các thuộc tính: + + cart_item_id: mã Id của sản phẩm ở trong giỏ hàng. Kiểu Integer + + quantity: số lượng của sản phẩm đó trong giỏ hàng. Kiểu Integer + + total_price: tổng tiền của sản phẩm. Kiểu Double + + cart_id: mã Id của giỏ hàng trong đơn nhóm mà sản phẩm đang thuộc về. Kiểu Integer + + pro_id: mã Id của sản phẩm trong cửa hàng. Kiểu Integer + + size: kích cỡ của sản phẩm (Là giá trị Enum gồm có: S,M,L) + + date_created: ngày thêm sản phẩm vào giỏ hàng. Kiểu dateTime + + date_deleted: ngày sản phẩm trong giỏ hàng bị xóa. Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong giỏ hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem sản phẩm trong giỏ hàng có bị xóa hay không. Kiểu giá trị Boolean ++ item_price: giá của sản phẩm. Kiểu Double + + note: Ghi chú. Kiểu String +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng cart_group + + Khóa ngoại liên kết với bảng product_variant + +- group_order_members +Đây là bảng chứa các thành viên trong một nhóm đặt hàng +-Gồm có các thuộc tính: + + member_id: mã Id của thành viên ở trong nhóm. Kiểu Integer + + amount: tổng tiền sản phẩm thành viên đã thêm vào đơn nhóm. Kiểu Double + + quantity: số lượng của sản phẩm thành viên đã thêm vào đơn nhóm. Kiểu Integer + + status: trạng thái của nhóm đặt hàng. Kiểu Enum gồm có: CANCELED, CHECKOUT, COMPLETED, CREATED, SHOPPING + + type_payment: hình thức thanh toán của thành viên trong nhóm đặt hàng. Kiểu Enum gồm có: CASH, MOMO, NONE, PAYOS, VNPAY, ZALO + + cart_id: mã Id của giỏ hàng trong đơn nhóm mà sản phẩm đang thuộc về. Kiểu Integer + + group_order_id: mã Id của nhóm đặt hàng đang tham gia. Kiểu Integer + + user_id: mã Id của người dùng. Kiểu Integer + + date_created: ngày tham gia nhóm đặt hành. Kiểu dateTime + + date_deleted: ngày bị xóa khỏi nhóm đặt hàng. Kiểu dateTime + + date_updated: ngày cập nhật sản phẩm trong nhóm đặt hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem thành viên trong nhóm có bị xóa hay không. Kiểu giá trị Boolean + + is_leader: Kiểm tra xem thành viên có phải trưởng nhóm không. Kiểu Boolean + + is_paid: kiểm tả xem thành viên đã thanh toán chưa. Kiểu Boolean + + note: Ghi chú. Kiểu Text +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng group_orders + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng cart_group + + +- group_orders +Đây là bảng chứa các thông tin về các nhóm đặt hàng +-Gồm có các thuộc tính: + + group_order_id: mã Id của nhóm đặt hàng. Kiểu Integer + + address: địa chỉ giao hàng cho đơn nhóm. Kiểu String + + code: mã tham giao vào đơn nhóm. Kiểu Integer ++ date_created: ngày tạo nhóm đặt hàng. Kiểu dateTime + + date_deleted: ngày xóa nhóm đặt hàng. Kiểu dateTime + + date_updated: ngày cập nhật thông tin trong nhóm đặt hàng. Kiểu dateTime + + deadline_payment: giới hạn thời gian tồn tại của nhóm đặt hàng. Kiểu dateTime + + is_deleted: Kiểm tra xem nhóm có bị xóa hay không. Kiểu giá trị Boolean + + is_flexible_payment: Kiểm tra xem có thể thanh toán nhiều hình thức không. Kiểu Boolean + + link: đường dẫn để tham gia nhóm đặt hàng. Kiểu String ++ note: Ghi chú. Kiểu Text ++ order_date: ngày đặt hàng. Kiểu dateTime + + name_group: tên nhóm đặt hàng. Kiểu String + + status: trạng thái của nhóm đặt hàng. Kiểu Enum gồm có: CANCELED, CHECKOUT, COMPLETED, CREATED, SHOPPING + + total_price: tổng tiền của nhóm đặt hàng. Kiểu Double + + total_quantity: tổng số lượng sản phẩm có trong nhóm. Kiểu Integer + + type_bill: loại thanh toán cho nhóm đặt hàng. Kiểu Enum gồm có: PAY_FOR_ALL, SPLIT_BILL_WITH_ALL + + type_payment: hình thức thanh toán của nhóm đặt hàng. Kiểu Enum gồm có: CASH, MOMO, NONE, PAYOS, VNPAY, ZALO ++ user_id: mã Id của trưởng nhóm. Kiểu Integer +-Các mối liên hệ: ++ Khóa ngoại liên kết với bảng user + + + + + + +- +- voucher +Đây là bảng chứa các mã khuyến mãi của từng người dùng. ++ voucher_id: mã Id của bảng voucher. Kiểu Integer + + key_voucher: mã khóa định danh cho từng voucher riêng biệt. + + number: số lượng voucher hiện có. Kiểu Interger + + start_date: Ngày bắt đầu của mã khuyến mãi. Kiểu dateTime + + end_date: Ngày kết thúc của mã khuyến mãi. + + status: Trạng thái của mã khuyến mãi(Là giá trị Enum gồm có: ACTIVE, EXPIRED, ) + + is_deleted: Kiểm tra xem voucher có bị xóa hay không. Kiểu giá Boolean + + date_deleted: Ngày xóa voucher. + + discount: Giá trị của khuyến mãi. Kiểu Double + + post_id: bài viết mà voucher đó liên quan +- Các mối quan hệ: + + Khóa ngoại liên kết với bảng post. + +- user_voucher +Đây là bảng thể hiện mối liên hệ giữa người dùng và voucher mà họ sở hữu +-Gồm có các thuộc tính: + + user_voucher_id: Mã Id của bảng user_voucher. Kiểu Integer + + status: trạng thái của voucher mà người dùng sở hữu. Kiểu Enum gồm có: INACTIVE, USED + + user_id: mã Id của người dùng sở hữu voucher. Kiểu Integer + + voucher_id: mã voucher của cửa hàng mà người dùng đã thu thập. Kiểu Integer. +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + + Khóa ngoại liên kết với bảng voucher + +- notification +Đây là bảng chứa thông báo mà người dùng nhận được +-Gồm có các thuộc tính: + + notifi_id: mã Id của thông báo. Kiểu Integer. + + is_read: kiểm tra xem thông báo đã được đọc hay chưa. Kiểu Boolean + + message: nội dung thông báo. Kiểu String + + shipment_id: mã Id của đơn hàng vận chuyển. Kiểu Integer + + group_order_id: mã Id của nhóm đặt hàng. Kiểu Integer + + time: thời gian tạo thông báo. Kiểu dateTime + + user_id: mã Id người dùng nhận thông báo. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết tới bảng user + +- payments_group + Đây là bảng chứa các thanh toán cho các đơn hàng nhóm. +-Gồm có các thuộc tính: + + payment_id: mã Id thanh toán của bảng payments_group. Kiểu Integer. + + amount: tổng tiền cần thanh toán cho đơn hàng nhóm. Kiểu Double + + date_created: ngày tạo thanh toán cho đơn hàng nhóm. Kiểu dateTime + + date_deleted: ngày xóa thanh toán đơn hàng nhóm . Kiểu dateTime + + date_refunded: ngày hoàn tiền cho đơn hàng nhóm . Kiểu dateTime + + discount_percent: phần trăm giảm giá cho đơn hàng nhóm. Kiểu Double + + is_deleted: Kiểm tra thanh toán của đơn hàng có bị xóa hay không. Kiểu giá trị Boolean + + is_refunded: kiểm tra xem đơn hàng đã được hoàn tiền chưa. Kiểu Boolean + + link: url trang thanh toán bên thứ ba. Kiểu Text + + order_id_payment: mã Id của thanh toán khi sử dụng các cổng thanh toán bên thứ ba. Kiểu String + + payment_method: phương thức thanh toán đã lựa chọn cho đơn hàng. Kiểu Enum gồm có: CASH, CREDIT + + status: trạng thái thanh toán đơn hàng. Kiểu Enum gồm có: COMPLETED, FAILED , PENDING , REFUND + + group_order_id: mã Id của đơn hàng nhóm đang thanh toán. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng group_orders thông qua group_order_id + + + +- shipment +Đây là bảng chứa các vận đơn +-Gồm có các thuộc tính: + + shipment_id: mã Id vận đơn. Kiểu Integer + + date_created: ngày tạo vận đơn. Kiểu dateTime + + date_deleted: ngày vận đơn bị xóa. Kiểu dateTime + + date_delivered: thời gian giao hàng dự kiến cho vận đơn. Kiểu dateTime + + date_shipped: thời gian giao hàng thành công. Kiểu dateTime + + date_canceled: thời gian hủy vận đơn. Kiểu dateTime + + is_deleted: Kiểm tra vận đơn có bị xóa hay không. Kiểu giá trị Boolean + + status: trạng thái vận đơn. Kiểu Enum gồm có: CANCELLED, SHIPPING, SUCCESS, WAITING + + payment_id: mã thanh toán của đơn hàng. Kiểu Integer + + user_id: mã Id của nhân viên giao hàng. Kiểu Integer + + distance: khoảng cách giao hàng. Kiểu Double + + note: ghi chú cho đơn hàng khi giao hàng. Kiểu String +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng payments thông qua payment_id + + Khóa ngoại liên kết với bảng user thông qua user_id + +- shipment_group +Đây là bảng chứa các vận đơn +-Gồm có các thuộc tính: + + shipment_id: mã Id vận đơn của đơn hàng nhóm. Kiểu Integer + + date_canceled: thời gian hủy vận đơn. Kiểu dateTime + + date_created: ngày tạo vận đơn. Kiểu dateTime + + date_deleted: ngày vận đơn bị xóa. Kiểu dateTime + + date_delivered: thời gian giao hàng dự kiến cho vận đơn. Kiểu dateTime + + date_shipped: thời gian giao hàng thành công. Kiểu dateTime + + distance: khoảng cách giao hàng. Kiểu Double + + is_deleted: Kiểm tra vận đơn có bị xóa hay không. Kiểu giá trị Boolean + + note: ghi chú cho đơn hàng khi giao hàng. Kiểu String + + status: trạng thái vận đơn. Kiểu Enum gồm có: CANCELLED, SHIPPING, SUCCESS, WAITING + + payment_id: mã thanh toán của đơn hàng nhóm. Kiểu Integer + + user_id: mã Id của nhân viên giao hàng. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng payments_group + + Khóa ngoại liên kết với bảng user thông qua user_id + +- shipper_attendance +Đây là bảng quản lý chấm công của shipper +-Gồm có các thuộc tính: + + id: mã định danh chấm công. Kiểu Integer + + attendance_date: ngày chấm công. Kiểu Date + + check_in_time: thời gian check-in của shipper. Kiểu dateTime + + created_at: thời điểm chấm công. Kiểu dateTime + + is_present: kiểm tra có mặt hay không. Kiểu Boolean + + note: ghi chú liên quan đến chấm công. Kiểu Text + + status: trạng thái chấm công. Kiểu Enum gồm các giá trị: ABSENT, LATE, NONE, ON_LEAVE, ON_TIME + + updated_at: thời điểm cập nhật gần nhất. Kiểu DateTime(6) + + user_id: mã ID của shipper. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user thông qua user_id + +- shipper_commission_detail +Đây là bảng chứa chi tiết hoa hồng hàng ngày của shipper +-Gồm có các thuộc tính: + + id: mã định danh hoa hồng. Kiểu Integer + + bonus: tiền thưởng thêm. Kiểu Double + + commission_date: ngày tính hoa hồng. Kiểu Date + + daily_commission: hoa hồng trong ngày. Kiểu Decimal(12,2) + + note: ghi chú. Kiểu Text + + order_count: số đơn hàng giao trong ngày. Kiểu Integer + + user_id: mã ID của shipper. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +- shipper_salary_summary +Đây là bảng tổng kết lương hàng tháng của shipper +-Gồm có các thuộc tính: + + id: mã tổng kết lương. Kiểu Integer + + approved_leave_days: số ngày nghỉ được duyệt. Kiểu Integer + + base_salary: lương cơ bản. Kiểu Decimal(12,2) + + commission: tổng hoa hồng. Kiểu Decimal(12,2) + + created_at: thời điểm tạo bản ghi. Kiểu DateTime(6) + + date_deleted: thời điểm xóa bản ghi. Kiểu DateTime + + is_deleted: cờ xác định đã xóa hay chưa. Kiểu Boolean + + month: tháng tính lương. Kiểu Integer + + note: ghi chú. Kiểu Text + + total_orders: tổng số đơn hàng. Kiểu Integer + + total_salary: tổng thu nhập. Kiểu Decimal(12,2) + + updated_at: thời điểm cập nhật. Kiểu DateTime(6) + + working_days: số ngày làm việc. Kiểu Integer + + year: năm tính lương. Kiểu Integer + + user_id: mã ID của shipper. Kiểu Integer +-Các mối liên hệ: + + Khóa ngoại liên kết với bảng user + +""" \ No newline at end of file diff --git a/function/prompt/table/__init__.py b/function/prompt/table/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/function/prompt/table/__pycache__/__init__.cpython-311.pyc b/function/prompt/table/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9ff4c33c56d4419f7f4791f32fa8892eb613dfd Binary files /dev/null and b/function/prompt/table/__pycache__/__init__.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/__init__.cpython-312.pyc b/function/prompt/table/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65d8fef646cacbeaa402dc962ec0936c07f28aef Binary files /dev/null and b/function/prompt/table/__pycache__/__init__.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/absence.cpython-311.pyc b/function/prompt/table/__pycache__/absence.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e46ce80edc1fe423283c6e2939a689ba5ce01c58 Binary files /dev/null and b/function/prompt/table/__pycache__/absence.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/cart_cartItem.cpython-311.pyc b/function/prompt/table/__pycache__/cart_cartItem.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe7d4f287bc0502e9afafa901f8ad5ca545a4bb8 Binary files /dev/null and b/function/prompt/table/__pycache__/cart_cartItem.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/cart_cartItem.cpython-312.pyc b/function/prompt/table/__pycache__/cart_cartItem.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9eff6ce18d04c90452f0cfd3e6032c45d2ad7180 Binary files /dev/null and b/function/prompt/table/__pycache__/cart_cartItem.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/cart_cartitem_group.cpython-311.pyc b/function/prompt/table/__pycache__/cart_cartitem_group.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccdcec43613bff8a05e04caefce03b349b062288 Binary files /dev/null and b/function/prompt/table/__pycache__/cart_cartitem_group.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/category_categoryTranslation.cpython-311.pyc b/function/prompt/table/__pycache__/category_categoryTranslation.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0147860430f502ff6d67fbb62b534f16b07cdcc Binary files /dev/null and b/function/prompt/table/__pycache__/category_categoryTranslation.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/category_categoryTranslation.cpython-312.pyc b/function/prompt/table/__pycache__/category_categoryTranslation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c906c51e95c67635395658e16c13e86e8a7115c7 Binary files /dev/null and b/function/prompt/table/__pycache__/category_categoryTranslation.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/favourite.cpython-311.pyc b/function/prompt/table/__pycache__/favourite.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f5cc10b6d5b65a91cb28059ad194cc514a292a6 Binary files /dev/null and b/function/prompt/table/__pycache__/favourite.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/favourite.cpython-312.pyc b/function/prompt/table/__pycache__/favourite.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4480337d469f689bd9038767b1cbeeeb5f242b46 Binary files /dev/null and b/function/prompt/table/__pycache__/favourite.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/group_order_members.cpython-311.pyc b/function/prompt/table/__pycache__/group_order_members.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4c695bea969e08437aa69582aef745ec559e0c4 Binary files /dev/null and b/function/prompt/table/__pycache__/group_order_members.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/group_orders.cpython-311.pyc b/function/prompt/table/__pycache__/group_orders.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e132c8fff645b8c96d1bc413fd225e3bd363697a Binary files /dev/null and b/function/prompt/table/__pycache__/group_orders.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/orders_orderitem.cpython-311.pyc b/function/prompt/table/__pycache__/orders_orderitem.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb89ed087a864e9820ea70450d8cd73977f9359e Binary files /dev/null and b/function/prompt/table/__pycache__/orders_orderitem.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/orders_orderitem.cpython-312.pyc b/function/prompt/table/__pycache__/orders_orderitem.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d3d23806bc25fffc8fe42ded74abec388db47e2 Binary files /dev/null and b/function/prompt/table/__pycache__/orders_orderitem.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/payments_shipment_group.cpython-311.pyc b/function/prompt/table/__pycache__/payments_shipment_group.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7acda1274bc9d00d963518895384d310ec9ece71 Binary files /dev/null and b/function/prompt/table/__pycache__/payments_shipment_group.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/post_postTranslation.cpython-311.pyc b/function/prompt/table/__pycache__/post_postTranslation.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbbf58598f796b2cd455ce3da0a9230485e82452 Binary files /dev/null and b/function/prompt/table/__pycache__/post_postTranslation.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/post_postTranslation.cpython-312.pyc b/function/prompt/table/__pycache__/post_postTranslation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c6c6ee28104b7409c51bf0f13b5cc69a790bc91 Binary files /dev/null and b/function/prompt/table/__pycache__/post_postTranslation.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/product.cpython-311.pyc b/function/prompt/table/__pycache__/product.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f2e0e69e7c66c3f78361bb5aae38b41d86c872e Binary files /dev/null and b/function/prompt/table/__pycache__/product.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/product.cpython-312.pyc b/function/prompt/table/__pycache__/product.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e76ba0c0823b7e8acd8077093b36e6429c5662f9 Binary files /dev/null and b/function/prompt/table/__pycache__/product.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/review.cpython-311.pyc b/function/prompt/table/__pycache__/review.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da56b0254ba30830949953889039e41909376447 Binary files /dev/null and b/function/prompt/table/__pycache__/review.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/review.cpython-312.pyc b/function/prompt/table/__pycache__/review.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ddf3cc770b36fe1195b52e78b866f673b0a396c1 Binary files /dev/null and b/function/prompt/table/__pycache__/review.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/shipment.cpython-311.pyc b/function/prompt/table/__pycache__/shipment.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8acc95b5ff536b1adc8a7d6aabc39184fcb30e19 Binary files /dev/null and b/function/prompt/table/__pycache__/shipment.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/shipment.cpython-312.pyc b/function/prompt/table/__pycache__/shipment.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fbb2ae1efb67af4c8ae9216bfea884d84b38b7f Binary files /dev/null and b/function/prompt/table/__pycache__/shipment.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/shipper_attendance.cpython-311.pyc b/function/prompt/table/__pycache__/shipper_attendance.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4090e3293cf40d96df67e08370605cbf91eaf3a Binary files /dev/null and b/function/prompt/table/__pycache__/shipper_attendance.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/shipper_commission_detail.cpython-311.pyc b/function/prompt/table/__pycache__/shipper_commission_detail.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b667001780a392ac43fc5e94ef44f14990f3129c Binary files /dev/null and b/function/prompt/table/__pycache__/shipper_commission_detail.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/shipper_salary_summary.cpython-311.pyc b/function/prompt/table/__pycache__/shipper_salary_summary.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a385e599eb5738f783e1b1015afb51d1a9ddf921 Binary files /dev/null and b/function/prompt/table/__pycache__/shipper_salary_summary.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/user_coin.cpython-311.pyc b/function/prompt/table/__pycache__/user_coin.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e648057c172b497cdd4abd1ace6e8bae097143e Binary files /dev/null and b/function/prompt/table/__pycache__/user_coin.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/user_coin.cpython-312.pyc b/function/prompt/table/__pycache__/user_coin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4dd751ceb5ad477b8e3ccb51ac88bc1623380d3b Binary files /dev/null and b/function/prompt/table/__pycache__/user_coin.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/user_voucher.cpython-311.pyc b/function/prompt/table/__pycache__/user_voucher.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7659aaf0b596509009f381dcc52dea695178158 Binary files /dev/null and b/function/prompt/table/__pycache__/user_voucher.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/user_voucher.cpython-312.pyc b/function/prompt/table/__pycache__/user_voucher.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c18bbdbd037bf5a6c43ee74692911fd2ac9e499 Binary files /dev/null and b/function/prompt/table/__pycache__/user_voucher.cpython-312.pyc differ diff --git a/function/prompt/table/__pycache__/users.cpython-311.pyc b/function/prompt/table/__pycache__/users.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00effc3486d8c8f22f4c6e12aa49163b75d29989 Binary files /dev/null and b/function/prompt/table/__pycache__/users.cpython-311.pyc differ diff --git a/function/prompt/table/__pycache__/users.cpython-312.pyc b/function/prompt/table/__pycache__/users.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..900f0332c518ab07ee84d0e0f8b84611781f6ff2 Binary files /dev/null and b/function/prompt/table/__pycache__/users.cpython-312.pyc differ diff --git a/function/prompt/table/absence.py b/function/prompt/table/absence.py new file mode 100644 index 0000000000000000000000000000000000000000..415d27bf49c14ec52cae4c5f0181c63f085ad09b --- /dev/null +++ b/function/prompt/table/absence.py @@ -0,0 +1,9 @@ +prompt_absence_management = """ + +#### A. Quyền hạn và quy tắc hoạt động của bảng Absence: +- **CUSTOMER/ADMIN Role:** + - Không có quyền thao tác với bảng này kể cả xem. +- **SHIPPER Roles:** + - Chỉ được phép xem không được thực hiện thêm xóa, sửa + +""" \ No newline at end of file diff --git a/function/prompt/table/cart_cartItem.py b/function/prompt/table/cart_cartItem.py new file mode 100644 index 0000000000000000000000000000000000000000..6a832d23ad10856150bc74527478adb8fa23b1a3 --- /dev/null +++ b/function/prompt/table/cart_cartItem.py @@ -0,0 +1,103 @@ +prompt_cart_management = """ +### 1. Giỏ hàng (cart) và Mục trong giỏ hàng (cart_item) + +#### A. Quyền hạn và quy tắc hoạt động: + +##### **1. Giỏ hàng (cart)** +- **ADMIN Role:** + - Có quyền **tạo mới (INSERT) giỏ hàng** cho bất kỳ người dùng nào. + - Chỉ cho phép tạo giỏ hàng mới khi trong giỏ của họ không còn giỏ nào ở trạng thái **'NEW'**. + - Không có quyền chỉnh sửa giỏ hàng của người dùng khác sau khi tạo. + + +- **CUSTOMER/SHIPPER Roles:** + - Chỉ được phép **chỉnh sửa (UPDATE)** các giỏ hàng **thuộc về họ** nếu giỏ hàng đang ở trạng thái **'NEW'**. + - Không thể sửa đổi giỏ hàng nếu trạng thái khác **'NEW'**. + - Không thể tạo giỏ hàng mới cho người dùng khác. + - Chỉ cho phép tạo giỏ hàng mới khi trong giỏ của họ không còn giỏ nào ở trạng thái **'NEW'**. + +- **Xóa giỏ hàng:** + - Không cho phép xóa vĩnh viễn (`DELETE`). + - Nếu cần đánh dấu giỏ hàng là đã xóa, cập nhật trường `isdeleted = 1` và thiết lập `date_deleted`. + +--- + +##### **2. Mục trong giỏ hàng (cart_item)** +- Khi thêm sản phẩm vào giỏ hàng: + - **Mặc định số lượng (`quantity`) là 1** nếu không được chỉ định. + - Kiểm tra tồn kho của **biến thể sản phẩm (`product_variant`)** trước khi thêm vào giỏ hàng: + - Nếu số lượng sản phẩm không đủ, trả về lỗi: + **"ERROR: Insufficient stock for this product variant."** + - Nếu sản phẩm có trạng thái `isdeleted = 1`, không được phép thêm vào giỏ hàng và trả về lỗi: + **"ERROR: This product variant is no longer available."** + +- **Quyền chỉnh sửa giỏ hàng:** + - Người dùng chỉ được phép **cập nhật mục trong giỏ hàng của chính họ**. Không được phép đụng vào giỏ hàng của người khác + - Nếu cố gắng sửa đổi giỏ hàng của người khác, trả về lỗi: + **"ERROR: Unauthorized modification of another user's cart."** + +- **Xóa sản phẩm khỏi giỏ hàng:** + - Không cho phép xóa vật lý (`DELETE`). + - Nếu cần xóa sản phẩm khỏi giỏ hàng, thực hiện xóa logic (`is_deleted = 1`, `data_deleted`). + +--- + +### 2. Các bước kiểm tra dữ liệu khi thao tác với giỏ hàng và sản phẩm trong giỏ hàng + +#### **A. Kiểm tra trước khi thêm sản phẩm vào giỏ hàng** +1. **Xác định giỏ hàng hợp lệ**: + - Nếu giỏ hàng không thuộc về người dùng hiện tại, trả về lỗi: + **"ERROR: Unauthorized modification of another user's cart."** + - Nếu trạng thái giỏ hàng khác **'NEW'**, không cho phép sửa đổi. + +2. **Kiểm tra sản phẩm hợp lệ**: + - Nếu sản phẩm **không tồn tại hoặc đã bị đánh dấu `is_deleted = 1`**, trả về lỗi: + **"ERROR: This product variant is no longer available."** + - Nếu tồn kho **không đủ số lượng yêu cầu**, trả về lỗi: + **"ERROR: Insufficient stock for this product variant."** + +3. **Thêm sản phẩm vào giỏ hàng**: + - Nếu số lượng sản phẩm không được cung cấp, thiết lập `quantity = 1` mặc định. + - Nếu sô lượng sản phẩm yêu cầu lớn hơn số sản phẩm còn lại hiện có thì hãy báo lỗi. + +--- + +#### **B. Kiểm tra trước khi cập nhật mục trong giỏ hàng** +1. **Xác minh quyền chỉnh sửa**: + - Nếu giỏ hàng không thuộc về người dùng hiện tại, trả về lỗi: + **"ERROR: Unauthorized modification of another user's cart."** + - Nếu trạng thái giỏ hàng khác **'NEW','PAUSE**, không cho phép cập nhật. + +2. **Kiểm tra số lượng sản phẩm hợp lệ**: + - Nếu sản phẩm **không tồn tại hoặc `is_deleted = 1`**, không được phép cập nhật số lượng và trả về lỗi: + **"ERROR: This product variant is no longer available."** + - Nếu tồn kho không đủ số lượng mong muốn, trả về lỗi: + **"ERROR: Insufficient stock for this product variant."** + +3. **Thực hiện cập nhật số lượng sản phẩm trong giỏ hàng**. + +--- + +#### **C. Kiểm tra trước khi xóa mục trong giỏ hàng** +1. Không cho phép xóa vật lý (`DELETE`). +2. Nếu muốn xóa, thực hiện xóa logic bằng cách cập nhật: + - `is_deleted = 1` + - `data_deleted = CURRENT_TIMESTAMP` + +--- + +## **. Xác Thực Danh Tính Trước Khi Xử Lý Yêu Cầu** +- **Quy tắc xác thực:** + - Nếu vai trò là **ADMIN**, hệ thống cấp quyền truy cập ngay lập tức. + - Nếu vai trò là **CUSTOMER hoặc SHIPPER**, hệ thống phải **xác minh rằng thông tin yêu cầu thuộc về chính người dùng đang xác thực** (dựa trên `user_id`). + +- **Truy vấn thông tin cá nhân qua email hoặc số điện thoại:** + - Hệ thống **phải xác minh email hoặc số điện thoại khớp với tài khoản của user_id đã xác thực**. + - Nếu không khớp, hệ thống **ngay lập tức từ chối truy vấn** với lỗi: + - **"ERROR: Unauthorized access to another user's information."** + +🚨 **Lưu ý quan trọng:** +- Nếu thao tác vi phạm quyền hạn hoặc không thỏa mãn điều kiện kiểm tra, **trả về lỗi ngay lập tức và không thực thi truy vấn SQL**. +- Đảm bảo kiểm tra tồn kho trước khi thêm hoặc cập nhật sản phẩm trong giỏ hàng. +- Việc xóa sản phẩm hoặc giỏ hàng chỉ được thực hiện dưới dạng **xóa logic** (`is_deleted = 1`). +""" \ No newline at end of file diff --git a/function/prompt/table/cart_cartitem_group.py b/function/prompt/table/cart_cartitem_group.py new file mode 100644 index 0000000000000000000000000000000000000000..f412f8d8d835acf03cb1006429913233e8fdc8cb --- /dev/null +++ b/function/prompt/table/cart_cartitem_group.py @@ -0,0 +1,22 @@ +prompt_cart_group_management = """ +### 1. Giỏ hàng nhóm (cart_group) và Mục trong giỏ hàng nhóm (cart_item_group) + +#### A. Quyền hạn và quy tắc hoạt động: + +##### **1. Giỏ hàng nhóm (cart_group)** +- **ADMIN/SHIPPER Role:** + - Không có quyền thao tác với bảng này kể cả xem. +- **CUSTOMER Roles:** + - Chỉ được phép **chỉnh sửa (UPDATE)** các giỏ hàng **thuộc về họ**. + - Chỉ trong 1 nhóm(group_order_member) mới được phép xem giỏ hàng của nhau. + +- **Xóa giỏ hàng:** + - Không được phép thao tác xóa giỏ hàng + +--- + +##### **2. Mục trong giỏ hàng (cart_item_group)** +- CHỉ được phép xem. Không được thêm, xóa, chỉnh sửa. + + +""" \ No newline at end of file diff --git a/function/prompt/table/category_categoryTranslation.py b/function/prompt/table/category_categoryTranslation.py new file mode 100644 index 0000000000000000000000000000000000000000..60997de0157c292fb1c0063247a4ce2f6b131c63 --- /dev/null +++ b/function/prompt/table/category_categoryTranslation.py @@ -0,0 +1,71 @@ +prompt_category_management = """ +### 1. Danh mục (category) và Dịch danh mục (category_translation) + +#### A. Quyền hạn và quy tắc hoạt động: +- **ADMIN Role:** Có quyền thêm (INSERT) và cập nhật (UPDATE) danh mục nhưng **không được xóa**. +- Ngày cập nhật hay ngày thêm luôn luôn là thời gian hiện tại. + - Nếu cố gắng xóa danh mục, hệ thống trả về lỗi: **"ERROR: Deletion of categories is not allowed."** + - Nếu thực hiện xóa danh mục, cần: + - **Xóa logic tất cả sản phẩm liên quan(product) (`is_deleted = 1`, `date_deleted`). Khi xoá các product liên quan hãy kiểm tra thêm để xóa các product_variants,product_translation liên quan tới product đó.** + - **Khôi phục các biến thể (`product` khi khôi phục product hãy khôi phục các bảng liên quan đến product_id này gồm có `product_translation`,`product_variants`) liên quan nếu danh mục được khôi phục.** +- **User/Shipper Role:** Chỉ có quyền xem danh mục. + - Nếu cố gắng **thêm, cập nhật, hoặc xóa**, hệ thống trả về lỗi: + **"ERROR: Unauthorized modification of another user's information."** + +--- + +### 2. Kiểm tra trùng lặp trước khi thêm/cập nhật danh mục(Chỉ có role ADMIN được phép thêm/cập nhật) + +#### A. Kiểm tra khi thêm mới danh mục (INSERT) +Trước khi thêm danh mục, hệ thống phải kiểm tra trùng lặp tên danh mục (`cate_name`) trong bảng `category` +- Ngày cập nhật hay ngày thêm luôn luôn là thời gian hiện tại. +1. Khi thêm danh mục, hệ thống thực hiện **dịch tên danh mục (cate_name)**: + - Nếu ngôn ngữ đầu vào là **Tiếng Việt**, kiểm tra trực tiếp trong bảng `category`. + - Nếu ngôn ngữ đầu vào là **Tiếng Anh**, trước tiên dịch sang **Tiếng Việt**, sau đó kiểm tra trong bảng `category`. + - Kiểm tra xem danh mục có tồn tại hay không (tức là `is_deleted = 0` và có tồn tại trong bảng `category`). Nếu có tồn tại, trả về lỗi: DUPLICATE_CATEGORY_NAME: This category name already exists. + - Nếu danh mục không tồn tại, thực hiện thêm danh mục với `cate_name` bằng Tiếng Việt. +2. Nếu danh mục (cate_name) đã tồn tại trong hệ thống, trả về lỗi: + **"DUPLICATE_CATEGORY_NAME: This category name already exists."** +3. Nếu không trùng lặp, thực hiện: + - Thêm danh mục với `cate_name` bằng Tiếng Việt. + - Tạo bản dịch danh mục (`category_translation`) với `cate_name` được dịch sang Tiếng Anh. Trước khi thêm, kiểm tra xem bản dịch đã tồn tại hay chưa. + - Nếu bản dịch đã tồn tại, trả về lỗi: **"DUPLICATE_CATEGORY_NAME: This category translation already exists."** + - Nếu không tồn tại, thêm bản dịch danh mục với `cate_name` bằng Tiếng Anh. + +#### B. Kiểm tra khi cập nhật danh mục (UPDATE) +- Ngày cập nhật hay ngày thêm luôn luôn là thời gian hiện tại. +Trước khi cập nhật danh mục, hệ thống phải kiểm tra trùng lặp tên danh mục (`cate_name`) trong bảng `category` và `product`. Check xem liệu danh mục đó có tồn tại hay không(tức là is_deleted = 0). +1. Khi cập nhật danh mục, dịch `cate_name` theo quy tắc: + - Nếu ngôn ngữ là **Tiếng Việt**, cập nhật danh mục với `cate_name` Tiếng Việt, sau đó dịch lại `cate_name` Tiếng Anh và cập nhật `category_translation`. + - Nếu ngôn ngữ là **Tiếng Anh**, cập nhật danh mục với `cate_name` Tiếng Việt trước, sau đó dịch lại `cate_name` Tiếng Anh và cập nhật `category_translation`. +2. Trước khi cập nhật, kiểm tra: + - Không có **bản ghi khác** (ngoại trừ danh mục đang cập nhật) có cùng `cate_name` sau khi dịch.(Gồm cả trong bảng product và product_translation) + - Nếu trùng lặp, trả về lỗi: + **"DUPLICATE_CATEGORY_NAME: Another category with the same name already exists."** +3. Nếu hợp lệ, tiến hành cập nhật danh mục và bản dịch danh mục tương ứng. Khi cập nhật vui lòng cập nhật lại trường 'date_updated'. + + +--- + +## **4. Xác Thực Danh Tính Trước Khi Xử Lý Yêu Cầu** +- **Quy tắc xác thực:** + - Nếu vai trò là **ADMIN**, hệ thống cấp quyền truy cập ngay lập tức. + - Nếu vai trò là **CUSTOMER hoặc SHIPPER**, hệ thống phải **xác minh rằng thông tin yêu cầu thuộc về chính người dùng đang xác thực** (dựa trên `user_id`). + +- **Truy vấn thông tin cá nhân qua email hoặc số điện thoại:** + - Hệ thống **phải xác minh email hoặc số điện thoại khớp với tài khoản của user_id đã xác thực**. + - Nếu không khớp, hệ thống **ngay lập tức từ chối truy vấn** với lỗi: + - **"ERROR: Unauthorized access to another user's information."** + + +### 5. **Xóa danh mục và khôi phục sản phẩm liên quan**(Chỉ có quyền ADMIN được phép) +- **Xóa danh mục** chỉ thực hiện xóa logic (`is_deleted = 1`). +- **Các sản phẩm liên quan sẽ bị xóa logic (`is_deleted = 1`) nhưng các biến thể (`product_variants`) cũng sẽ bị liên quan tức là(is_deleted = 1).**. NGoài ra các favourite_item liên quan cũng sẽ bị is_deleted = 1 +- **Khi khôi phục danh mục, hệ thống phải khôi phục lại tất cả sản phẩm liên quan như mình liệt kê ở trên.** + + + +🚨 **Lưu ý quan trọng:** +- Việc kiểm tra trùng lặp (`DUPLICATE_CATEGORY_NAME`) **phải được thực hiện trong một truy vấn SQL duy nhất**, đảm bảo nếu trùng lặp, **quá trình chèn hoặc cập nhật dừng ngay lập tức**. +- **Không thực hiện kiểm tra trùng lặp bằng truy vấn riêng lẻ trước đó**, tránh điều kiện tranh chấp dữ liệu (race condition). +""" diff --git a/function/prompt/table/contact.py b/function/prompt/table/contact.py new file mode 100644 index 0000000000000000000000000000000000000000..71a468783931aa8d4275c0a267e1ec9783eeb3e8 --- /dev/null +++ b/function/prompt/table/contact.py @@ -0,0 +1,3 @@ +prompt_contact = """ + +""" \ No newline at end of file diff --git a/function/prompt/table/favourite.py b/function/prompt/table/favourite.py new file mode 100644 index 0000000000000000000000000000000000000000..578a7c093c8a9982f165b5c9b04223a6f807945b --- /dev/null +++ b/function/prompt/table/favourite.py @@ -0,0 +1,42 @@ +prompt_favourite_management = """ +### 1. Danh sách yêu thích (favourite) + +#### A. Quyền hạn và quy tắc hoạt động: +- Người dùng có thể **tạo danh sách yêu thích** nếu danh sách đó **chưa tồn tại** cho `user_id` của họ. +- **Không cho phép tạo trùng danh sách yêu thích** – nếu danh sách đã tồn tại cho `user_id`, hệ thống **từ chối yêu cầu tạo mới**. +- Khi **xóa danh sách yêu thích**, hệ thống thực hiện **xóa logic**: + - **Đặt giá trị `is_deleted = 1`** để đánh dấu danh sách đã xóa. + - **Ghi lại `data_deleted`** để lưu thời điểm xóa. + - **Tất cả các mục yêu thích (`favourite_item`) liên quan cũng phải bị xóa logic** (`is_deleted = 1` và `data_deleted`). + +--- + +### 2. Mục yêu thích (favourite_item) + +#### A. Quyền hạn và quy tắc hoạt động: +- **CUSTOMER/ADMIN/SHIPPER có thể thêm sản phẩm vào danh sách yêu thích** với điều kiện: + - **Sản phẩm (product_id) và kích thước (size) chưa tồn tại** trong danh sách yêu thích của họ. + - Nếu sản phẩm và kích thước đã tồn tại, hệ thống từ chối thêm trùng lặp.(Quan trọng: Hãy kiểm tra kỹ trước khi thêm). +- **Không cho phép thêm sản phẩm đã bị xóa vào danh sách yêu thích**. + +- **CUSTOMER/ADMIN/SHIPPER có thể xóa mục yêu thích** nhưng chỉ được xóa **các mục do chính họ thêm vào**(Không được phép xóa của người khác(user_id khác)). +- Khi xóa một mục yêu thích, hệ thống thực hiện **xóa logic**: + - **Đặt giá trị `isdeleted = 1`** để đánh dấu mục đã xóa. + - **Ghi lại `data_deleted`** để lưu thời điểm xóa(ngày, thời gian). + +## **. Xác Thực Danh Tính Trước Khi Xử Lý Yêu Cầu** +- **Quy tắc xác thực:** + - Nếu vai trò là **ADMIN**, hệ thống cấp quyền truy cập ngay lập tức. + - Nếu vai trò là **CUSTOMER hoặc SHIPPER**, hệ thống phải **xác minh rằng thông tin yêu cầu thuộc về chính người dùng đang xác thực** (dựa trên `user_id`). + +- **Truy vấn thông tin cá nhân qua email hoặc số điện thoại:** + - Hệ thống **phải xác minh email hoặc số điện thoại khớp với tài khoản của user_id đã xác thực**. + - Nếu không khớp, hệ thống **ngay lập tức từ chối truy vấn** với lỗi: + - **"ERROR: Unauthorized access to another user's information."** + +#### B. Kiểm tra quyền truy cập: +- Nếu `user_id` cố gắng thao tác trên danh sách hoặc mục yêu thích của người khác, hệ thống trả về lỗi: + **"ERROR: Unauthorized access to another user's favourites."** Ngoại trừ ROLE **ADMIN**. +""" + + diff --git a/function/prompt/table/group_order_members.py b/function/prompt/table/group_order_members.py new file mode 100644 index 0000000000000000000000000000000000000000..abafff5b06513810f2dece01793cf1b3b8c2f809 --- /dev/null +++ b/function/prompt/table/group_order_members.py @@ -0,0 +1,21 @@ +prompt_shipper_attendance_management = """ + +#### A. Quyền hạn và quy tắc hoạt động của bảng Group Order Member: +- Đây là bảng chứa các thành viên trong nhóm nên mọi hoạt động xóa thêm đều tương tác với bản này +- **ADMIN/SHIPPER Role:** + - Không có quyền thao tác với bảng này kể cả xem. + - Nếu những người có quyền này cố tình vi phạm bảng này thì vui lòng trả lại lỗi. +- **CUSTOMER Role:** + + - Khỉ hỏi về nhóm tham gia thì vui lòng truy cập bảng group_order_member này để xác định xem customer đó có tham gia group_order_member nào rồi mới liên kết lại group_orders để lấy thêm thông tin về nhóm họ tham gia.Khi truy vấn hai bảng này không cần kiểm tra is_deleted. + - - Khỉ hỏi về nhóm tham gia thì vui lòng truy cập bảng group_order_member này để xác định xem user đó có tham gia nhóm nào rồi mới liên kết lại group_orders để lấy thêm thông tin. Khi truy vấn hai bảng này không cần kiểm tra is_deleted. + - Chỉ cho phép xem thành viên trong nhóm họ đang đứng không được phép xem nhóm khác mà họ không tham gia. + - Nếu người dùng có quyền Leader thì có thể xóa thành viên khác nhung phải set is_deleted = True và set thêm ngày xóa, không được xóa mất bản ghi + - Nếu người dùng có quyền Leader xóa thành viên đó khỏi nhóm thì tất phải gán is_deleted_leader = True. + - Nếu leader trong nhóm đó yêu cầu xóa chính họ khỏi nhóm thì tất cả thành viên trong nhóm đó sẽ bị set is_deleted là true.Sau khi set và cập nhật xong mới tiến hành cập nhật status của bảng liên quan gôm có Group Orders(set trạng thái Group là CANCELLED) và set is_deleted và date_deleted. Sau đó bảng đi theo các bảng liên quan Group_Order_member nhu Cart_Group, Cart_item_group để đánh dấu is_deleted và date_deleted. + - Mọi việc xóa đều đánh dấu là is_deleted và không được phép dùng DML Delete. + + *** Chỉ được phép dùng SELECT, UPDATE + + +""" \ No newline at end of file diff --git a/function/prompt/table/group_orders.py b/function/prompt/table/group_orders.py new file mode 100644 index 0000000000000000000000000000000000000000..d30c3fb7ccdf4aa6ea27b8a2c9c59e06c1f11501 --- /dev/null +++ b/function/prompt/table/group_orders.py @@ -0,0 +1,58 @@ +prompt = """ +# **Hệ Thống Kiểm Soát Quyền Truy Cập (RBAC) Cho GroupOrder** + +## **1. Quy Định Về Quyền Hạn** + +### **Role: ADMIN** +- Có **toàn quyền** thực hiện tất cả các thao tác SQL (SELECT, INSERT, UPDATE, DELETE) trên các bảng `OrderItem`, `Orders`, và `Payment`. +- Được phép xem và chỉnh sửa **tất cả dữ liệu** trong hệ thống. +- Phải sử dụng **truy vấn có tham số (parameterized queries)** để tránh tấn công SQL Injection. + +### **Role: CUSTOMER** +- Chỉ được phép **truy vấn (SELECT)** dữ liệu từ bảng `GroupOrders` và `GroupOrderMemeber` và các bảng liên quan, nhưng **chỉ đối với đơn hàng nhóm của chính họ**. +- Khỉ hỏi về nhóm tham gia thì vui lòng truy cập bảng group_order_member này để xác định xem customer đó có tham gia group_order_member nào rồi mới liên kết lại group_orders để lấy thêm thông tin về nhóm họ tham gia. Khi truy vấn hai bảng này không cần kiểm tra is_deleted. +- Khi hỏi về đơn hàng nhóm thành công chỉ được tính khi `status` của payment_group là 'COMPLETED' và grouporder có status là COMPLETED. +- Có thể sử dụng `JOIN` để kết hợp thông tin từ các bảng khác như `Payment_group`,`Group_order_member`, `Product`, `User`,... +- Cẩn thận khi kết hợp các bảng lại với nhau nhưng mà khi trả lời liên quan thì các bảng Grouporders, Group_order_member(liên quan đến thêm ví dụ như cart_group, cart_item_group)(cart_item_group lại có thể liên quan tới product, product_variants)), payment_group, là quan trọng nhất. +- Hệ thống phải kiểm tra `user_id` để đảm bảo người dùng chỉ truy xuất thông tin của chính họ. +- **Không được phép** thực hiện INSERT, UPDATE hoặc DELETE trên bất kỳ bảng nào. +- Nếu cố gắng thực hiện thao tác bị cấm, hệ thống phải **từ chối ngay lập tức** với lỗi: + - "ERROR: User does not have permission to perform this action." +- Phải sử dụng **truy vấn có tham số** để ngăn chặn SQL Injection. + +### **Role: SHIPPER** +- Được phép **truy vấn (SELECT)** bảng `GroupOrders`, nhưng **chỉ đối với các đơn hàng được giao cho họ** (dựa vào `shipment_group_id` truy ra được payment_group từ đó truy ra group_order và group_order_member). +- Có thể sử dụng `JOIN` với bảng `GroupOrdersMember` rồi bảng CartGroup rồi CartGroupItem để xem thông tin đơn hàng, nhưng **chỉ với đơn hàng mà họ được chỉ định**. +- **Không được phép** thực hiện INSERT, UPDATE hoặc DELETE trên bất kỳ bảng nào. +- Nếu cố gắng thực hiện thao tác bị cấm, hệ thống phải **từ chối ngay lập tức** với lỗi: + - "ERROR: User does not have permission to perform this action." +- Phải sử dụng **truy vấn có tham số** để ngăn chặn SQL Injection. + +--- + +## **2. Nguyên Tắc Bảo Mật Chung** +- Hệ thống phải kiểm soát quyền **ở cả mức truy vấn SQL và logic ứng dụng**. +- Mọi thao tác SQL phải được kiểm tra quyền truy cập trước khi thực thi. +- Bất kỳ nỗ lực truy cập trái phép nào phải **dừng ngay lập tức** và trả về lỗi rõ ràng. +- **Bắt buộc sử dụng truy vấn có tham số** để ngăn ngừa SQL Injection. +- Kiểm tra dữ liệu đầu vào để đảm bảo tính an toàn và đúng định dạng. +- Ghi log tất cả các truy cập và lỗi để phục vụ kiểm tra và giám sát bảo mật. + +--- + + +## **3. Quy Trình Kiểm Tra Trước Khi Thực Hiện Truy Vấn** +1. **Người dùng đăng nhập và vai trò của họ được xác minh.** +2. **Hệ thống kiểm tra quyền truy cập dựa trên vai trò.** +3. **CUSTOMER và SHIPPER chỉ có thể truy vấn dữ liệu liên quan đến họ.** +4. **Nếu phát hiện hành vi truy cập trái phép, hệ thống sẽ từ chối ngay lập tức.** +5. **Tất cả các thao tác được ghi log để theo dõi và kiểm tra bảo mật.** + +--- + +## **4. Mục Tiêu Của Hệ Thống** +- **Đảm bảo tính bảo mật cao nhất cho dữ liệu đơn hàng, sản phẩm và thanh toán.** +- **Ngăn chặn truy cập trái phép, tấn công SQL Injection và leo thang quyền hạn.** +- **Hệ thống có khả năng mở rộng tốt và có thể hỗ trợ nhiều người dùng cùng lúc.** + +""" \ No newline at end of file diff --git a/function/prompt/table/orders_orderitem.py b/function/prompt/table/orders_orderitem.py new file mode 100644 index 0000000000000000000000000000000000000000..1b06e4ec60549740e81e5405c73d17a7ae1d6ddd --- /dev/null +++ b/function/prompt/table/orders_orderitem.py @@ -0,0 +1,59 @@ +prompt = """ +# **Hệ Thống Kiểm Soát Quyền Truy Cập (RBAC) Cho OrderItem, Orders, và Payment** + +## **1. Quy Định Về Quyền Hạn** + +### **Role: ADMIN** +- Có **toàn quyền** thực hiện tất cả các thao tác SQL (SELECT, INSERT, UPDATE, DELETE) trên các bảng `OrderItem`, `Orders`, và `Payment`. +- Được phép xem và chỉnh sửa **tất cả dữ liệu** trong hệ thống. +- Phải sử dụng **truy vấn có tham số (parameterized queries)** để tránh tấn công SQL Injection. +- Mọi hành động đều được ghi log để phục vụ mục đích kiểm tra và bảo mật. + +### **Role: CUSTOMER** +- Chỉ được phép **truy vấn (SELECT)** dữ liệu từ bảng `Orders` và `OrderItem` và các bảng liên quan, nhưng **chỉ đối với đơn hàng của chính họ**. +- Khi hỏi về đơn hàng thành công chỉ được tính khi `status` của payment là 'COMPLETED' và order là CONFIRMED. +- Có thể sử dụng `JOIN` để kết hợp thông tin từ các bảng khác như `Payment`, `Product`, `User`,... +- Cẩn thận khi kết hợp các bảng lại với nhau nhưng mà khi trả lời liên quan thì các bảng orders, orderitem(liên quan đến thêm ví dụ như cart, cart_item(cart_item lại có thể liên quan tới product, product_variants)), payment, là quan trọng nhất. +- Hệ thống phải kiểm tra `user_id` để đảm bảo người dùng chỉ truy xuất thông tin của chính họ. +- **Không được phép** thực hiện INSERT, UPDATE hoặc DELETE trên bất kỳ bảng nào. +- Nếu cố gắng thực hiện thao tác bị cấm, hệ thống phải **từ chối ngay lập tức** với lỗi: + - "ERROR: User does not have permission to perform this action." +- Phải sử dụng **truy vấn có tham số** để ngăn chặn SQL Injection. + +### **Role: SHIPPER** +- Được phép **truy vấn (SELECT)** bảng `Orders`, nhưng **chỉ đối với các đơn hàng được giao cho họ** (dựa vào `assignment_id`). +- Có thể sử dụng `JOIN` với bảng `OrderItem` để xem thông tin đơn hàng, nhưng **chỉ với đơn hàng mà họ được chỉ định**. +- **Không được phép** thực hiện INSERT, UPDATE hoặc DELETE trên bất kỳ bảng nào. +- Nếu cố gắng thực hiện thao tác bị cấm, hệ thống phải **từ chối ngay lập tức** với lỗi: + - "ERROR: User does not have permission to perform this action." +- Phải sử dụng **truy vấn có tham số** để ngăn chặn SQL Injection. + +--- + +## **2. Nguyên Tắc Bảo Mật Chung** +- Hệ thống phải kiểm soát quyền **ở cả mức truy vấn SQL và logic ứng dụng**. +- Mọi thao tác SQL phải được kiểm tra quyền truy cập trước khi thực thi. +- Bất kỳ nỗ lực truy cập trái phép nào phải **dừng ngay lập tức** và trả về lỗi rõ ràng. +- **Bắt buộc sử dụng truy vấn có tham số** để ngăn ngừa SQL Injection. +- Kiểm tra dữ liệu đầu vào để đảm bảo tính an toàn và đúng định dạng. +- Ghi log tất cả các truy cập và lỗi để phục vụ kiểm tra và giám sát bảo mật. + +--- + + + +## **3. Quy Trình Kiểm Tra Trước Khi Thực Hiện Truy Vấn** +1. **Người dùng đăng nhập và vai trò của họ được xác minh.** +2. **Hệ thống kiểm tra quyền truy cập dựa trên vai trò.** +3. **CUSTOMER và SHIPPER chỉ có thể truy vấn dữ liệu liên quan đến họ.** +4. **Nếu phát hiện hành vi truy cập trái phép, hệ thống sẽ từ chối ngay lập tức.** +5. **Tất cả các thao tác được ghi log để theo dõi và kiểm tra bảo mật.** + +--- + +## **4. Mục Tiêu Của Hệ Thống** +- **Đảm bảo tính bảo mật cao nhất cho dữ liệu đơn hàng, sản phẩm và thanh toán.** +- **Ngăn chặn truy cập trái phép, tấn công SQL Injection và leo thang quyền hạn.** +- **Hệ thống có khả năng mở rộng tốt và có thể hỗ trợ nhiều người dùng cùng lúc.** + +""" diff --git a/function/prompt/table/payments_shipment_group.py b/function/prompt/table/payments_shipment_group.py new file mode 100644 index 0000000000000000000000000000000000000000..9442d7b9ce3fbba560a7671e4574139a5feb56b8 --- /dev/null +++ b/function/prompt/table/payments_shipment_group.py @@ -0,0 +1,55 @@ +prompt_shipment = """ +###Quản lý Shipment_Group + +#### 1. **Quyền hạn của Role Shipper** +- **Shippers chỉ có thể cập nhật trạng thái của shipment_group trong các điều kiện sau:** + + 1. **Cập nhật trạng thái thành `SUCCESS`:** + - Khi cập nhật shipment_group thành `SUCCESS`, kiểm tra phương thức thanh toán liên quan: + - Nếu phương thức thanh toán là `CASH`, cập nhật trạng thái thanh toán thành `COMPLETED`. + - ⚠️ **Lưu ý:** Không được phép thay đổi trạng thái shipment_group nếu nó đã ở `CANCELLED`, `SUCCESS`, hoặc `SHIPPING`. + + 2. **Cập nhật trạng thái thành `CANCELLED`:** + - Khi cập nhật shipment_group thành `CANCELLED`: + - Cập nhật trường `datecancel` của bảng `Shipment_Group` với timestamp hiện tại. + - Kiểm tra phương thức thanh toán liên quan: + - Nếu phương thức là `CASH`, cập nhật trạng thái thanh toán thành `FAILED`. + - Nếu phương thức không phải `CASH`, cập nhật trạng thái thanh toán thành `REFUND`. + - Cập nhật bảng `Order`: + - Đặt trạng thái đơn hàng thành `CANCELLED`. + - Cập nhật `cancel_date` của đơn hàng với timestamp hiện tại. + + 3. **Giới hạn trạng thái:** + - Khi một shipment_group đã có trạng thái `CANCELLED`, `SUCCESS`, hoặc `SHIPPING`, không được phép thay đổi trạng thái đó. + - Nếu vi phạm, trả về lỗi: + ``` + "ERROR: Shipment_group status cannot be reverted once set to SUCCESS, CANCELLED, or SHIPPING." + ``` + + 4. **Chỉ có thể cập nhật shipment_group mà shipper sở hữu:** + - Nếu shipper cố gắng cập nhật shipment không thuộc về họ, hệ thống phải từ chối và trả về lỗi: + ``` + "ERROR: Unauthorized to update shipment group status for this shipment group." + ``` + +--- + +#### 2. **Quyền hạn của các vai trò khác (ADMIN, CUSTOMER)** +- **Admin và CUSTOMER KHÔNG có quyền cập nhật shipment_group.** +- Nếu bất kỳ vai trò nào ngoài **SHIPPER** cố gắng cập nhật shipment_group, hệ thống phải ngay lập tức từ chối truy vấn với lỗi: "ERROR: Role not authorized to update shipment status." + +--- + +#### 3. **Quy tắc chung về Shipment Group** +- **KHÔNG được phép thực hiện `INSERT` hoặc `DELETE` trên bảng Shipment_Group.** +- Chỉ các truy vấn `SELECT` và `JOIN` được phép đối với các vai trò khác ngoài Shipper. +- **Nếu có nỗ lực không hợp lệ, hệ thống phải từ chối ngay lập tức.** + +--- + +🚨 **Lưu ý quan trọng:** +- **SHIPPER chỉ có thể cập nhật trạng thái trong các điều kiện hợp lệ**. +- **Cập nhật trạng thái phải tuân thủ quy tắc thanh toán và đơn hàng**. +- **Hệ thống phải từ chối ngay lập tức nếu vi phạm quyền hạn(Câu query phải kiểm tra thật kĩ càng)**. + +""" \ No newline at end of file diff --git a/function/prompt/table/post_postTranslation.py b/function/prompt/table/post_postTranslation.py new file mode 100644 index 0000000000000000000000000000000000000000..ea4d438adcd1862faea43f3aa41fe2b2df44298c --- /dev/null +++ b/function/prompt/table/post_postTranslation.py @@ -0,0 +1,54 @@ +prompt_post_management = """ +# **Quản Lý Bài Viết Theo Vai Trò (Post Management by Role)** + +## **1. Quyền Hạn Theo Vai Trò** + +### **A. ADMIN (Quản trị viên)** +- Được phép **thêm mới (INSERT) và cập nhật (UPDATE)** bài viết (`post`) và bản dịch bài viết (`post_translation`). +- **Không được phép xóa bài viết.** +- Nếu thực hiện thao tác không hợp lệ, hệ thống **ngay lập tức trả về lỗi** và **không thực thi truy vấn SQL**. + +#### **Quy trình thêm bài viết (INSERT)** +** Quan trọng: Kiểm tra ngôn ngữ hiện tại đang dùng là Vietnamese hay English.** +1. Khi thêm bài viết (`post`) mà ngôn ngữ đang tiếng Anh(english), hệ thống **tự động chuyển đổi tiêu đề (`title`) và nội dung (`content`) sang tiếng Việt trước khi lưu vào bảng `post`**. +2. Khi thêm bài viết vào bảng `post`, hệ thống **tự động tạo bản dịch bài viết (`post_translation`) với tiêu đề và nội dung được dịch sang tiếng Anh**.(áp dụng khi ngôn ngữ hiện tại là tiếng Việt). +3. Việc dịch nội dung đảm bảo nội dung tiếng Anh phản ánh chính xác nội dung tiếng Việt. + +#### **Quy trình cập nhật bài viết (UPDATE)** +Luôn luôn check xem liệu tiêu đề mới mà bạn muốn đổi có tồn tại hay không. +1. Khi cập nhật bài viết, hệ thống **chỉ cập nhật nội dung của bài viết hiện tại**, không tạo bản ghi mới. +2. **Nếu bài viết hiện tại bằng tiếng Việt:** + - Cập nhật tiêu đề và nội dung bằng tiếng Việt. + - Sau đó, hệ thống **cập nhật bản dịch (`post_translation`) với tiêu đề và nội dung đã được dịch sang tiếng Anh**. +3. **Nếu bài viết hiện tại bằng tiếng Anh:** + - Cập nhật tiêu đề và nội dung bằng tiếng Việt. + - Sau khi cập nhật, hệ thống **cập nhật bản dịch (`post_translation`) với tiêu đề và nội dung đã được dịch lại sang tiếng Anh** để đảm bảo tính nhất quán. +4. **Không cho phép cập nhật trực tiếp bài viết bằng tiếng Anh mà không có bản gốc tiếng Việt**. + +--- + +### **B. CUSTOMER & SHIPPER (Người dùng & Shipper)** +- **Chỉ có quyền xem bài viết (`SELECT`)**. Khi xem bài viết mà ngôn ngữ là English hãy ưu tiên dùng post_translation để xem bài viết đã được dịch sang tiếng Anh. Khi ngôn ngữ là tiếng Việt hay dùng Post để xem bài viết gốc. +- **Không được phép thêm mới (INSERT) hoặc cập nhật (UPDATE) bài viết hoặc bản dịch (`post_translation`,'post')**. +- Nếu cố gắng thực hiện thao tác bị cấm, hệ thống sẽ **ngay lập tức từ chối** và trả về lỗi: + - **"ERROR: Unauthorized modification of another user's information."** + +--- + +### **2. Xử Lý Lỗi & Kiểm Tra Quyền Hạn** +- **Mọi thao tác sai quyền hạn** sẽ bị từ chối ngay lập tức với thông báo lỗi rõ ràng. +- **Không thực thi bất kỳ truy vấn SQL nào** nếu vai trò không hợp lệ. +- Khi cập nhật bài viết, Vui lòng kiểm tra **bài viết có tồn tại không**(ưu tiên check tiêu đề của bài post) trước khi thực hiện bất kỳ thay đổi nào. Nếu bài viết không tồn tại, hệ thống trả về lỗi: + - **"ERROR: Post not found."** + +--- + +**Lưu ý:** +- Hệ thống đảm bảo **bài viết gốc luôn bằng tiếng Việt**(tức là sẽ được lưu vào post trước khi chuyển đổi ngôn ngữ sang tiếng Anh). +- **Bản dịch bài viết (`post_translation`) luôn bằng tiếng Anh**. +- Luôn luôn kiểm tra trường is_deleted để xem bài viết có bị xóa không. +- **Không được phép xóa bài viết dùng bất kỳ cách nào chỉ cho phép xóa mềm dữ liệu(set is_deleted, date_deleted)**. +- Bảng post luôn luôn chỉ có tiếng Việt +- **Chỉ ADMIN** có thể thực hiện thay đổi bài viết. +- **Người dùng không có quyền ADMIN không thể thực hiện bất kỳ thay đổi nào** ngoài việc xem bài viết. +""" diff --git a/function/prompt/table/product.py b/function/prompt/table/product.py new file mode 100644 index 0000000000000000000000000000000000000000..58b9d9431cfcee765fc9feaaa16e705e7292bdc3 --- /dev/null +++ b/function/prompt/table/product.py @@ -0,0 +1,50 @@ +prompt_product_management = """ +- Khi câu hỏi về tiềm kiếm sản phẩm gì đó thì phải dùng kiểu LIKE N' tìm kiểm chu không phải tên sản phẩm = , vui lòng kèm theo proId trả về cho mình +### 1. Quyền hạn theo vai trò: + +#### A. ADMIN (Quản trị viên) +- **Thêm (INSERT) hoặc Cập nhật (UPDATE) sản phẩm** + - Ngày cập nhật hay ngày thêm sản phẩm luôn luôn là thời gian hiện tại. + - Câu lệnh Insert hoặc Update phải xác định rõ cate_id bởi vì đây là trường quan trọng. Ví dụ sản phẩm bubu thúi 4 thì 4 ở đây là tên sản phẩm chứ không phải là cate_id. + - Được phép thêm hoặc cập nhật dữ liệu. + - **Cập nhật giá** phải được ghi lại vào lịch sử giá. + - **Không được phép xóa bản ghi.** + - **Không được phép thêm hoặc cập nhật bản ghi khác ngoài sản phẩm.** + - Luôn đảm bảo tuân thủ các điều trên mình đã nói bao gồm quá trình kiểm tra. + - Nếu thực hiện thao tác không hợp lệ, hệ thống **trả về lỗi ngay lập tức** và **không thực thi truy vấn SQL**. + +##### Quy trình thêm sản phẩm (INSERT): (Luôn luôn phải đảm bảo không được thiếu sót các công đoạn này) +**Luôn đảm bảo quy trình thêm sản phẩm phải tuân thủ các bước sau:** +- - Ngày cập nhật hay ngày thêm sản phẩm luôn luôn là thời gian hiện tại. +- Câu lệnh Insert hoặc Update phải xác định rõ cate_id bởi vì đây là trường quan trọng. Ví dụ sản phẩm bubu thúi 4 thì 4 ở đây là tên sản phẩm chứ không phải là cate_id. +1. **Chuyển đổi tên sản phẩm (`pro_name`) sang tiếng Việt trước khi lưu vào bảng `product`.** +2. **Kiểm tra trùng lặp tên sản phẩm trước khi chèn:** + - Nếu ngôn ngữ hiện tại là **tiếng Việt**, kiểm tra `pro_name` trực tiếp trong bảng `product`. + - Nếu ngôn ngữ hiện tại là **tiếng Anh**, trước tiên **dịch `pro_name` sang tiếng Việt** rồi kiểm tra trong bảng `product`. +3. **Chèn sản phẩm theo ngôn ngữ hiện tại:** + - Nếu ngôn ngữ hiện tại là **tiếng Việt**, chèn sản phẩm với `pro_name` và `description` bằng tiếng Việt. + - Sau khi chèn, tạo bản dịch sản phẩm với `pro_name` và `description` bằng tiếng Anh. + - Nếu ngôn ngữ hiện tại là **tiếng Anh**, chèn sản phẩm với `pro_name` và `description` bằng tiếng Việt. + - Sau khi chèn, tạo bản dịch sản phẩm với `pro_name` và `description` được dịch lại sang tiếng Anh. + +##### Quy trình cập nhật sản phẩm (UPDATE):(Luôn luôn phải đảm bảo không được thiếu sót các công đoạn này) +**Luôn đảm bảo quy trình thêm sản phẩm phải tuân thủ các bước sau:** +- Ngày cập nhật hay ngày thêm sản phẩm luôn luôn là thời gian hiện tại. +1. **Đảm bảo không có bản ghi khác (ngoại trừ bản ghi hiện tại) có cùng tên sản phẩm đã dịch.** + - Nếu phát hiện trùng lặp, hệ thống trả về cảnh báo **DUPLICATE_NAME** và không thực hiện cập nhật. +2. **Cập nhật sản phẩm theo ngôn ngữ hiện tại:** + - Nếu ngôn ngữ hiện tại là **tiếng Việt**, cập nhật sản phẩm với `pro_name` và `description` bằng tiếng Việt. + - Sau khi cập nhật, cập nhật bản dịch sản phẩm với `pro_name` và `description` bằng tiếng Anh. + - Nếu ngôn ngữ hiện tại là **tiếng Anh**, cập nhật sản phẩm với `pro_name` và `description` bằng tiếng Việt. + - Sau khi cập nhật, cập nhật bản dịch sản phẩm với `pro_name` và `description` được dịch lại sang tiếng Anh. + +--- + +#### B. CUSTOMER & SHIPPER (Người dùng & Shipper) +- **Chỉ có quyền xem sản phẩm và giá.** +- **Không được phép thêm (INSERT) hoặc cập nhật (UPDATE) bản ghi.** +- Nếu cố gắng thực hiện thao tác bị cấm, hệ thống trả về lỗi: + **"ERROR: Unauthorized modification of another user's information."** +- Khi hỏi sản phẩm luôn luôn tối ưu truy vấn để có thể kèm theo product_variants chi tiết của sản phẩm đó như giá size, số lượng, kích cỡ, hình ảnh. +- Các câu hỏi product luôn quan trong hình ảnh(Nếu có thể kèm theo để minh họa một cách rõ ràng, trực quan). +""" \ No newline at end of file diff --git a/function/prompt/table/review.py b/function/prompt/table/review.py new file mode 100644 index 0000000000000000000000000000000000000000..e302b06de1fbae0b77505c585489fa4503f5e71b --- /dev/null +++ b/function/prompt/table/review.py @@ -0,0 +1,42 @@ +prompt_review = """ +# **Quy Định Về Hệ Thống Đánh Giá (Review Management Policy)** + +## **1. Quyền Xem Đánh Giá** +- **Mọi người dùng đều có quyền xem đánh giá (SELECT) của một sản phẩm cụ thể.** +- Đánh giá sản phẩm là dữ liệu công khai, không bị giới hạn quyền truy cập. + +## **2. Quyền Quản Lý Đánh Giá** +- **Người dùng chỉ được phép tạo đánh giá cho sản phẩm mà họ đã mua hoặc sử dụng.** +- Sau khi đánh giá được tạo, **không ai có quyền xóa đánh giá khỏi hệ thống**, kể cả ADMIN. +- **Không được phép thực hiện xóa vĩnh viễn (`DELETE`).** + +## **3. Quy Tắc Chỉnh Sửa Đánh Giá** +- Người dùng **chỉ có thể chỉnh sửa (UPDATE) đánh giá của chính họ**. +- Khi cập nhật đánh giá, hệ thống phải kiểm tra `user_id` của người yêu cầu có khớp với `user_id` của đánh giá không. +- **Không được phép sử dụng email hoặc số điện thoại của người khác để truy vấn hoặc chỉnh sửa đánh giá.** +- Hệ thống phải đối chiếu email hoặc số điện thoại với thông tin đã xác thực từ `user_id`. Nếu không khớp, từ chối cập nhật ngay lập tức. +- Nếu không khớp `user_id`, hệ thống **phải từ chối cập nhật ngay lập tức** với lỗi: + - **"ERROR: Unauthorized modification of another user's review."** +- ADMIN **có thể duyệt, ẩn hoặc hiển thị đánh giá**, nhưng không thể sửa đổi nội dung đánh giá. +- Hệ thống sẽ ghi lại mọi thay đổi đánh giá thông qua `updated_at` để bảo đảm tính minh bạch. + +## **4. Ngăn Chặn Hành Vi Gian Lận** +- Hệ thống sẽ kiểm tra để đảm bảo rằng **người dùng không thể đánh giá sản phẩm nhiều lần một cách bất hợp pháp**. +- Nếu phát hiện hành vi gian lận, hệ thống sẽ **từ chối thao tác và đưa ra cảnh báo phù hợp**. + +## **5. Xác Thực Danh Tính Trước Khi Xử Lý Yêu Cầu** +- **Quy tắc xác thực:** + - Nếu vai trò là **ADMIN**, hệ thống cấp quyền truy cập ngay lập tức. + - Nếu vai trò là **CUSTOMER hoặc SHIPPER**, hệ thống phải **xác minh rằng thông tin yêu cầu thuộc về chính người dùng đang xác thực** (dựa trên `user_id`). + +- **Truy vấn thông tin cá nhân qua email hoặc số điện thoại:** + - Hệ thống **phải xác minh email hoặc số điện thoại khớp với tài khoản của user_id đã xác thực**. + - Nếu không khớp, hệ thống **ngay lập tức từ chối truy vấn** với lỗi: + - **"ERROR: Unauthorized access to another user's information."** + + +## **5. Mục Tiêu** +- **Đảm bảo tính minh bạch và trung thực của hệ thống đánh giá.** +- **Ngăn chặn các hành vi xóa hoặc thay đổi đánh giá một cách không hợp lệ.** + +""" diff --git a/function/prompt/table/shipment.py b/function/prompt/table/shipment.py new file mode 100644 index 0000000000000000000000000000000000000000..65bce75dbcaa1bc237e9a8a81a61c99241787e69 --- /dev/null +++ b/function/prompt/table/shipment.py @@ -0,0 +1,55 @@ +prompt_shipment = """ +###Quản lý Shipment + +#### 1. **Quyền hạn của Role Shipper** +- **Shippers chỉ có thể cập nhật trạng thái của shipment trong các điều kiện sau:** + + 1. **Cập nhật trạng thái thành `SUCCESS`:** + - Khi cập nhật shipment thành `SUCCESS`, kiểm tra phương thức thanh toán liên quan: + - Nếu phương thức thanh toán là `CASH`, cập nhật trạng thái thanh toán thành `COMPLETED`. + - ⚠️ **Lưu ý:** Không được phép thay đổi trạng thái shipment nếu nó đã ở `CANCELLED`, `SUCCESS`, hoặc `SHIPPING`. + + 2. **Cập nhật trạng thái thành `CANCELLED`:** + - Khi cập nhật shipment thành `CANCELLED`: + - Cập nhật trường `datecancel` của bảng `Shipment` với timestamp hiện tại. + - Kiểm tra phương thức thanh toán liên quan: + - Nếu phương thức là `CASH`, cập nhật trạng thái thanh toán thành `FAILED`. + - Nếu phương thức không phải `CASH`, cập nhật trạng thái thanh toán thành `REFUND`. + - Cập nhật bảng `Order`: + - Đặt trạng thái đơn hàng thành `CANCELLED`. + - Cập nhật `cancel_date` của đơn hàng với timestamp hiện tại. + + 3. **Giới hạn trạng thái:** + - Khi một shipment đã có trạng thái `CANCELLED`, `SUCCESS`, hoặc `SHIPPING`, không được phép thay đổi trạng thái đó. + - Nếu vi phạm, trả về lỗi: + ``` + "ERROR: Shipment status cannot be reverted once set to SUCCESS, CANCELLED, or SHIPPING." + ``` + + 4. **Chỉ có thể cập nhật shipment mà shipper sở hữu:** + - Nếu shipper cố gắng cập nhật shipment không thuộc về họ, hệ thống phải từ chối và trả về lỗi: + ``` + "ERROR: Unauthorized to update shipment status for this shipment." + ``` + +--- + +#### 2. **Quyền hạn của các vai trò khác (ADMIN, CUSTOMER)** +- **Admin và CUSTOMER KHÔNG có quyền cập nhật shipment.** +- Nếu bất kỳ vai trò nào ngoài **SHIPPER** cố gắng cập nhật shipment, hệ thống phải ngay lập tức từ chối truy vấn với lỗi: "ERROR: Role not authorized to update shipment status." + +--- + +#### 3. **Quy tắc chung về Shipment** +- **KHÔNG được phép thực hiện `INSERT` hoặc `DELETE` trên bảng Shipment.** +- Chỉ các truy vấn `SELECT` và `JOIN` được phép đối với các vai trò khác ngoài Shipper. +- **Nếu có nỗ lực không hợp lệ, hệ thống phải từ chối ngay lập tức.** + +--- + +🚨 **Lưu ý quan trọng:** +- **SHIPPER chỉ có thể cập nhật trạng thái trong các điều kiện hợp lệ**. +- **Cập nhật trạng thái phải tuân thủ quy tắc thanh toán và đơn hàng**. +- **Hệ thống phải từ chối ngay lập tức nếu vi phạm quyền hạn(Câu query phải kiểm tra thật kĩ càng)**. + +""" \ No newline at end of file diff --git a/function/prompt/table/shipper_attendance.py b/function/prompt/table/shipper_attendance.py new file mode 100644 index 0000000000000000000000000000000000000000..63ed780b45e55c268eae65c99217890284a84b96 --- /dev/null +++ b/function/prompt/table/shipper_attendance.py @@ -0,0 +1,11 @@ +prompt_shipper_attendance_management = """ + +#### A. Quyền hạn và quy tắc hoạt động của bảng Shipper Attendance: +- **CUSTOMER/ADMIN Role:** + - Không có quyền thao tác với bảng này kể cả xem. +- **SHIPPER Roles:** + - Chỉ được phép xem không được thực hiện thêm xóa, sửa + + *** Chỉ được phép dùng SELECT + +""" \ No newline at end of file diff --git a/function/prompt/table/shipper_commission_detail.py b/function/prompt/table/shipper_commission_detail.py new file mode 100644 index 0000000000000000000000000000000000000000..c1781127a4a2855b161c50726462518ec9af9ac6 --- /dev/null +++ b/function/prompt/table/shipper_commission_detail.py @@ -0,0 +1,13 @@ +prompt_commission_management = """ + +#### A. Quyền hạn và quy tắc hoạt động trong bảng Shipper Comisson: + +- **CUSTOMER/ADMIN Role:** + - Không có quyền thao tác với bảng này kể cả xem. +- **SHIPPER Roles:** + - Chỉ được phép xem không được thực hiện thêm xóa, sửa + - Không được phép xóa bảng ghi + + *** Chỉ được phép dùng SELECT + +""" \ No newline at end of file diff --git a/function/prompt/table/shipper_salary_summary.py b/function/prompt/table/shipper_salary_summary.py new file mode 100644 index 0000000000000000000000000000000000000000..f768f3ba5285b08f7a1f11b2233ff3933434b658 --- /dev/null +++ b/function/prompt/table/shipper_salary_summary.py @@ -0,0 +1,12 @@ +prompt_commission_management = """ + +#### A. Quyền hạn và quy tắc hoạt động của bảng Shipper Salary Summary: +- **CUSTOMER/ADMIN Role:** + - Không có quyền thao tác với bảng này kể cả xem. +- **SHIPPER Roles:** + - Chỉ được phép xem không được thực hiện thêm xóa, sửa + + + *** Chỉ được phép dùng SELECT + +""" \ No newline at end of file diff --git a/function/prompt/table/user_coin.py b/function/prompt/table/user_coin.py new file mode 100644 index 0000000000000000000000000000000000000000..1ea2e417dd8bd9b8081599061481d46832cd7625 --- /dev/null +++ b/function/prompt/table/user_coin.py @@ -0,0 +1,45 @@ +prompt_user_coint = """ +# **Quản Lý Người Dùng & Quyền Hạn Theo Vai Trò (User Management & Role-Based Access Control)** + +## **1. Quyền Hạn Theo Vai Trò** + +### **A. ADMIN (Quản trị viên)** +- **Có toàn quyền xem (SELECT), cập nhật (UPDATE), và xóa (DELETE) user_coin của người dùng**. +- Có thể truy vấn thông tin của bất kỳ người dùng nào mà không cần xác thực thêm. +- Khi xóa user_coin người dùng, không được xóa vĩnh viễn (`DELETE`), mà phải thực hiện xóa logic: + - `is_deleted = TRUE` + - `date_deleted = CURRENT_TIMESTAMP` +- Khi khôi phục người dùng, cập nhật: + - `is_deleted = FALSE` + - `date_deleted = NULL` + +### **B. CUSTOMER & SHIPPER** +- **Chỉ được phép xem thông tin của chính họ.** +- Nếu cố gắng truy vấn thông tin của người khác bằng bất kỳ phương thức nào, hệ thống sẽ **ngay lập tức từ chối truy vấn** với lỗi: + - **"ERROR: Unauthorized access to another user's information."** +- **Không được phép cập nhật (UPDATE) hoặc xóa (DELETE) người dùng khác.** + +## **2. Xác Thực Danh Tính Khi Xử Lý Yêu Cầu** +- **Quy tắc xác thực:** + - Nếu là **ADMIN**, truy cập được cấp ngay lập tức. + - Nếu là **CUSTOMER hoặc SHIPPER**, hệ thống phải xác minh rằng thông tin yêu cầu thuộc về **chính họ** (dựa trên `user_id`). +- **Truy vấn qua email hoặc số điện thoại:** + - Hệ thống **phải kiểm tra email hoặc số điện thoại có khớp với tài khoản của user_id đã xác thực không**. + - Nếu không khớp, hệ thống ngay lập tức từ chối với lỗi: + - **"ERROR: Unauthorized access to another user's information."** + +## **3. Ngăn Chặn Lách Luật Bằng Email Hoặc Số Điện Thoại** +- Chỉ áp dụng kiểm tra này khi vai trò là **CUSTOMER hoặc SHIPPER**. +- Người dùng **không được phép yêu cầu thông tin của người khác bằng cách cung cấp email hoặc số điện thoại không thuộc về họ**. +- Hệ thống **phải so sánh email hoặc số điện thoại yêu cầu với dữ liệu của user_id đã xác thực**. Nếu không khớp, hệ thống sẽ **ngay lập tức chặn truy vấn**. + +## **4. Hành Động Bị Cấm** +- **CUSTOMER & SHIPPER không được phép cập nhật hoặc xóa thông tin người dùng khác.** +- **Không cho phép xóa vĩnh viễn (`DELETE`) dữ liệu người dùng**. Chỉ áp dụng xóa logic (`is_deleted = TRUE`). +- **Mọi hành vi truy vấn trái phép đều bị phát hiện và chặn ngay lập tức trước khi thực thi truy vấn SQL.** + +## **5. Mục Tiêu** +- **Bảo vệ quyền riêng tư và tính toàn vẹn dữ liệu của người dùng.** +-** Truy vấn được số coint sao cho phì hợp với vai trò của người dùng.**(Bao gồm việc đã áp dụng tránh lách luật) +- **Mọi hành vi truy vấn trái phép, kể cả cố ý vượt qua quy trình xác thực, phải bị phát hiện và ngăn chặn ngay lập tức.** +""" diff --git a/function/prompt/table/user_voucher.py b/function/prompt/table/user_voucher.py new file mode 100644 index 0000000000000000000000000000000000000000..d14a4a8a347f079eea07266a9811b7d9546a7feb --- /dev/null +++ b/function/prompt/table/user_voucher.py @@ -0,0 +1,74 @@ +prompt_user_voucher = """ +# **Quản Lý User Voucher** + +## **1. Quyền Hạn Theo Vai Trò** + +### **A. CUSTOMER/SHIPPER** +- **Chỉ được phép truy vấn thông tin cá nhân của chính họ** (`name`, `phone`, `email`). +- **Cần xác thực danh tính trước khi truy vấn**: + - Nếu truy vấn bằng `email`: + ```sql + SELECT * FROM users WHERE email = provided_email AND user_id = current_user_id; + ``` + - Nếu truy vấn bằng `phone`: + ```sql + SELECT * FROM users WHERE phone = provided_phone AND user_id = current_user_id; + ``` + - Nếu xác thực thất bại, hệ thống trả về lỗi: + ``` + "ERROR: Unauthorized access to personal information." + ``` +- **Chỉ có thể xem và quản lý các voucher thuộc về họ** (`user_id`). +- **Không được phép truy vấn voucher của người khác.** Nếu vi phạm, hệ thống trả về lỗi: + ``` + "ERROR: User does not have permission to perform this action." + ``` + +--- + +### **B. ADMIN** +- Có toàn quyền **xem, chỉnh sửa, và quản lý** tất cả các bản ghi `user_voucher`. +- **Không có hạn chế nào về quyền truy cập**. + +--- + +## **2. Chính Sách Xóa Dữ Liệu (Logical Deletion)** +- Không cho phép xóa vĩnh viễn (`DELETE`). +- Xóa dữ liệu sử dụng xóa logic (`is_deleted = TRUE`). +- Khi xóa voucher: + ```sql + UPDATE user_voucher + SET is_deleted = TRUE, data_deleted = CURRENT_TIMESTAMP + WHERE id = voucher_id; + ``` +- Khi khôi phục voucher: + ```sql + UPDATE user_voucher + SET is_deleted = FALSE, data_deleted = NULL + WHERE id = voucher_id; + ``` +- Người dùng chỉ có thể xem các voucher chưa bị xóa: + ```sql + SELECT * FROM user_voucher WHERE user_id = current_user_id AND is_deleted = FALSE; + ``` + +--- + +## **3. Kiểm Tra Trước Khi Thao Tác** +### **A. Kiểm Tra Trước Khi Truy Vấn Voucher** +- Xác minh quyền truy cập dữ liệu cá nhân. +- Người dùng **chỉ có thể truy vấn voucher của chính họ** (`user_id`). +- Nếu cố truy vấn voucher của người khác, trả về lỗi ngay lập tức. + +### **B. Kiểm Tra Trước Khi Xóa Voucher** +- **Chỉ áp dụng xóa logic (`is_deleted = TRUE`).** +- **Người dùng không thể xóa voucher của người khác.** +- Nếu vi phạm, hệ thống **chặn thao tác ngay lập tức**. + +--- + +**🚨 Lưu ý quan trọng:** +- **Mọi vi phạm quyền truy cập đều bị chặn ngay lập tức trước khi thực thi truy vấn SQL.** +- **CUSTOMER/SHIPPER chỉ có thể thao tác với dữ liệu của họ, ADMIN có toàn quyền truy cập.** +- **Xóa dữ liệu luôn thực hiện bằng xóa logic để bảo toàn lịch sử sử dụng voucher.** +""" \ No newline at end of file diff --git a/function/prompt/table/users.py b/function/prompt/table/users.py new file mode 100644 index 0000000000000000000000000000000000000000..5e5999e8cd9a01e3baf48eb8ad6cb00396caf769 --- /dev/null +++ b/function/prompt/table/users.py @@ -0,0 +1,46 @@ +prompt_user = """ +# **Quản Lý Người Dùng Theo Vai Trò (User Management by Role)** + +## **1. Quyền Hạn Theo Vai Trò** + +### **A. ADMIN (Quản trị viên)** +- **Toàn quyền truy cập** vào thông tin của mọi người dùng mà không cần xác thực thêm. +- Được phép thực hiện **các truy vấn, cập nhật (UPDATE)** trên hồ sơ người dùng. +- **Không được phép xóa vĩnh viễn hồ sơ người dùng (`DELETE`).** + +### **B. CUSTOMER & SHIPPER (Khách hàng & Shipper)** +- **Chỉ được phép truy vấn thông tin cá nhân của chính họ**. +- Nếu cố gắng truy cập thông tin của người khác **bằng bất kỳ phương thức nào**, hệ thống sẽ **ngay lập tức từ chối truy vấn** và trả về lỗi: + - **"ERROR: Unauthorized access to another user's information."** + +## **2. Xác Thực Danh Tính Trước Khi Xử Lý Yêu Cầu** +- **Quy tắc xác thực:** + - Nếu vai trò là **ADMIN**, hệ thống cấp quyền truy cập ngay lập tức. + - Nếu vai trò là **CUSTOMER hoặc SHIPPER**, hệ thống phải **xác minh rằng thông tin yêu cầu thuộc về chính người dùng đang xác thực** (dựa trên `user_id`). Phải xác minh chính xác liệu là họ có hỏi thông tin của chính họ hay không. + - Nếu vai trò là **CUSTOMER hoặc SHIPPER** cố tình hỏi về user_id của nguoi khác thì phải cân nhac check user_id của họ + +- **Truy vấn thông tin cá nhân qua email hoặc số điện thoại:** + - Hệ thống **phải xác minh email hoặc số điện thoại khớp với tài khoản của user_id đã xác thực**. + - Nếu không khớp, hệ thống **ngay lập tức từ chối truy vấn** với lỗi: + - **"ERROR: Unauthorized access to another user's information."** + +## **3. Ngăn Chặn Lách Luật Bằng Email Hoặc Số Điện Thoại** +- Chỉ áp dụng kiểm tra này khi vai trò là **CUSTOMER hoặc SHIPPER**. +- Người dùng **không được phép yêu cầu thông tin của người khác bằng cách cung cấp email hoặc số điện thoại không thuộc về họ** để vượt qua xác thực. +- Hệ thống **phải đối chiếu email hoặc số điện thoại yêu cầu với dữ liệu đã xác thực**. Nếu không khớp, hệ thống **chặn ngay lập tức và trả về lỗi**: + - **"ERROR: Unauthorized access to another user's information."** + +## **4. Hành Động Bị Cấm** +- **Không cho phép xóa vĩnh viễn (`DELETE`) hồ sơ người dùng** dưới bất kỳ hình thức nào, kể cả ADMIN. +- Luôn luôn ghi chú: **Không cho bất kỳ ai yêu cầu xóa người dùng, khôi phục người dùng(kể cả dùng email, số điện thoại, địa chỉ, user_name, tài khoản,..) hoặc bất kỳ hành động nào liên quan đến việc xóa dữ liệu**. +- Tất cả truy vấn chỉ hiển thị dữ liệu chưa bị xóa (`is_deleted = FALSE`). + +## **5. Mục Tiêu** +- Quy tắc này đảm bảo **bảo vệ tuyệt đối quyền riêng tư của người dùng** và **tính toàn vẹn dữ liệu**. +- Mọi hành vi truy cập trái phép, kể cả cố ý vượt qua quy trình xác thực, **phải bị phát hiện và ngăn chặn ngay lập tức**. +- luôn luôn đảm bảo được bí mật về thông tin của user khác. Không được phép để người dùng Role CUSTOMER/SHIPPER xem thông tin của người khác. +- Vai trò ADMIN có thể xem thông tin của tất cả người dùng. Khi xem thông tin người dùng hãy bỏ qua thông tin của họ +- Luôn tối ưu truy vấn khi không để lộ user_id. +- Mọi hành động xóa Logic luôn luôn phải cấm xóa vĩnh viễn. +- Không được phép xóa thông tin của người khác. +""" \ No newline at end of file diff --git a/function/prompt/table/voucher.py b/function/prompt/table/voucher.py new file mode 100644 index 0000000000000000000000000000000000000000..82e5cbbf6d81fb43eb52285bdfb3fc95c48e7ffc --- /dev/null +++ b/function/prompt/table/voucher.py @@ -0,0 +1,40 @@ +prompt_voucher = """ + +Đây là bảng chứa các mã khuyến mãi của từng người dùng. + +- Gồm có các thuộc tính: + + voucher_id: mã Id của bảng voucher. Kiểu Integer + + key_voucher: mã khóa định danh cho từng voucher riêng biệt. + + number: số lượng voucher hiện có. Kiểu Interger + + start_date: Ngày bắt đầu của mã khuyến mãi. Kiểu dateTime + + end_date: Ngày kết thúc của mã khuyến mãi. + + status: Trạng thái của mã khuyến mãi(Là giá trị Enum gồm có: ACTIVE, EXPIRED, ) + + is_deleted: Kiểm tra xem voucher có bị xóa hay không. Kiểu giá Boolean + + date_deleted: Ngày xóa voucher. + + discount: Giá trị của khuyến mãi. Kiểu Double + + post_id: bài viết mà voucher đó liên quan + +- Các mối quan hệ: + + Khóa ngoại liên kết với bảng orders. + + Khóa ngoại liên kết với bảng user_voucher. + + +## **. Xác Thực Danh Tính Trước Khi Xử Lý Yêu Cầu** +- **Quy tắc xác thực:** + - Nếu vai trò là **ADMIN**, hệ thống cấp quyền truy cập ngay lập tức. + - Nếu vai trò là **CUSTOMER hoặc SHIPPER**, hệ thống phải **xác minh rằng thông tin yêu cầu thuộc về chính người dùng đang xác thực** (dựa trên `user_id`). + +- **Truy vấn thông tin cá nhân qua email hoặc số điện thoại:** + - Hệ thống **phải xác minh email hoặc số điện thoại khớp với tài khoản của user_id đã xác thực**. + - Nếu không khớp, hệ thống **ngay lập tức từ chối truy vấn** với lỗi: + - **"ERROR: Unauthorized access to another user's information."** + +- Quyền hạn sử dụng bảng này với các vai trò người dùng: + + Quan trong: Không vai trò nào được phép xóa vật lý dữ liệu trong bảng chỉ được phép xóa mềm(chỉ cập nhật is_deleted và date_deleted) và với vai trò ADMIN. + + vai trò ADMIN: Có quyền xem tất cả voucher hiện có, Có quyền cập nhật các bản ghi Voucher. + .Nếu cập nhật trạng thái thì phải chỉnh sửa trạng thái của các người dùng sử dụng chi tiết gồm có: + + Bước 1: Bạn xem là liệu có ai đã dùng voucher đó chưa. Nếu cập nhật về EXPIRED thì nên cập nhật lại cho các user_voucher đi theo. + + Bước 2: Nếu trạng thái cập nhật về ACTIVATE thì bạn phải xem liệu có người dùng nào đã dùng voucher đó cho Orders hay không nếu họ chưa dùng thì ta cập nhật trạng thái sử dụng của user_voucher. + + Vai trò CUSTOMER,SHIPPER: Có quyền xem voucher, tìm hiểu các voucher còn tồn tại nhưng không được quyền thao tác gồm có UPDATE, DELETE, CREATE. + +""" \ No newline at end of file diff --git a/function/recommender/__init__.py b/function/recommender/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/function/recommender/__pycache__/__init__.cpython-311.pyc b/function/recommender/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..417a9f1638c0921c7f6c72854a7674f2e964d14f Binary files /dev/null and b/function/recommender/__pycache__/__init__.cpython-311.pyc differ diff --git a/function/recommender/__pycache__/__init__.cpython-312.pyc b/function/recommender/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5da0eb715d195024e25a38caaa8c1c3cd8824d21 Binary files /dev/null and b/function/recommender/__pycache__/__init__.cpython-312.pyc differ diff --git a/function/recommender/__pycache__/get_data.cpython-311.pyc b/function/recommender/__pycache__/get_data.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e867f599b23d3f4bef35b96f2f0c8ffe6c79a93 Binary files /dev/null and b/function/recommender/__pycache__/get_data.cpython-311.pyc differ diff --git a/function/recommender/__pycache__/get_data.cpython-312.pyc b/function/recommender/__pycache__/get_data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d509e35cc4d5f32300418e02f80e1911ca19c16a Binary files /dev/null and b/function/recommender/__pycache__/get_data.cpython-312.pyc differ diff --git a/function/recommender/__pycache__/recommend.cpython-311.pyc b/function/recommender/__pycache__/recommend.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01ee3480970a98ebf01a6ab1653221713764722b Binary files /dev/null and b/function/recommender/__pycache__/recommend.cpython-311.pyc differ diff --git a/function/recommender/__pycache__/recommend.cpython-312.pyc b/function/recommender/__pycache__/recommend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8dc9d0b974690c8fdc05f21bd34bbc4ee63ba8cc Binary files /dev/null and b/function/recommender/__pycache__/recommend.cpython-312.pyc differ diff --git a/function/recommender/get_data.py b/function/recommender/get_data.py new file mode 100644 index 0000000000000000000000000000000000000000..5f0dd2500526b05786dc18c6595e1769dafd5e12 --- /dev/null +++ b/function/recommender/get_data.py @@ -0,0 +1,128 @@ +import pymysql +import pandas as pd +from sqlalchemy import create_engine +import os +from dotenv import load_dotenv + +from dotenv import load_dotenv, find_dotenv + +# Tự động tìm file .env gần nhất trong cây thư mục cha +load_dotenv(find_dotenv(), override=True) + + +DB_HOST = os.getenv("DB_HOST") +DB_USER = os.getenv("DB_USER") +DB_PASSWORD = os.getenv("DB_PASSWORD") +DB_NAME = os.getenv("DB_NAME") +DB_PORT = os.getenv("DB_PORT") + +# Tạo connection string +import os +from urllib.parse import quote + +password = os.getenv("DB_PASSWORD") +DB_PASSWORD = quote(password) +# Tạo connection string +connection_uri = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" +# db_url = "mysql+pymysql://root@127.0.0.1:3306/demohmdrinks" +db_url = connection_uri +engine = create_engine(db_url) + + +# # Kết nối đến MySQL +# conn = pymysql.connect( +# host="localhost", +# user="root", +# password="Yahana0509@", +# database="demohmdrinks", +# port=4000, +# charset="utf8mb4" +# ) + + +async def get_data_recommend(): + #Lây danh sách yêu thích + query_favourite_items = """ +SELECT f.user_id, fi.pro_id +FROM favourite_item fi +JOIN favourite f ON fi.fav_id = f.fav_id +JOIN product p ON fi.pro_id = p.pro_id +WHERE p.is_deleted = FALSE; +""" + df_favourite_items = pd.read_sql(query_favourite_items, con=engine) + favourite_items_data = { + "user_id": df_favourite_items["user_id"].tolist(), + "product_id": df_favourite_items["pro_id"].tolist() +} + + #Lấy danh sách Rating + query_rating = """ SELECT re.user_id, re.pro_id, re.rating_star + FROM review re + JOIN product p ON re.pro_id = p.pro_id + WHERE p.is_deleted = FALSE AND re.is_deleted = FALSE ;""" + df_rating = pd.read_sql(query_rating, con=engine) + reviews_data = { + "user_id": df_rating["user_id"].tolist(), + "product_id": df_rating["pro_id"].tolist(), + "rating": df_rating["rating_star"].tolist() +} + + #Lấy danh sách sản phẩm + query_products = """ + SELECT p.pro_id, p.pro_name, c.cate_name AS category + FROM product p + JOIN category c ON p.category_id = c.cate_id + WHERE p.is_deleted = FALSE; +""" + df_products = pd.read_sql(query_products, con=engine) + products_data = { + "product_id": df_products["pro_id"].tolist(), + "name": df_products["pro_name"].tolist(), + "category": df_products["category"].tolist() +} + + #Lấy lịch sử mua hàng + query_item_cart_new = """ +SELECT c.user_id, ci.pro_id +FROM cart c +JOIN cart_item ci ON c.cart_id = ci.cart_id +JOIN product p ON ci.pro_id = p.pro_id +WHERE + c.status = 'NEW' + AND ci.is_deleted = FALSE + AND p.is_deleted = FALSE; + """ + df_cart_new_items= pd.read_sql(query_item_cart_new, con=engine) + cart_new_items_data = { + "user_id": df_cart_new_items["user_id"].tolist(), + "product_id": df_cart_new_items["pro_id"].tolist() +} + + query_history = """ +SELECT o.user_id, ci.pro_id AS product_id +FROM `orders` o +JOIN order_item oi ON o.order_id = oi.order_id +JOIN cart c ON oi.cart_id = c.cart_id +JOIN cart_item ci ON c.cart_id = ci.cart_id +JOIN product pro ON pro.pro_id = ci.pro_id +JOIN payments p ON o.order_id = p.order_id +WHERE o.status = 'CONFIRMED' + AND p.status = 'SUCCESS' + AND pro.is_deleted = FALSE + +UNION ALL + +SELECT c.user_id, ci.pro_id AS product_id +FROM cart c +JOIN cart_item ci ON c.cart_id = ci.cart_id +ORDER BY user_id, product_id; +""" + +# Đọc dữ liệu vào DataFrame + df_history = pd.read_sql(query_history, con=engine) + order_history_data = { + "user_id": df_history["user_id"].tolist(), + "product_id": df_history["product_id"].tolist() +} + + return favourite_items_data,reviews_data,products_data,order_history_data,cart_new_items_data \ No newline at end of file diff --git a/function/recommender/recommend.py b/function/recommender/recommend.py new file mode 100644 index 0000000000000000000000000000000000000000..17953e00a99040b805fa9a78a74f51fdf5e8b56f --- /dev/null +++ b/function/recommender/recommend.py @@ -0,0 +1,186 @@ +import numpy as np +import pandas as pd +from sklearn.feature_extraction.text import TfidfVectorizer +from sklearn.metrics.pairwise import cosine_similarity +from scipy.sparse.linalg import svds +from sklearn.preprocessing import MinMaxScaler +import random +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) +import response.ResponseRecommender as res_rcm +from function.recommender import get_data + + +class HybridRecommender: + def __init__(self, cosine_sim, products, reviews, favourite_items, order_history, new_item_cart, + num_factors=3, regularization=0.02): + self.cosine_sim = cosine_sim + self.products = products + self.reviews = reviews + self.favourite_items = favourite_items + self.order_history = order_history + self.new_item_cart = new_item_cart + self.num_factors = num_factors + self.regularization = regularization + + self.user_ids = reviews["user_id"].unique() + self.product_ids = products["product_id"].unique() + self.user_to_index = {uid: i for i, uid in enumerate(self.user_ids)} + self.product_to_index = {pid: i for i, pid in enumerate(self.product_ids)} + self.index_to_product = {i: pid for pid, i in self.product_to_index.items()} + + self.ratings_matrix = self._create_ratings_matrix() + self.global_mean = self.reviews['rating'].mean() + + def _create_ratings_matrix(self): + matrix = np.zeros((len(self.user_ids), len(self.product_ids))) + for _, row in self.reviews.iterrows(): + u_idx = self.user_to_index[row["user_id"]] + p_idx = self.product_to_index[row["product_id"]] + matrix[u_idx, p_idx] = row["rating"] + return matrix + + def train_svd(self): + ratings_filled = np.where(self.ratings_matrix == 0, self.global_mean, self.ratings_matrix) + U, sigma, Vt = svds(ratings_filled, k=self.num_factors) + sigma = np.diag(sigma) + self.predicted_ratings = np.dot(np.dot(U, sigma), Vt) + scaler = MinMaxScaler(feature_range=(1, 5)) + self.predicted_ratings = scaler.fit_transform( + self.predicted_ratings.reshape(-1, 1) + ).reshape(self.ratings_matrix.shape) + + def get_content_score(self, user_id, top_n=3): + fav_items = self.favourite_items[self.favourite_items['user_id'] == user_id]['product_id'] + past_orders = self.order_history[self.order_history['user_id'] == user_id]['product_id'] + cart_items = self.new_item_cart[self.new_item_cart['user_id'] == user_id]['product_id'] + + relevant_items = set(fav_items).union(set(past_orders)).union(set(cart_items)) + content_scores = {} + for item in relevant_items: + if item in self.product_to_index: + idx = self.product_to_index[item] + sim_scores = enumerate(self.cosine_sim[idx]) + top_similar = sorted(sim_scores, key=lambda x: x[1], reverse=True)[:top_n] + for sim_idx, score in top_similar: + prod_id = self.index_to_product[sim_idx] + content_scores[prod_id] = content_scores.get(prod_id, 0) + score + + return content_scores + + def hybrid_recommend(self, user_id, top_n=10, weights={'collab': 0.2, 'content': 0.1, 'history': 0.7}, randomness=0.1): + if user_id not in self.user_to_index: + print(f"User {user_id} chưa có đánh giá nào. Kiểm tra dữ liệu khác...") + content_scores = self.get_content_score(user_id) + recommendations = sorted(content_scores.items(), key=lambda x: x[1], reverse=True) + + # Nếu chưa đủ sản phẩm thì lấy thêm từ cold-start (sản phẩm phổ biến) + if len(recommendations) < top_n: + popular_items = self.reviews.groupby('product_id')['rating'].mean().sort_values(ascending=False) + popular_items = popular_items.loc[~popular_items.index.isin(content_scores.keys())] + extra_items = list(popular_items.head(top_n - len(recommendations)).items()) + recommendations.extend(extra_items) + + return recommendations[:top_n] + + user_idx = self.user_to_index[user_id] + + collab_scores = dict(enumerate(self.predicted_ratings[user_idx])) + collab_scores = {self.index_to_product[i]: s for i, s in collab_scores.items()} + + content_scores = self.get_content_score(user_id) + + history_scores = {} + user_history = self.order_history[self.order_history['user_id'] == user_id] + for _, row in user_history.iterrows(): + history_scores[row['product_id']] = row['time_weight'] + + final_scores = {} + for prod_id in self.product_ids: + final_score = 0 + if prod_id in collab_scores: + final_score += weights['collab'] * collab_scores[prod_id] + if prod_id in content_scores: + final_score += weights['content'] * content_scores[prod_id] + if prod_id in history_scores: + final_score += weights['history'] * history_scores[prod_id] + if final_score > 0: + noise = random.uniform(-randomness, randomness) * final_score + final_scores[prod_id] = final_score + noise + + purchased = set(self.order_history[self.order_history['user_id'] == user_id]['product_id']) + final_scores = {k: v for k, v in final_scores.items() if k not in purchased} + + top_candidates = sorted(final_scores.items(), key=lambda x: x[1], reverse=True) + + if len(top_candidates) < top_n: + popular_items = self.reviews.groupby('product_id')['rating'].mean().sort_values(ascending=False) + popular_items = popular_items.loc[~popular_items.index.isin(final_scores.keys())] + extra_items = list(popular_items.head(top_n - len(top_candidates)).items()) + top_candidates.extend(extra_items) + + return sorted(top_candidates[:top_n], key=lambda x: x[1], reverse=True) + +async def recommend(user_id: int, number: int): + data = await get_data.get_data_recommend() + products = pd.DataFrame(list(data)[2]) + reviews = pd.DataFrame(list(data)[1]) + favourite_items = pd.DataFrame(list(data)[0]) + order_history = pd.DataFrame(list(data)[3]) + new_item_cart = pd.DataFrame(list(data)[4]) + + products['description'] = products['name'] + ' ' + products['category'] + products['description'] = products['description'].str.lower() + + tfidf = TfidfVectorizer(stop_words='english', ngram_range=(1, 2), max_features=1000) + tfidf_matrix = tfidf.fit_transform(products['description'].fillna('')) + cosine_sim = cosine_similarity(tfidf_matrix, tfidf_matrix) + + recommender = HybridRecommender(cosine_sim, products, reviews, favourite_items, order_history, new_item_cart) + recommender.train_svd() + + recommendations = recommender.hybrid_recommend(user_id, top_n=number) + items = [res_rcm.ItemRecommend(pro_id=pid, product_name=products.loc[products['product_id'] == pid, 'name'].iloc[0]) + for pid, _ in recommendations] + + return res_rcm.ListItemRecommend(user_id=user_id, total=len(items), list_item=items) + + + + + + +async def recommend(user_id:int, number:int): + data = await get_data.get_data_recommend() + products = pd.DataFrame(list(data)[2]) + reviews = pd.DataFrame(list(data)[1]) + favourite_items = pd.DataFrame(list(data)[0]) + order_history = pd.DataFrame(list(data)[3]) + new_item_cart = pd.DataFrame(list(data)[4]) + products['description'] = products['name'] + ' ' + products['category'] + products['description'] = products['description'].str.lower() +# Thêm trọng số thời gian cho lịch sử mua hàng + order_history['timestamp'] = pd.date_range(start='2024-10-01', periods=len(order_history), freq='D') + order_history['time_weight'] = 1 - (pd.Timestamp.now() - order_history['timestamp']).dt.days / 365 +# Content-Based Filtering cải tiến + tfidf = TfidfVectorizer(stop_words='english', ngram_range=(1, 2), max_features=1000) + tfidf_matrix = tfidf.fit_transform(products['description'].fillna('')) + cosine_sim = cosine_similarity(tfidf_matrix, tfidf_matrix) + + + recommender = HybridRecommender(cosine_sim,products, reviews, favourite_items, order_history,new_item_cart) + recommender.train_svd() + recommendations = recommender.hybrid_recommend(user_id, top_n=number, randomness=0.1) + items = [ + res_rcm.ItemRecommend( + pro_id=product_id, + product_name=products.loc[products['product_id'] == product_id, 'name'].iloc[0] + ) + for product_id, _ in recommendations +] + + return res_rcm.ListItemRecommend(user_id=user_id, total=len(items), list_item=items) +if __name__ == "__main__": + import asyncio + print(asyncio.run(recommend(4,10))) \ No newline at end of file diff --git a/function/recommender/test/recommend.ipynb b/function/recommender/test/recommend.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..895e454c4135e2539508055bfedb2eb90db00bc2 --- /dev/null +++ b/function/recommender/test/recommend.ipynb @@ -0,0 +1,660 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " user_id pro_id\n", + "0 3 2\n", + "1 3 1\n", + "2 3 13\n", + "3 3 13\n", + "4 3 13\n", + "5 3 13\n", + "6 3 13\n", + "7 3 7\n", + "8 3 12\n", + "9 3 4\n", + "10 7 5\n", + "11 7 12\n", + "12 8 7\n", + "13 9 2\n", + "14 9 1\n", + "15 9 4\n", + "16 9 14\n", + "17 10 5\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\vonhu\\AppData\\Local\\Temp\\ipykernel_5672\\2900762178.py:20: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n", + " df_favourite_items = pd.read_sql(query, conn)\n" + ] + } + ], + "source": [ + "import pymysql\n", + "import pandas as pd\n", + "\n", + "# Kết nối đến MySQL\n", + "conn = pymysql.connect(\n", + " host=\"localhost\",\n", + " user=\"root\",\n", + " password=\"Yahana0509@\",\n", + " database=\"demohmdrinks\",\n", + " port=4000,\n", + " charset=\"utf8mb4\"\n", + ")\n", + "\n", + "# Truy vấn dữ liệu\n", + "query = \"\"\"\n", + "SELECT f.user_id, fi.pro_id\n", + "FROM favourite_item fi\n", + "JOIN favourite f ON fi.fav_id = f.fav_id;\n", + "\"\"\"\n", + "df_favourite_items = pd.read_sql(query, conn)\n", + "\n", + "# Đóng kết nối\n", + "conn.close()\n", + "\n", + "# Xem trước dữ liệu\n", + "print(df_favourite_items)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{3: [2, 1, 13, 13, 13, 13, 13, 7, 12, 4], 7: [5, 12], 8: [7], 9: [2, 1, 4, 14], 10: [5]}\n" + ] + } + ], + "source": [ + "# Chuyển DataFrame thành dictionary dạng mong muốn\n", + "favourite_items_data = df_favourite_items.groupby(\"user_id\")[\"pro_id\"].apply(list).to_dict()\n", + "\n", + "print(favourite_items_data)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'user_id': [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 7, 8, 9, 9, 9, 9, 10], 'product_id': [2, 1, 13, 13, 13, 13, 13, 7, 12, 4, 5, 12, 7, 2, 1, 4, 14, 5]}\n" + ] + } + ], + "source": [ + "favourite_items_data = {\n", + " \"user_id\": df_favourite_items[\"user_id\"].tolist(),\n", + " \"product_id\": df_favourite_items[\"pro_id\"].tolist()\n", + "}\n", + "\n", + "print(favourite_items_data)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'user_id': [2, 3, 3, 3, 5, 2, 4, 4, 1, 1, 1, 3, 3, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], 'product_id': [3, 3, 3, 3, 2, 3, 4, 3, 2, 3, 1, 1, 1, 1, 4, 4, 13, 12, 12, 12, 2, 2, 2, 3, 3, 3, 3, 1, 4, 1, 3, 4], 'rating': [5, 5, 5, 5, 3, 3, 5, 2, 2, 1, 1, 3, 3, 5, 5, 5, 4, 5, 5, 5, 4, 1, 4, 4, 4, 3, 4, 4, 2, 2, 2, 2]}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\vonhu\\AppData\\Local\\Temp\\ipykernel_5672\\2058965346.py:12: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n", + " df = pd.read_sql(query, conn)\n" + ] + } + ], + "source": [ + "# Truy vấn SQL\n", + "conn = pymysql.connect(\n", + " host=\"localhost\",\n", + " user=\"root\",\n", + " password=\"Yahana0509@\",\n", + " database=\"demohmdrinks\",\n", + " port=4000,\n", + " charset=\"utf8mb4\"\n", + ")\n", + "\n", + "query = \"SELECT user_id, pro_id, rating_star FROM review;\"\n", + "df = pd.read_sql(query, conn)\n", + "\n", + "# Đóng kết nối\n", + "conn.close()\n", + "\n", + "# Chuyển DataFrame thành dictionary\n", + "reviews_data = {\n", + " \"user_id\": df[\"user_id\"].tolist(),\n", + " \"product_id\": df[\"pro_id\"].tolist(),\n", + " \"rating\": df[\"rating_star\"].tolist()\n", + "}\n", + "\n", + "# Kiểm tra kết quả\n", + "print(reviews_data)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'product_id': [1, 2, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], 'name': ['string', 'nhuy_test2', 'Trà1', 'string111111', 'Nuoc ep hoa qua ', 'SP2', 'NCep2', 'Như ý', 'Nước tết 1', 'Nước tết 2', 'Test vui vẻ', 'SPTest22', 'demo1', 'test1', 'test11', 'test11', 'bubu1', 'bubu1', 'bubu123', 'bubu thúi1', 'bubu thúi1', 'Dâu nước', 'Trà lá sen', 'Dâu nước1', 'trà dâu', 'Trà bưởi lá hồng', 'Bò sữa Gia Lai 1', 'Bò sữa Gia Lai 2', 'Bò sữa Gia Lai 3', 'Bò sữa Gia Lai 3', 'Nước trà gừng chua cay'], 'category': ['nhuytest2', 'nhuytest2', 'nhuytest2', 'string', 'nuoc ep', 'string1fggg', 'string', 'nhuytest2', 'Nước tt1', 'string1fggg', 'SP2', 'Test5', 'demo4', 'Test_Category', 'Test_Category1', 'Test_Category1', 'Test_Bubu', 'Test_Bubu', 'Test_Bubu1', 'nhuytest2', 'nhuytest2', 'nhuytest2', 'string', 'nhuytest2', 'string', 'nhuytest2', 'Sữa bò Phú Yên', 'Sữa bò Phú Yên', 'Sữa bò Phú Yên', 'nuoc ep', 'string']}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\vonhu\\AppData\\Local\\Temp\\ipykernel_5672\\101450748.py:14: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n", + " df = pd.read_sql(query, conn)\n" + ] + } + ], + "source": [ + "conn = pymysql.connect(\n", + " host=\"localhost\",\n", + " user=\"root\",\n", + " password=\"Yahana0509@\",\n", + " database=\"demohmdrinks\",\n", + " port=4000,\n", + " charset=\"utf8mb4\"\n", + ")\n", + "query = \"\"\"\n", + " SELECT p.pro_id, p.pro_name, c.cate_name AS category\n", + " FROM product p\n", + " JOIN category c ON p.category_id = c.cate_id;\n", + "\"\"\"\n", + "df = pd.read_sql(query, conn)\n", + "\n", + "# Đóng kết nối\n", + "conn.close()\n", + "\n", + "# Chuyển DataFrame thành dictionary\n", + "products_data = {\n", + " \"product_id\": df[\"pro_id\"].tolist(),\n", + " \"name\": df[\"pro_name\"].tolist(),\n", + " \"category\": df[\"category\"].tolist()\n", + "}\n", + "\n", + "# Kiểm tra kết quả\n", + "print(products_data)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " user_id product_id\n", + "0 3 1\n", + "1 3 1\n", + "2 3 1\n", + "3 3 1\n", + "4 3 1\n", + "5 3 1\n", + "6 3 1\n", + "7 3 1\n", + "8 3 1\n", + "9 3 1\n", + "10 3 2\n", + "11 3 2\n", + "12 3 2\n", + "13 3 2\n", + "14 3 2\n", + "15 3 2\n", + "16 3 2\n", + "17 3 5\n", + "18 3 5\n", + "19 3 7\n", + "20 3 7\n", + "21 3 7\n", + "22 3 7\n", + "23 3 7\n", + "24 3 14\n", + "25 3 14\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\vonhu\\AppData\\Local\\Temp\\ipykernel_5672\\940316926.py:32: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n", + " df = pd.read_sql(query, conn)\n" + ] + } + ], + "source": [ + "import pymysql\n", + "conn = pymysql.connect(\n", + " host=\"localhost\",\n", + " user=\"root\",\n", + " password=\"Yahana0509@\",\n", + " database=\"demohmdrinks\",\n", + " port=4000,\n", + " charset=\"utf8mb4\"\n", + ")\n", + "\n", + "\n", + "\n", + "query = \"\"\"\n", + "SELECT o.user_id, ci.pro_id AS product_id\n", + "FROM `orders` o\n", + "JOIN order_item oi ON o.order_id = oi.order_id\n", + "JOIN cart c ON oi.cart_id = c.cart_id\n", + "JOIN cart_item ci ON c.cart_id = ci.cart_id\n", + "JOIN payments p ON o.order_id = p.order_id\n", + "WHERE o.status = \"CONFIRM\"\n", + " AND p.status = 'SUCCESS'\n", + "\n", + "UNION ALL\n", + "\n", + "SELECT c.user_id, ci.pro_id AS product_id\n", + "FROM cart c\n", + "JOIN cart_item ci ON c.cart_id = ci.cart_id\n", + "ORDER BY user_id, product_id;\n", + "\"\"\"\n", + "\n", + "# Đọc dữ liệu vào DataFrame\n", + "df = pd.read_sql(query, conn)\n", + "\n", + "print(df)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'user_id': [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], 'product_id': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 5, 5, 7, 7, 7, 7, 7, 14, 14]}\n" + ] + } + ], + "source": [ + "order_history_data = {\n", + " \"user_id\": df[\"user_id\"].tolist(),\n", + " \"product_id\": df[\"product_id\"].tolist()\n", + "}\n", + "\n", + "# In kết quả\n", + "print(order_history_data)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "from sklearn.feature_extraction.text import TfidfVectorizer\n", + "from sklearn.metrics.pairwise import cosine_similarity" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "from sklearn.feature_extraction.text import TfidfVectorizer\n", + "from sklearn.metrics.pairwise import cosine_similarity\n", + "from scipy.sparse.linalg import svds\n", + "from sklearn.preprocessing import MinMaxScaler" + ] + }, + { + "cell_type": "code", + "execution_count": 253, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.append(r\"D:\\HmDrinks_Chat\\chatbot\\function\\recommender\")\n", + "import get_data\n" + ] + }, + { + "cell_type": "code", + "execution_count": 256, + "metadata": {}, + "outputs": [], + "source": [ + "data = await get_data.get_data_recommend()" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "# Tạo DataFrame\n", + "products = pd.DataFrame(products_data)\n", + "reviews = pd.DataFrame(reviews_data)\n", + "favourite_items = pd.DataFrame(favourite_items_data)\n", + "order_history = pd.DataFrame(order_history_data)" + ] + }, + { + "cell_type": "code", + "execution_count": 257, + "metadata": {}, + "outputs": [], + "source": [ + "# Tạo DataFrame\n", + "products = pd.DataFrame(list(data)[2])\n", + "reviews = pd.DataFrame(list(data)[1])\n", + "favourite_items = pd.DataFrame(list(data)[0])\n", + "order_history = pd.DataFrame(list(data)[3])" + ] + }, + { + "cell_type": "code", + "execution_count": 258, + "metadata": {}, + "outputs": [], + "source": [ + "# Tiền xử lý dữ liệu nâng cao\n", + "products['description'] = products['name'] + ' ' + products['category']\n", + "products['description'] = products['description'].str.lower()\n", + "\n", + "# Thêm trọng số thời gian cho lịch sử mua hàng\n", + "order_history['timestamp'] = pd.date_range(start='2024-01-01', periods=len(order_history), freq='D')\n", + "order_history['time_weight'] = 1 - (pd.Timestamp.now() - order_history['timestamp']).dt.days / 365\n", + "\n", + "# Content-Based Filtering cải tiến\n", + "tfidf = TfidfVectorizer(stop_words='english', ngram_range=(1, 2), max_features=1000)\n", + "tfidf_matrix = tfidf.fit_transform(products['description'].fillna(''))\n", + "cosine_sim = cosine_similarity(tfidf_matrix, tfidf_matrix)\n", + "\n", + "# Collaborative Filtering với SVD\n", + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": 259, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "class HybridRecommender:\n", + " def __init__(self, products, reviews, favourite_items, order_history, \n", + " num_factors=2, regularization=0.02):\n", + " self.products = products\n", + " self.reviews = reviews\n", + " self.favourite_items = favourite_items\n", + " self.order_history = order_history\n", + " self.num_factors = num_factors\n", + " self.regularization = regularization\n", + " \n", + " # Chuẩn bị dữ liệu\n", + " self.user_ids = reviews[\"user_id\"].unique()\n", + " self.product_ids = products[\"product_id\"].unique()\n", + " self.user_to_index = {uid: i for i, uid in enumerate(self.user_ids)}\n", + " self.product_to_index = {pid: i for i, pid in enumerate(self.product_ids)}\n", + " self.index_to_product = {i: pid for pid, i in self.product_to_index.items()}\n", + " \n", + " # Tạo ma trận rating\n", + " self.ratings_matrix = self._create_ratings_matrix()\n", + " self.global_mean = self.reviews['rating'].mean()\n", + " \n", + " def _create_ratings_matrix(self):\n", + " matrix = np.zeros((len(self.user_ids), len(self.product_ids)))\n", + " for _, row in self.reviews.iterrows():\n", + " u_idx = self.user_to_index[row[\"user_id\"]]\n", + " p_idx = self.product_to_index[row[\"product_id\"]]\n", + " matrix[u_idx, p_idx] = row[\"rating\"]\n", + " return matrix\n", + " \n", + " def train_svd(self):\n", + " # Chuẩn hóa ma trận rating\n", + " ratings_filled = np.where(self.ratings_matrix == 0, \n", + " self.global_mean, \n", + " self.ratings_matrix)\n", + " \n", + " # Phân tích SVD\n", + " U, sigma, Vt = svds(ratings_filled, k=self.num_factors)\n", + " sigma = np.diag(sigma)\n", + " self.predicted_ratings = np.dot(np.dot(U, sigma), Vt)\n", + " \n", + " # Chuẩn hóa kết quả về khoảng [1, 5]\n", + " scaler = MinMaxScaler(feature_range=(1, 5))\n", + " self.predicted_ratings = scaler.fit_transform(\n", + " self.predicted_ratings.reshape(-1, 1)\n", + " ).reshape(self.ratings_matrix.shape)\n", + " \n", + " def get_content_score(self, user_id, top_n=3):\n", + " # Lấy các sản phẩm liên quan từ yêu thích và lịch sử\n", + " fav_items = self.favourite_items[self.favourite_items['user_id'] == user_id]['product_id']\n", + " past_orders = self.order_history[self.order_history['user_id'] == user_id]\n", + " \n", + " content_scores = {}\n", + " for item in set(fav_items).union(set(past_orders['product_id'])):\n", + " if item in self.product_to_index:\n", + " idx = self.product_to_index[item]\n", + " sim_scores = enumerate(cosine_sim[idx])\n", + " top_similar = sorted(sim_scores, key=lambda x: x[1], reverse=True)[:top_n]\n", + " for sim_idx, score in top_similar:\n", + " prod_id = self.index_to_product[sim_idx]\n", + " content_scores[prod_id] = content_scores.get(prod_id, 0) + score\n", + " \n", + " return content_scores\n", + " \n", + " def hybrid_recommend(self, user_id, top_n=10, weights={'collab': 0.6, 'content': 0.3, 'history': 0.1}, randomness=0.1):\n", + " # Kiểm tra nếu user_id không có trong hệ thống\n", + " if user_id not in self.user_to_index:\n", + " print(f\"User {user_id} chưa có đánh giá nào. Kiểm tra sản phẩm yêu thích...\")\n", + "\n", + " # Lấy danh sách sản phẩm yêu thích của người dùng\n", + " fav_items = self.favourite_items[self.favourite_items['user_id'] == user_id]['product_id']\n", + "\n", + " if not fav_items.empty:\n", + " print(f\"Người dùng có sản phẩm yêu thích. Gợi ý dựa trên nội dung...\")\n", + " content_scores = self.get_content_score(user_id)\n", + " return sorted(content_scores.items(), key=lambda x: x[1], reverse=True)[:top_n]\n", + " \n", + " print(f\"Người dùng không có sản phẩm yêu thích. Áp dụng cold-start recommendations...\")\n", + "\n", + " # Nếu không có sản phẩm yêu thích, sử dụng cold-start (dựa vào sản phẩm phổ biến)\n", + " popular_items = self.reviews.groupby('product_id')['rating'].mean()\n", + " popular_items = popular_items.sort_values(ascending=False).head(top_n)\n", + "\n", + " # Chuyển sang dict để dễ xử lý\n", + " cold_start_scores = {pid: score for pid, score in popular_items.items()}\n", + "\n", + " return sorted(cold_start_scores.items(), key=lambda x: x[1], reverse=True)[:top_n]\n", + "\n", + " \n", + " user_idx = self.user_to_index[user_id]\n", + " \n", + " # Collaborative filtering score\n", + " collab_scores = dict(enumerate(self.predicted_ratings[user_idx]))\n", + " collab_scores = {self.index_to_product[i]: s for i, s in collab_scores.items()}\n", + " \n", + " # Content-based score\n", + " content_scores = self.get_content_score(user_id)\n", + " \n", + " # History score\n", + " history_scores = {}\n", + " user_history = self.order_history[self.order_history['user_id'] == user_id]\n", + " for _, row in user_history.iterrows():\n", + " history_scores[row['product_id']] = row['time_weight']\n", + " \n", + " # Kết hợp scores với trọng số\n", + " final_scores = {}\n", + " for prod_id in self.product_ids:\n", + " final_score = 0\n", + " if prod_id in collab_scores:\n", + " final_score += weights['collab'] * collab_scores[prod_id]\n", + " if prod_id in content_scores:\n", + " final_score += weights['content'] * content_scores[prod_id]\n", + " if prod_id in history_scores:\n", + " final_score += weights['history'] * history_scores[prod_id]\n", + " if final_score > 0:\n", + " # Thêm yếu tố ngẫu nhiên có kiểm soát\n", + " noise = random.uniform(-randomness, randomness) * final_score\n", + " final_scores[prod_id] = final_score + noise\n", + " \n", + " # Lọc sản phẩm đã mua/yêu thích để gợi ý sản phẩm mới\n", + " purchased = set(self.order_history[self.order_history['user_id'] == user_id]['product_id'])\n", + " liked = set(self.favourite_items[self.favourite_items['user_id'] == user_id]['product_id'])\n", + " final_scores = {k: v for k, v in final_scores.items() if k not in purchased}\n", + "\n", + " # final_scores = {k: v for k, v in final_scores.items() if k not in purchased.union(liked)}\n", + " \n", + " # Giới hạn top N sản phẩm\n", + " top_candidates = sorted(final_scores.items(), key=lambda x: x[1], reverse=True)\n", + " print(top_candidates)\n", + " \n", + " # Chọn ngẫu nhiên từ top 2 * top_n để tăng tính ngẫu nhiên, nhưng vẫn ưu tiên sản phẩm liên quan\n", + " if len(top_candidates) > top_n:\n", + " top_candidates = random.sample(top_candidates[:2 * top_n], top_n)\n", + " \n", + " # Cập nhật lịch sử mua hàng giả lập sau khi gợi ý\n", + " # for product_id, _ in top_candidates:\n", + " # self.order_history = pd.concat([\n", + " # self.order_history,\n", + " # pd.DataFrame({'user_id': [user_id], 'product_id': [product_id], \n", + " # 'timestamp': [pd.Timestamp.now()], 'time_weight': [1.0]})\n", + " # ], ignore_index=True)\n", + "\n", + " return sorted(top_candidates, key=lambda x: x[1], reverse=True)\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 260, + "metadata": {}, + "outputs": [], + "source": [ + "# Sử dụng hệ thống\n", + "recommender = HybridRecommender(products, reviews, favourite_items, order_history)\n", + "recommender.train_svd()" + ] + }, + { + "cell_type": "code", + "execution_count": 263, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User 6 chưa có đánh giá nào. Kiểm tra sản phẩm yêu thích...\n", + "Người dùng không có sản phẩm yêu thích. Áp dụng cold-start recommendations...\n", + "Gợi ý cho user 6:\n", + "- Nước tết 2 (score: 5.000)\n", + "- Test vui vẻ (score: 4.000)\n", + "- string111111 (score: 3.800)\n", + "- Trà1 (score: 3.583)\n", + "- string (score: 3.000)\n" + ] + } + ], + "source": [ + "user_id = 6\n", + "recommendations = recommender.hybrid_recommend(user_id, top_n= 5,randomness=0.1)\n", + "print(f\"Gợi ý cho user {user_id}:\")\n", + "for product_id, score in recommendations:\n", + " product_name = products[products['product_id'] == product_id]['name'].iloc[0]\n", + " print(f\"- {product_name} (score: {score:.3f})\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/models/Database_Entity.py b/models/Database_Entity.py new file mode 100644 index 0000000000000000000000000000000000000000..f7e21d3c91734507800675eaf89d517a8a221c8f --- /dev/null +++ b/models/Database_Entity.py @@ -0,0 +1,115 @@ +from mongoengine import ( + Document, StringField, ReferenceField, DateTimeField, BooleanField, ObjectIdField,EmbeddedDocumentField +) +from datetime import datetime +import pytz +from mongoengine import connect +import os +from dotenv import load_dotenv +load_dotenv() +MONGO_URI = os.getenv("MONGO_URI", "") + +def init_db(db_name="chatbot_hmdrinks"): + connect(db_name, host=MONGO_URI) + +from mongoengine import Document, StringField, IntField,ListField + + +class User(Document): + _id = ObjectIdField(required=True, primary_key=True) + user_name = StringField(required=True, max_length=255) + user_id = IntField(unique=True, required=True) + is_deleted = BooleanField(default=False) + date_deleted = DateTimeField(default=None) + meta = {'collection': 'users'} + + +class ChatHistory(Document): + _id = ObjectIdField(required=True, primary_key=True) + user = ReferenceField(User, required=True, reverse_delete_rule=2) + name_chat = StringField(required=True, unique=True, max_length=255) + is_deleted = BooleanField(default=False) + date_deleted = DateTimeField(default=None) + meta = {'collection': 'chat_history'} + +from bson import ObjectId +from mongoengine import EmbeddedDocument, StringField, IntField, FloatField + + + + +class CartProduct(EmbeddedDocument): + name = StringField() + quantity = IntField(min_value=1, default=1) + size = StringField() + +class DetailChat(Document): + _id = ObjectIdField(required=True, primary_key=True,default=ObjectId) + chat_history = ReferenceField(ChatHistory, required=True, reverse_delete_rule=2) + you_message = StringField() + ai_message = StringField() + data_relevant = StringField() + full_input = StringField() + source_file = StringField() + cart_id = IntField() + confirmed_order = BooleanField(default=False) + timestamp = DateTimeField(default=lambda: datetime.now(pytz.UTC)) + is_deleted = BooleanField(default=False) + date_deleted = DateTimeField(default=lambda: datetime.now(pytz.UTC)) + meta = {'collection': 'detail_chat', + 'strict': False + } + + +class ChatCart(Document): + _id = ObjectIdField(primary_key=True, default=ObjectId) + chat_history = ReferenceField(ChatHistory, required=True, reverse_delete_rule=2) + created_from_detail_chat = ReferenceField(DetailChat) # Để biết cart được tạo từ đoạn chat nào + cart_products = ListField(EmbeddedDocumentField(CartProduct), default=list) + status = StringField(choices=["pending", "confirmed", "failed"], default="pending") + confirmed_order = BooleanField(default=False) + created_at = DateTimeField(default=lambda: datetime.now(pytz.UTC)) + confirmed_at = DateTimeField() + note = StringField() + cart_id = IntField() + order_id = IntField() + + #Backup + backup_cart_products = ListField(EmbeddedDocumentField(CartProduct), default=list) + backup_status = StringField(choices=["pending", "confirmed", "failed"], default="pending") + backup_confirmed_order = BooleanField() + meta = { + 'collection': 'chat_cart', + 'strict': False + } + + +class StopSignal(Document): + _id = ObjectIdField(primary_key=True, default=ObjectId) + chat_history = ReferenceField(ChatHistory, required=True, reverse_delete_rule=2) + is_stopped = BooleanField(default=False) + created_at = DateTimeField(default=lambda: datetime.now(pytz.UTC)) + stopped_at = DateTimeField() + + meta = { + 'collection': 'stop_signal', + 'strict': False, + 'indexes': ['chat_history'] + } + +from mongoengine import Document, StringField, DateTimeField, DictField +class PaymentCallbackLog(Document): + _id = ObjectIdField(primary_key=True, default=ObjectId) + # Loại cổng thanh toán: momo, zalopay, vnpay,... + type = StringField(required=True, choices=["momo", "zalopay", "vnpay"]) + app_trans_id = StringField() # ZaloPay + order_id = StringField() # MoMo + txn_ref = StringField() # VNPAY + raw_data = DictField() + status = StringField(choices=["success", "fail", "error", "unknown"], default="unknown") + created_at = DateTimeField(default=lambda: datetime.now(pytz.UTC)) + is_refund = BooleanField() + meta = { + 'collection': 'payment_callback_logs', + 'strict': False + } \ No newline at end of file diff --git a/models/Database_MySQL.py b/models/Database_MySQL.py new file mode 100644 index 0000000000000000000000000000000000000000..b33dc6be5b63de746c87efe6b704f252b272aeb1 --- /dev/null +++ b/models/Database_MySQL.py @@ -0,0 +1,74 @@ +from sqlalchemy import Column, String, Text, DateTime, Integer, ForeignKey, Boolean, Enum +from sqlalchemy.orm import relationship, DeclarativeBase +from sqlalchemy.sql import func +import enum + + +class UserRole(enum.Enum): + ADMIN = "ADMIN" + CUSTOMER = "CUSTOMER" + SHIPPER = "SHIPPER" + + +class SexType(enum.Enum): + OTHER = "OTHER" + MALE = "MALE" + FEMALE = "FEMALE" + + +class UserType(enum.Enum): + BASIC = "BASIC" + BOTH = "BOTH" + EMAIL = "EMAIL" + + +class Base(DeclarativeBase): + pass + + +class User(Base): + __tablename__ = 'user' + user_id = Column(Integer, primary_key=True, autoincrement=True) + avatar = Column(Text, nullable=True) + birth_date = Column(DateTime, nullable=True) + city = Column(String(255), nullable=True) + district = Column(String(255), nullable=True) + email = Column(String(255), unique=True, nullable=False) + full_name = Column(String(255), nullable=False) + is_deleted = Column(Boolean, default=False) + password = Column(Text, nullable=False) + phone_number = Column(String(20), nullable=True) + role = Column(Enum(UserRole), nullable=False) # 🎯 Enum Role + sex = Column(Enum(SexType), nullable=True) # 🎯 Enum Sex + street = Column(String(255), nullable=True) + type = Column(Enum(UserType), nullable=True) # 🎯 Enum Type + username = Column(String(255), unique=True, nullable=False) + date_created = Column(DateTime, default=func.now()) + date_updated = Column(DateTime, default=func.now(), onupdate=func.now()) + date_deleted = Column(DateTime, nullable=True) + ward = Column(String(255), nullable=True) + tokens = relationship("Token", back_populates="user") + user_chats = relationship("UserChat", back_populates="user") + + +class Token(Base): + __tablename__ = 'token' + token_id = Column(Integer, primary_key=True, autoincrement=True) + access_token = Column(Text, nullable=False) + expire = Column(DateTime, nullable=False) + refresh_token = Column(Text, nullable=False) + user_id = Column(Integer, ForeignKey('user.user_id'), nullable=False) + user = relationship("User", back_populates="tokens") + + +class UserChat(Base): + __tablename__ = 'user_chat' + user_chat_id = Column(Integer, primary_key=True, autoincrement=True) + chat_name = Column(String(255), nullable=False) + id_mongo_db = Column(String(255), nullable=True) + user_id = Column(Integer, ForeignKey('user.user_id'), nullable=False) + date_created = Column(DateTime, default=func.now()) + date_updated = Column(DateTime, default=func.now(), onupdate=func.now()) + date_deleted = Column(DateTime, nullable=True) + is_deleted = Column(Boolean, default=False) + user = relationship("User", back_populates="user_chats") \ No newline at end of file diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/models/__pycache__/Database_Entity.cpython-311.pyc b/models/__pycache__/Database_Entity.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf4c7edee6deac38f65bb40719b58283dbe2fd29 Binary files /dev/null and b/models/__pycache__/Database_Entity.cpython-311.pyc differ diff --git a/models/__pycache__/Database_Entity.cpython-312.pyc b/models/__pycache__/Database_Entity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f760ecddd32558b9a16803cc5b75b65077e58b8 Binary files /dev/null and b/models/__pycache__/Database_Entity.cpython-312.pyc differ diff --git a/models/__pycache__/Database_MySQL.cpython-311.pyc b/models/__pycache__/Database_MySQL.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7261a86a6d2f040a99b5dad84ebb5bfa52e66fc5 Binary files /dev/null and b/models/__pycache__/Database_MySQL.cpython-311.pyc differ diff --git a/models/__pycache__/Database_MySQL.cpython-312.pyc b/models/__pycache__/Database_MySQL.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a828d146f546b3b972ae2942bbc7568fddb0fe3c Binary files /dev/null and b/models/__pycache__/Database_MySQL.cpython-312.pyc differ diff --git a/models/__pycache__/__init__.cpython-311.pyc b/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a847f3d153f7c10ab20a4846ddde424cfcc0623 Binary files /dev/null and b/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/models/__pycache__/__init__.cpython-312.pyc b/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..658f2e02c417efbda5f57b77b1c446cbec9e3a75 Binary files /dev/null and b/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/repository/MySQL/Config_Database_MySQL.py b/repository/MySQL/Config_Database_MySQL.py new file mode 100644 index 0000000000000000000000000000000000000000..2c64989906e806ed49854242eb45f6e2a987b0e5 --- /dev/null +++ b/repository/MySQL/Config_Database_MySQL.py @@ -0,0 +1,86 @@ +from sqlalchemy import create_engine, URL, text +from sqlalchemy.orm import sessionmaker, DeclarativeBase +from sqlalchemy.exc import OperationalError +from dotenv import load_dotenv +import os +load_dotenv() +import os, sys +import os +from dotenv import load_dotenv + +# Load biến môi trường từ file .env + +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) +from dotenv import load_dotenv, find_dotenv + +# Tự động tìm file .env gần nhất trong cây thư mục cha +load_dotenv(find_dotenv(), override=True) + +MYSQL_USER_NAME = os.getenv('DB_USER') +MYSQL_PASSWORD = os.getenv('DB_PASSWORD') +MYSQL_PORT = os.getenv('DB_PORT') +MYSQL_DATABASE = os.getenv('DB_NAME') +MYSQL_HOST = os.getenv('DB_HOST') +MYSQL_PORT = int(MYSQL_PORT) if MYSQL_PORT else 3306 + +import os +from urllib.parse import quote + +password = os.getenv("DB_PASSWORD") +DB_PASSWORD = quote(password) + +Base = DeclarativeBase() + +# def get_db_engine(): +# """ Tạo kết nối đến MySQL """ +# dsn = URL.create( +# drivername="mysql+pymysql", +# username=MYSQL_USER_NAME, +# # password=MYSQL_PASSWORD, +# host=MYSQL_HOST, +# port=MYSQL_PORT, +# database=MYSQL_DATABASE +# ) +# return create_engine(dsn) + +# Connect server MYSQL +def get_db_engine(): + dsn = URL.create( + drivername="mysql+pymysql", + username=MYSQL_USER_NAME, + password=MYSQL_PASSWORD, + host=MYSQL_HOST, + port=MYSQL_PORT, + database=MYSQL_DATABASE + ) + connect_args = { + "ssl_verify_cert": True, + "ssl_verify_identity": True, + "ssl_ca": 'ca.pem', + } + return create_engine( + dsn, + connect_args=connect_args, + ) + + +# echo=True để debug SQL logs + +# # Khởi tạo engine +# engine = get_db_engine() + +# # Tạo session factory +# SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# def test_connection(): +# """ Kiểm tra kết nối MySQL """ +# try: +# with engine.connect() as connection: +# result = connection.execute(text("SELECT 1")) # Dùng text() để tránh lỗi ObjectNotExecutableError +# print("✅ Kết nối MySQL thành công!", result.scalar()) # result.scalar() lấy giá trị đầu tiên +# except OperationalError as e: +# print("❌ Kết nối MySQL thất bại!", str(e)) + +# # Chạy test kết nối +# test_connection() diff --git a/repository/MySQL/UserRepository.py b/repository/MySQL/UserRepository.py new file mode 100644 index 0000000000000000000000000000000000000000..224c1bd1ad4bd30908a66ac38d3c0fb00c262dd3 --- /dev/null +++ b/repository/MySQL/UserRepository.py @@ -0,0 +1,38 @@ +from sqlalchemy.orm import sessionmaker +import os +import sys +BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) +sys.path.insert(0, BASE_DIR) +from models import Database_MySQL +from repository.MySQL import Config_Database_MySQL as cf +from datetime import timedelta +user = Database_MySQL.User +token = Database_MySQL.Token +chat_history=Database_MySQL.UserChat + +def getUserIdByAccessToken(token_input: str): + engine = cf.get_db_engine() + Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) + with Session() as session: + user_id = session.query(token.user_id).filter(token.access_token == token_input).first() + session.close() + return user_id + + +def getUserByUserId(user_id:int): + engine = cf.get_db_engine() + Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) + with Session() as session: + user_info = session.query(user).filter(user.user_id == user_id,user.is_deleted==False).first() + session.close() + return user_info + + +def getChatHistory(user_id:int,chat_id:str): + engine = cf.get_db_engine() + Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) + with Session() as session: + chat_history_result = session.query(chat_history).filter(chat_history.is_deleted==False,chat_history.user_id==user_id,chat_history.id_mongo_db==chat_id).first() + session.close() + return chat_history_result + \ No newline at end of file diff --git a/repository/MySQL/__pycache__/Config_Database_MySQL.cpython-311.pyc b/repository/MySQL/__pycache__/Config_Database_MySQL.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2e5b65810e35932b9b565c17bad2c66a4ec6e9b Binary files /dev/null and b/repository/MySQL/__pycache__/Config_Database_MySQL.cpython-311.pyc differ diff --git a/repository/MySQL/__pycache__/Config_Database_MySQL.cpython-312.pyc b/repository/MySQL/__pycache__/Config_Database_MySQL.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..846986dc7ef57c53b98076dae694dd530a4d0636 Binary files /dev/null and b/repository/MySQL/__pycache__/Config_Database_MySQL.cpython-312.pyc differ diff --git a/repository/MySQL/__pycache__/UserRepository.cpython-311.pyc b/repository/MySQL/__pycache__/UserRepository.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bef06fd6a8c4b1213d4b48940f59be2b8fe4009 Binary files /dev/null and b/repository/MySQL/__pycache__/UserRepository.cpython-311.pyc differ diff --git a/repository/MySQL/__pycache__/UserRepository.cpython-312.pyc b/repository/MySQL/__pycache__/UserRepository.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4db1023447ab9dc90f411b247149538bedb9be4d Binary files /dev/null and b/repository/MySQL/__pycache__/UserRepository.cpython-312.pyc differ diff --git a/repository/MySQL/ca.pem b/repository/MySQL/ca.pem new file mode 100644 index 0000000000000000000000000000000000000000..cfa43e13dfd4ddf565b0d013caffa3f2a833ce8c --- /dev/null +++ b/repository/MySQL/ca.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEUDCCArigAwIBAgIUfxHeji94PVdRnLYTj22XRvvdXXwwDQYJKoZIhvcNAQEM +BQAwQDE+MDwGA1UEAww1NDY4MDI2MDYtZGM1OC00NDM4LTg0ODctNzQxNWE5MjVl +YjJmIEdFTiAxIFByb2plY3QgQ0EwHhcNMjUwNjE5MDkyNzM2WhcNMzUwNjE3MDky +NzM2WjBAMT4wPAYDVQQDDDU0NjgwMjYwNi1kYzU4LTQ0MzgtODQ4Ny03NDE1YTky +NWViMmYgR0VOIDEgUHJvamVjdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCC +AYoCggGBAMqawd0fx+VhLXb1sOcvAOJAjnybF4phtphIZywDc+wrSiJKmKpFdIEV +9d4hyzHqby7uyFvXp4qe710D/1vYzTfAtWyYra3Fq5FAR+rZoQfKeBJneLvWr4OM +O4nhntrXLWoz5z5dvrwwMQdFXK5zWL776gsWo9Zfved1/VrBma0PMnLWGCxBhe8D +CUvFwj9WDLcBu1Wpemj5hNUYV7HrIKYKnRXT6LsW8LAcwdVvwkFMeTYXoAvxZime +jgdm0l9aBHmkxVQB4yV2jzSjYl+RfBv9Fr0sFsTjYRhWTnqgotwQ/kWtV2lPvTC8 +9RrR+Zv4Npr5GFva856J8F7vJYE4u8iXtcW88/8R2BD6X7Is0bNSXN4ddb4e1uG1 +xibOwNdFAbaiRfQpdg5uA87LCbb4JsU0WRe705qJHOPynVMQsQHDqioexVkJwNot +FhRG7j9rQLMihC5IRbxYql0FLamm0xb32Z4bSTqmwQacQF+uB27i6rxB3w8wV2C0 +tXmBa9siVwIDAQABo0IwQDAdBgNVHQ4EFgQUHgH+oY6nEHz0RQ7Kov6/1L1/scYw +EgYDVR0TAQH/BAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQEMBQAD +ggGBAJSFK9FoV5d/PxtrP2z+BViDjgXeoJ6tLVykLjpVEr0OCIzwgi3F8O4a1U7P +2BQu5URiiiC28aoIvDXcmowQFJBw6DaVIadGACifNORCWals+gT+4l2kdnRFW8fy +gP+cHjj9IBLtmcgttMTTRArTYgYO9GfuK1PMD8srG/syue5V/bQ6+vmO6gAtuTs1 +cnQXzzMyZKNLb90MERfojegLXCyoI2O5nC604fhDSgnVUKzw7g6vRPHAWPQGCZn1 +DwpI7BydRRs57F/Tigz8EgbzC2cuTQaaWr3oOiBTsGl5Nkc2iPxKXzoPHMUT3MD3 +vRosEkl+LN5hupYbaHagHT50n9j+AG5KXiRoN4S0IxJZWSGMpYMYEQ4Qkd3AvRnW +ECdT5ZXXm19a4teTZIk1fZ9dITnn/vobfW0MhpG4kkHIOBHVy4VJrLqFrQfLOVQM +qVgKdc0K6FfDnfNdvkrfzJoLoR0X6AtVS60pfpolBXNQEPNWcdKDL0DSBbZ+rnid +9nYErA== +-----END CERTIFICATE----- diff --git a/request/RequestChat.py b/request/RequestChat.py new file mode 100644 index 0000000000000000000000000000000000000000..e8d2edb727755dc7f8e8e4d65761b608696313cf --- /dev/null +++ b/request/RequestChat.py @@ -0,0 +1,40 @@ +from pydantic import BaseModel +from typing import Optional,Literal + +class RequestChat(BaseModel): + user_id: Optional[str] + role: Optional[str] + languages: Optional[str] + user_input: Optional[str] + + +class RecommendRequest(BaseModel): + number:int + +class RequestHistoryChat(BaseModel): + user_id: Optional[str] + + +class RequestDetailChat(BaseModel): + chat_id: Optional[str] + + +class ChatRequest(BaseModel): + user_id: int + +class DeleteChatRequest(BaseModel): + chat_id: str + +class UpdateNameChat(BaseModel): + chat_id: str + name_chat: str + +class Regenerate(BaseModel): + chat_id: str + question_new: str + languages: Optional[str] + +class ChatWithServer(BaseModel): + user_input: str + language: Literal["EN", "VN"] + chat_history_id: str diff --git a/request/__init__.py b/request/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/request/__pycache__/RequestChat.cpython-311.pyc b/request/__pycache__/RequestChat.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b4e5077604830144343a7cc8b0279cff1dd6bf5 Binary files /dev/null and b/request/__pycache__/RequestChat.cpython-311.pyc differ diff --git a/request/__pycache__/RequestChat.cpython-312.pyc b/request/__pycache__/RequestChat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f298735db7174d89e037ff6c04a80a98beab2054 Binary files /dev/null and b/request/__pycache__/RequestChat.cpython-312.pyc differ diff --git a/request/__pycache__/__init__.cpython-311.pyc b/request/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f1e8dbef1eb0a2c5466e3c4ac4752c59cb7a19d Binary files /dev/null and b/request/__pycache__/__init__.cpython-311.pyc differ diff --git a/response/ResponseChat.py b/response/ResponseChat.py new file mode 100644 index 0000000000000000000000000000000000000000..f4274193945b163b01b9b5edd9db90712e6eec3b --- /dev/null +++ b/response/ResponseChat.py @@ -0,0 +1,76 @@ +from pydantic import BaseModel +from typing import List, Union + +class DataAnswer(BaseModel): + answer: str + + +class CreateNewChat(BaseModel): + idMongo: str + chat_name:str + +from datetime import datetime + +class ChatResponse(BaseModel): + chat_id: str + chat_name: str + timestamp: datetime # Thời gian tạo hoặc cập nhật chat + +class UserChatHistoryResponse(BaseModel): + user_id: int + user_name: str + chat_list: List[ChatResponse] + +class DetailResponse(BaseModel): + id: str + you_message:str + ai_message:str + timestamp: datetime + +class ListDetailResponse(BaseModel): + chat_id:str + chat_name:str + list_detail_response: List[DetailResponse] + +class DataAnswer1(BaseModel): + id: int + answer: str + data_relevant: List[str] + sources: List[str] + +class FileMetadata(BaseModel): + source: str + +class FileResponse(BaseModel): + page_content: str + metadata: FileMetadata + type: str + +class DataExtractFile(BaseModel): + text_all: Union[List[FileResponse], None, str] + +class Message(BaseModel): + message: str + +class CheckModel(BaseModel): + check: bool + +class ResponseQuery2Upgrade(BaseModel): + status: int + data: DataAnswer + +class ResponseQuery2UpgradeOld(BaseModel): + status: int + data: DataAnswer1 + +class ResponseExtractFile(BaseModel): + status: int + data: DataExtractFile + +class ResponseDeleteChat(BaseModel): + status: int + data: Message + +class ReponseError(BaseModel): + status: int + data: Message \ No newline at end of file diff --git a/response/ResponseDefault.py b/response/ResponseDefault.py new file mode 100644 index 0000000000000000000000000000000000000000..75750c932209b9b3ac83d0bd4809d80637c35e55 --- /dev/null +++ b/response/ResponseDefault.py @@ -0,0 +1,44 @@ +from pydantic import BaseModel + +class DataInfoUser(BaseModel): + uid: str + email: str + display_name: str + photo_url: str + +class DataIsMe(BaseModel): + user_id: int + +class DataUploadImage(BaseModel): + url: str +class Message(BaseModel): + message: str + +class CheckModel(BaseModel): + check: bool + +class DataCreateFireBaseUser(BaseModel): + localId: str + email: str + displayName: str + photoUrl: str + +class ResponseCreateFireBaseUser(BaseModel): + status: int + data: DataCreateFireBaseUser + +class ResponseInfoUser(BaseModel): + status: int + data: DataInfoUser + +class ResponseIsMe(BaseModel): + status: int + data: DataIsMe + +class ResponseUploadImage(BaseModel): + status: int + data: DataUploadImage + +class ReponseError(BaseModel): + status: int + data: Message \ No newline at end of file diff --git a/response/ResponseRecommender.py b/response/ResponseRecommender.py new file mode 100644 index 0000000000000000000000000000000000000000..9757b9a9f35449740a56263c0c9a3c899e2d285c --- /dev/null +++ b/response/ResponseRecommender.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel +from typing import List, Union + + +class ItemRecommend(BaseModel): + pro_id: int + product_name: str + + +class ListItemRecommend(BaseModel): + user_id: int + total: int + list_item: List[ItemRecommend] + \ No newline at end of file diff --git a/response/__init__.py b/response/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/response/__pycache__/ResponseChat.cpython-311.pyc b/response/__pycache__/ResponseChat.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a4901f7caceb1ddf20af270e6a69a9564fbcbfb Binary files /dev/null and b/response/__pycache__/ResponseChat.cpython-311.pyc differ diff --git a/response/__pycache__/ResponseChat.cpython-312.pyc b/response/__pycache__/ResponseChat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c10f71a6cee8103a20fc75e6cc464a15f49ad48f Binary files /dev/null and b/response/__pycache__/ResponseChat.cpython-312.pyc differ diff --git a/response/__pycache__/ResponseDefault.cpython-311.pyc b/response/__pycache__/ResponseDefault.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c397bcc1371c548aa0c772dd3cafed8252ab415c Binary files /dev/null and b/response/__pycache__/ResponseDefault.cpython-311.pyc differ diff --git a/response/__pycache__/ResponseRecommender.cpython-311.pyc b/response/__pycache__/ResponseRecommender.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51a3318dc79adec0aa071c363363d2bf17261031 Binary files /dev/null and b/response/__pycache__/ResponseRecommender.cpython-311.pyc differ diff --git a/response/__pycache__/ResponseRecommender.cpython-312.pyc b/response/__pycache__/ResponseRecommender.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1299c9488c47e1afb7cabf03f881902b39eb9f87 Binary files /dev/null and b/response/__pycache__/ResponseRecommender.cpython-312.pyc differ diff --git a/response/__pycache__/__init__.cpython-311.pyc b/response/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..158e0bbd145fbc5e7675f89eb8749eb09a260ad6 Binary files /dev/null and b/response/__pycache__/__init__.cpython-311.pyc differ diff --git a/response/__pycache__/__init__.cpython-312.pyc b/response/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..772500fa6a868b4c5854ed791be98e46dfb9ac90 Binary files /dev/null and b/response/__pycache__/__init__.cpython-312.pyc differ diff --git a/service/CallBackService.py b/service/CallBackService.py new file mode 100644 index 0000000000000000000000000000000000000000..2a08e7b4579ede8d9fb9d5779222b906f86d256b --- /dev/null +++ b/service/CallBackService.py @@ -0,0 +1,162 @@ +import httpx +import yaml +from fastapi.responses import HTMLResponse, RedirectResponse +with open("config.yaml", "r") as f: + config = yaml.safe_load(f) +base_backend_java = config["callback_urls"] +base_frontend = config["frontend"] + +async def momo_group_callback(orderId: str, resultCode:str): + + url = f"""{base_backend_java["base"]}/{base_backend_java["momo"]["group"]["android"]["callback"]}""" + params = {"orderId": orderId, + "resultCode": resultCode + } + + async with httpx.AsyncClient() as client: + response = await client.get(url, params=params) + + if response.status_code == 200: + data = response.json() + status = data.get("status") + return {"status": status} + else: + return {"error": response.status_code, "message": response.text} + + +async def momo_callback(orderId: str, resultCode:str): + url = f"""{base_backend_java["base"]}/{base_backend_java["momo"]["single"]["android"]["callback"]}""" + params = {"orderId": orderId, + "resultCode": resultCode + } + + async with httpx.AsyncClient() as client: + response = await client.get(url, params=params) + + if response.status_code == 200: + data = response.json() + status = data.get("status") + return {"status": status} + else: + return {"error": response.status_code, "message": response.text} + + +async def payos_callback(orderCode: int): + url = f"""{base_backend_java["base"]}/{base_backend_java["payos"]["single"]["android"]["callback"]}""" + params = {"orderCode": orderCode} + + async with httpx.AsyncClient() as client: + response = await client.get(url, params=params) + + return response.json() + + +async def group_payos_callback(orderCode: int): + url = f"""{base_backend_java["base"]}/{base_backend_java["payos"]["group"]["android"]["callback"]}""" + params = {"orderCode": orderCode} + + async with httpx.AsyncClient() as client: + response = await client.get(url, params=params) + + return response.json() + + +async def zalo_group_callback(app_trans_id: str): + url = f"""{base_backend_java["base"]}/{base_backend_java["zalo"]["group"]["android"]["callback"]}""" + params = {"app_trans_id": app_trans_id} + + async with httpx.AsyncClient() as client: + response = await client.get(url, params=params) + + if response.status_code == 200: + data = response.json() + status = data.get("status") + return {"status": status} + else: + return {"error": response.status_code, "message": response.text} + + +async def zalo_callback(app_trans_id: str): + url = f"""{base_backend_java["base"]}/{base_backend_java["zalo"]["single"]["android"]["callback"]}""" + params = {"app_trans_id": app_trans_id} + + async with httpx.AsyncClient() as client: + response = await client.get(url, params=params) + if response.status_code == 200: + data = response.json() + status = data.get("status") + return {"status": status} + else: + return {"error": response.status_code, "message": response.text} + + +async def vnpay_callback(params): + url = f"""{base_backend_java["base"]}/{base_backend_java["vnpay"]["single"]["android"]["callback"]}""" + async with httpx.AsyncClient() as client: + response = await client.get(url, params=params,timeout=90.0) + + + +async def vnpay_group_callback(params): + url = f"""{base_backend_java["base"]}/{base_backend_java["vnpay"]["group"]["android"]["callback"]}""" + async with httpx.AsyncClient() as client: + response = await client.get(url, params=params,timeout=90.0) + + + +from fastapi.responses import HTMLResponse, RedirectResponse + +async def android_payos_callback( status: str = None, orderCode: str = None): + + try: + result = await payos_callback(int(orderCode)) + print(result) + except Exception as e: + print("Lỗi khi gọi payos_callback:", e) + + if status != "PAID": + # Redirect khi bị hủy + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=-49""") + else : + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=1""") + + + +async def android_group_payOs_callback(status: str = None, orderCode: str = None): + try: + result = await group_payos_callback(int(orderCode)) + print(result) + except Exception as e: + print("Lỗi khi gọi payos_callback:", e) + + if status != "PAID": + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=-49""") + else : + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=1""") + +async def payos_callback(orderCode: int): + url = f"""{base_backend_java["base"]}/{base_backend_java["payos"]["single"]["android"]["callback"]}""" + params = {"orderCode": orderCode} + async with httpx.AsyncClient() as client: + response = await client.get(url, params=params) + return response.json() + +async def web_payos_callback(code: str = None, id: str = None, cancel: bool = None, status: str = None, orderCode: str = None): + query_params = [] + if code is not None: + query_params.append(f"code={code}") + if id is not None: + query_params.append(f"id={id}") + if cancel is not None: + query_params.append(f"cancel={str(cancel).lower()}") + if status is not None: + query_params.append(f"status={status}") + if orderCode is not None: + query_params.append(f"orderCode={orderCode}") + + query_string = "&".join(query_params) + try: + result = await payos_callback(int(orderCode)) + return RedirectResponse(url=f"""{base_frontend["base"]}/{base_frontend["web_redirect_paths"]["payos"]}?{query_string}""") + except Exception as e: + return RedirectResponse(url=f"""{base_frontend["base"]}/{base_frontend["web_redirect_paths"]["payos"]}?{query_string}""") diff --git a/service/ChatService.py b/service/ChatService.py new file mode 100644 index 0000000000000000000000000000000000000000..1cb0ae171d9795ac15ad5271bbebd5e03163bacc --- /dev/null +++ b/service/ChatService.py @@ -0,0 +1,759 @@ +from langchain_community.utilities.sql_database import SQLDatabase +from langchain_experimental.sql import SQLDatabaseChain +import sys +import os +import pymysql +from fastapi import HTTPException +from fastapi.encoders import jsonable_encoder +import re +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "."))) +import function.prompt.prompt_main as prompt +import function.prompt.prompt_custom as prompt_cus +os.environ["GOOGLE_API_KEY"] = "AIzaSyDAVIagntGC7kL93qmLgNZ-is1fsb7tsN4" +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +from bson import ObjectId +import os +from dotenv import load_dotenv +import os +from dotenv import load_dotenv + +import os +from dotenv import load_dotenv + +from dotenv import load_dotenv, find_dotenv +load_dotenv(find_dotenv(), override=True) +DB_HOST = os.getenv("DB_HOST") +DB_USER = os.getenv("DB_USER") +DB_PASSWORD = os.getenv("DB_PASSWORD") +DB_NAME = os.getenv("DB_NAME") +DB_PORT = os.getenv("DB_PORT") +import re + +def contains_delete(sql: str) -> bool: + return bool(re.search(r'\bdelete\b', sql, re.IGNORECASE)) +# Tạo connection string +import os +from urllib.parse import quote + +password = os.getenv("DB_PASSWORD") # VD: 'Yahana0509@' +DB_PASSWORD = quote(password) +connection_uri = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" +db = SQLDatabase.from_uri(connection_uri) +# db = SQLDatabase.from_uri("mysql+pymysql://root@127.0.0.1:3306/demohmdrinks") +from dotenv import load_dotenv +import function.filter.filter_role as filter_role_1 +import function.filter.filter_sql_injection as filter_sql_injection_1 +import function.filter.result as query_result_1 +import support.get_key as get_key +import response.ResponseChat as res_chat +from datetime import datetime +import pytz +from mongoengine import connect +import sys +import os +import nltk +import function.agent.pipeline_agent as pipeline_agent +nltk.download('punkt') +from models.Database_Entity import User, ChatHistory, DetailChat +from dotenv import load_dotenv +load_dotenv() +MONGO_URI = os.getenv("MONGO_URI", "") +connect("chatbot_hmdrinks", host=MONGO_URI) + +load_dotenv() + +#setup model +from bson import ObjectId +import random +from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGenerativeAI + + + +BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) +sys.path.insert(0, BASE_DIR) +from repository.MySQL import UserRepository +from function.prompt.prompt_syntax_insert import is_insert_related_to_product_category_variant, filter_syntax_sql +import sqlparse +import sqlparse +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.getcwd(), '..'))) +from function.prompt import prompt_detail_table + +schema_mapping = { + "user": prompt_detail_table.prompt_users, + "user_voucher": prompt_detail_table.prompt_user_voucher, + "category": prompt_detail_table.prompt_categort, + "category_translation": prompt_detail_table.prompt_category_translation, + "cart": prompt_detail_table.prompt_cart, + "cart_item": prompt_detail_table.prompt_cart_item, + "orders":prompt_detail_table.prompt_orders, + "order_item": prompt_detail_table.prompt_order_item, + "payment": prompt_detail_table.prompt_payments, + "favourite": prompt_detail_table.prompt_favourite, + "favourite_item": prompt_detail_table.prompt_fav_item, + "post": prompt_detail_table.prompt_post, + "post_translation": prompt_detail_table.prompt_post_translation, + "product": prompt_detail_table.prompt_product, + "product_translation": prompt_detail_table.prompt_product_translation, + "shipment": prompt_detail_table.prompt_shipment, + "product_variants": prompt_detail_table.prompt_product_variants, + "review": prompt_detail_table.prompt_review, + "user_coin": prompt_detail_table.prompt_user_coin, + "absence": prompt_detail_table.prompt_absence, + "cart_group": prompt_detail_table.prompt_cart_group, + "cart_item_group": prompt_detail_table.prompt_cartitem_group, + "group_orders": prompt_detail_table.prompt_group_orders, + "payments_group": prompt_detail_table.prompt_payments_group, + "group_order_members":prompt_detail_table.prompt_group_orders_member, + "shipment_group":prompt_detail_table.prompt_shipment_group, + "shipper_attendance":prompt_detail_table.prompt_shipper_attendance, + "shipper_commission_detail":prompt_detail_table.prompt_shipper_commission_detail, + "shipper_salary_summary":prompt_detail_table.prompt_shipper_salary_summary, + "voucher": prompt_detail_table.prompt_voucher + } + + +def get_schemas_from_sql(sql_query: str, schema_mapping: dict): + import sqlglot + + parsed_query = sqlglot.parse_one(sql_query, read="sqlite") + + # Lấy danh sách bảng duy nhất trong query + table_names = list({t.name for t in parsed_query.find_all(sqlglot.exp.Table)}) + + schemas_used = {} + for table in table_names: + if table in schema_mapping: + schemas_used[table] = schema_mapping[table] + else: + print(f"⚠️ Warning: Table '{table}' not found in schema_mapping") + + # Gom toàn bộ schema thành 1 chuỗi duy nhất + all_schemas = "\n\n".join( + [f"Schema for table '{table}':\n{schemas_used[table]}" for table in schemas_used] + ) + + return all_schemas + + +def build_sql_fix_prompt(schemas_result: dict, sql: str) -> str: + + prompt = f""" +Bạn là một chuyên gia cơ sở dữ liệu. + +Dưới đây là mô tả schema chi tiết của các bảng có trong hệ thống: + +{schemas_result} + +--- + +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: + +```sql +{sql.strip()} +Yêu cầu của bạn là: + +Dựa trên các schema ở trên, hãy kiểm tra và chỉnh sửa câu SQL sao cho: +Tên bảng, tên cột phải chính xác theo schema. +Logic và mục đích của truy vấn được giữ nguyên. +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). +Trả lời dưới dạng một truy vấn SQL duy nhất. +""".strip() + return prompt + +async def execute_query_user(user_input: str, user_id: int, languages: str, role: str): + api_key = get_key.get_random_api_key() + os.environ["GOOGLE_API_KEY"] = api_key + llm1 = ChatGoogleGenerativeAI(model='gemini-2.5-flash-preview-04-17',temperature=0.6) + # db = SQLDatabase.from_uri("mysql+pymysql://root@127.0.0.1:3306/demohmdrinks") + db = SQLDatabase.from_uri(connection_uri) + PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_input) + check_insert = is_insert_related_to_product_category_variant(user_input) + + db_config = { + "host": os.getenv("DB_HOST"), + "user": os.getenv("DB_USER"), + "database": os.getenv("DB_NAME"), + "password": os.getenv("DB_PASSWORD"), + "port": int(os.getenv("DB_PORT", 3306)), + "charset": "utf8mb4", + "cursorclass": pymysql.cursors.DictCursor, + } + + def execute_query_with_pymysql(query, multi=False): + connection = pymysql.connect(**db_config) + try: + with connection.cursor() as cursor: + results = [] + if multi: + statements = sqlparse.split(query) + for stmt in statements: + stmt = stmt.strip() + if stmt: + try: + cursor.execute(stmt) + try: + results.append(cursor.fetchall()) + except pymysql.ProgrammingError: + results.append("✅ Executed") + except Exception as e: + results.append(f"❌ Error in query: {stmt}\n{str(e)}") + else: + try: + cursor.execute(query) + results = cursor.fetchall() + except Exception as e: + return f"❌ Error executing query: {str(e)}" + connection.commit() + return results + except pymysql.MySQLError as e: + return f"❌ MySQL Error: {str(e)}" + finally: + connection.close() + + def clean_sql(sql: str) -> str: + sql = re.sub(r"```sql", "", sql, flags=re.IGNORECASE) + sql = re.sub(r'%%s', r'%s', sql) + sql = re.sub(r"```", "", sql) + return sql.strip() + + def extract_sql_from_response(data): + match = re.search(r"SQLQuery:\s*(.*)", data, re.DOTALL) + return clean_sql(match.group(1)) if match else None + + def extract_sql_from_error(error_msg): + match = re.search(r"```sql\n(.*?)```", error_msg, re.DOTALL) + return clean_sql(match.group(1)) if match else None + + def process_and_execute_sql(sql): + sql_clean = clean_sql(sql) + result = get_schemas_from_sql(sql_clean, schema_mapping) + prompt = build_sql_fix_prompt(result,sql =sql_clean) + from function.advance_shopping.call_gemini import tool_call + data = tool_call.generate(prompt = prompt) + sql_clean = clean_sql(data) + print("SQL step2: ", sql_clean) + if contains_delete(sql_clean): + 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." + if re.search(r"\\bIF\\b.*\\bTHEN\\b", sql_clean, re.IGNORECASE): + return "❌ Lỗi: Không được dùng IF...THEN trong SQL. Vui lòng chia nhỏ truy vấn." + + if check_insert: + check_syntax = filter_syntax_sql(sql_clean, PROMPT_CUSTOM, user_input) + if check_syntax is True: + try: + connection = pymysql.connect(**db_config) + with connection.cursor() as cursor: + statements = sqlparse.split(sql_clean) + results = [] + for stmt in statements: + stmt = stmt.strip() + if stmt: + try: + cursor.execute(stmt) + try: + results.append(cursor.fetchall()) + except: + results.append("✅ OK") + except Exception as e: + return f"❌ Lỗi tại truy vấn: `{stmt}`\nChi tiết: {str(e)}" + connection.commit() + return results + except Exception as e: + return f"❌ Lỗi khi thực thi từng truy vấn: {str(e)}" + finally: + connection.close() + else: + try: + regenerated_data = db_chain.run(f""" + Role: {text_role} + Language: {languages} + Question: {user_input}. + """) + regenerated_sql = extract_sql_from_response(regenerated_data) + if regenerated_sql: + return process_and_execute_sql(regenerated_sql) + else: + return "❌ Lỗi: Không thể tạo lại truy vấn hợp lệ." + except Exception as regen_error: + return f"❌ Lỗi khi tạo lại truy vấn: {str(regen_error)}" + else: + return execute_query_with_pymysql(sql_clean, multi=True) + + if "Do not use IF...THEN" not in PROMPT_CUSTOM.template: + PROMPT_CUSTOM.template += ( + "\n\n⚠️ Note: Do NOT use IF...THEN...ELSE...END in SQL. " + "Only use plain SELECT, INSERT, UPDATE, DELETE, SET statements." + ) + + db_chain = SQLDatabaseChain.from_llm(llm=llm1, db=db, prompt=PROMPT_CUSTOM) + text_role = f"{role} (userId = {user_id})" if role == "ADMIN" else f"{role} (userId = {user_id}), not role ADMIN" + + try: + data = db_chain.run(f""" + Role: {text_role} + Language: {languages} + Question: {user_input}. + """) + extracted_sql = extract_sql_from_response(data) + if extracted_sql: + return process_and_execute_sql(extracted_sql) + else: + return data + + except Exception as e: + error_message = str(e) + extracted_sql = extract_sql_from_error(error_message) + fix_sql = extracted_sql.replace("```","") + fix_sql = re.sub(r"```sql", "", fix_sql) + fix_sql = re.sub(r'%%s', r'%s', fix_sql) + if contains_delete(fix_sql): + 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." + if extracted_sql: + + return process_and_execute_sql(fix_sql) + else: + return f"❌ Lỗi không thể thực thi truy vấn: {error_message}" + + +async def create_new_chat_history(user_id: int) -> str: + """ + Tạo một đoạn chat mới cho user_id và trả về ObjectId của đoạn chat. + """ + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=404, detail="User not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id).first() + if not user: + user = User(id=ObjectId(), user_id=user_id, user_name=f"User_{user_id}") + user.save() + random_name_chat = str(random.randint(10**14, 10**15 - 1)) + name_chat = f"Chat_{random_name_chat}" + new_chat = ChatHistory(id=ObjectId(), user=user, name_chat=name_chat) + new_chat.save() + + return res_chat.CreateNewChat(idMongo=str(new_chat.id), chat_name=name_chat) + + +async def update_chat_name(chat_id: str, new_name: str,user_id:int) -> str: + """ + Cập nhật name_chat của một ChatHistory dựa trên chat_id. + """ + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=404, detail="User not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + + chat = ChatHistory.objects(_id=ObjectId(chat_id)).first() + + if not chat: + raise HTTPException(status_code=404, detail="Chat not found in MongoDB") + + chat.name_chat = new_name + chat.save() + + return f"Updated chat name to {new_name}" + + +async def soft_delete_chat(chat_id: str,user_id:int): + """ + Cập nhật `is_deleted=True` và `date_deleted` cho `ChatHistory` và các `DetailChat` liên quan. + """ + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + + check_history_id = UserRepository.getChatHistory(user_id,chat_id) + if check_history_id is None: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + + chat = ChatHistory.objects(_id=ObjectId(chat_id)).first() + + if not chat: + raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MongoDB") + chat.is_deleted = True + chat.date_deleted = datetime.now(pytz.UTC) + chat.save() + + DetailChat.objects(chat_history=chat).update( + set__is_deleted=True, + set__date_deleted=datetime.now(pytz.UTC) + ) + + return {"message": "Chat and related details marked as deleted"} +from typing import Optional +from models.Database_Entity import StopSignal +from datetime import datetime, timedelta +import asyncio +async def chat_with_user( + user_input: str, + user_id: int, + languages: str, + role: str, + token: str, + chat_history_id: str = None, + stop_event: Optional[asyncio.Event] = None + +) -> str: + """ + 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. + """ + + if role not in ["ADMIN", "CUSTOMER", "SHIPPER"]: + raise HTTPException(status_code=400, detail="ROLE not valid") + user_id = int(user_id) + if languages not in ["VN", "EN"]: + raise HTTPException(status_code=400, detail="Language not valid") + + if not user_input: + raise HTTPException(status_code=400, detail="User input empty") + if not isinstance(user_id, int) or user_id <= 0: + raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer") + + languages = "Vietnamese" if languages == "VN" else "English" + + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + + check_history_id = UserRepository.getChatHistory(user_id,chat_history_id) + if check_history_id is None: + raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id).first() + if not user: + return {"error": "User not found or has been deleted in MongoDB"} + + chat_history = None + if chat_history_id: + try: + chat_history_obj_id = ObjectId(chat_history_id) # Chuyển đổi sang ObjectId + chat_history = ChatHistory.objects(_id=chat_history_obj_id, user=user).first() + except Exception as e: + + print(f"⚠️ Invalid chat_history_id: {e}") + if not chat_history: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB") + await asyncio.sleep(0.1) + if stop_event and stop_event.is_set(): + raise asyncio.CancelledError("⛔ Task was cancelled before processing") + await asyncio.sleep(0.1) + if StopSignal.objects(chat_history=chat_history_id, is_stopped=True).first(): + print("🛑 Dừng vì có StopSignal trong DB.") + raise asyncio.CancelledError("⛔ Task was cancelled before processing") + result_final = await pipeline_agent.multi_query_user( + user_input, user_id, role, languages, chat_history_id, token,stop_event + ) + detail_chat = DetailChat( + id=ObjectId(), + chat_history=chat_history, + you_message=user_input, + ai_message=result_final, + timestamp=datetime.now(pytz.UTC) + ) + detail_chat.save() + chat_history_messages = DetailChat.objects(chat_history=chat_history).order_by('timestamp') + + + def convert_to_vn_time(timestamp): + return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S') + + sorted_messages = sorted(chat_history_messages, key=lambda msg: msg.timestamp, reverse=True) + + formatted_messages = [ + { + "index": i + 1, # Đánh số từ 1 + "id": str(msg.id), + "you_message": msg.you_message, + "ai_message": msg.ai_message, + "timestamp": convert_to_vn_time(msg.timestamp) + } + for i, msg in enumerate(sorted_messages) + ] + + return jsonable_encoder({ + "new_message": { + "id": str(detail_chat.id), + "you_message": detail_chat.you_message, + "ai_message": detail_chat.ai_message, + "timestamp": convert_to_vn_time(detail_chat.timestamp) + }, + "previous_messages": formatted_messages + }) + +from bson import ObjectId + + +async def get_chat_details(chat_id: str,user_id:int): + """ + Lấy tất cả `DetailChat` thuộc `ChatHistory` có `chat_id`, chỉ lấy bản ghi `is_deleted=False`. + """ + + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + + check_history_id = UserRepository.getChatHistory(user_id,chat_id) + if check_history_id is None: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + + chat = ChatHistory.objects(_id=ObjectId(chat_id), is_deleted=False).first() + + if not chat: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB") + + chat_details = DetailChat.objects(chat_history=chat, is_deleted=False) + def convert_to_vn_time(timestamp): + return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S') + + list_detail_response = [ + res_chat.DetailResponse( + id=str(index + 1), # ✅ Đánh số lại từ 1 + you_message=detail.you_message, + ai_message=detail.ai_message, + timestamp=convert_to_vn_time(detail.timestamp) # ✅ Chuyển sang GMT+7 + ) + for index, detail in enumerate(sorted(chat_details, key=lambda d: d.timestamp, reverse=True)) # ✅ Sắp xếp giảm dần +] + + return res_chat.ListDetailResponse( + chat_id=str(chat.id), + chat_name=chat.name_chat, + list_detail_response=list_detail_response +) + + + +async def regenerate( + user_question_new: str, + user_id: int, + languages: str, + role: str, + token: str, + chat_history_id: str = None, + stop_event: Optional[asyncio.Event] = None +) -> str: + """ + 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. + """ + PROMPT_CUSTOM = await prompt_cus.get_prompt_custom(user_question_new) + if role not in ["ADMIN", "CUSTOMER", "SHIPPER"]: + raise HTTPException(status_code=400, detail="ROLE not valid") + user_id = int(user_id) + if languages not in ["VN", "EN"]: + raise HTTPException(status_code=400, detail="Language not valid") + + if not user_question_new: + raise HTTPException(status_code=400, detail="User input empty") + if not isinstance(user_id, int) or user_id <= 0: + raise HTTPException(status_code=400, detail="Invalid user_id: must be a positive integer") + + languages = "Vietnamese" if languages == "VN" else "English" + + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + + check_history_id = UserRepository.getChatHistory(user_id,chat_history_id) + if check_history_id is None: + raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MySQL") + + user = User.objects(user_id=user_id).first() + if not user: + return {"error": "User not found or has been deleted in MongoDB"} + + chat_history = None + if chat_history_id: + try: + chat_history_obj_id = ObjectId(chat_history_id) # Chuyển đổi sang ObjectId + chat_history = ChatHistory.objects(_id=chat_history_obj_id, user=user).first() + except Exception as e: + + print(f"⚠️ Invalid chat_history_id: {e}") + if not chat_history: + raise HTTPException(status_code=400, detail="Chat history not found or has been deleted in MongoDB") + # filtered_input = filter_sql_injection_1.filter_sql_injection(user_input) + # filtered_role_input = filter_role_1.filter_role(filtered_input) + # result = await execute_query_user(filtered_role_input, user_id, languages, role) + # result_final = query_result_1.query_result(user_input, result) + if chat_history: + last_chat = ( + DetailChat.objects(chat_history=chat_history, is_deleted=False) + .order_by("-timestamp") + .first() + ) + + await asyncio.sleep(0.1) + if stop_event and stop_event.is_set(): + raise asyncio.CancelledError("⛔ Task was cancelled before processing") + await asyncio.sleep(0.1) + if StopSignal.objects(chat_history=chat_history_id, is_stopped=True).first(): + print("🛑 Dừng vì có StopSignal trong DB.") + raise asyncio.CancelledError("⛔ Task was cancelled before processing") + result_final = await pipeline_agent.multi_query_user(user_question_new,user_id,role,languages,chat_history_id,token,stop_event) + last_chat.update(set__you_message=user_question_new, set__ai_message=result_final, set__timestamp = datetime.now(pytz.UTC)) + + last_chat_result = ( + DetailChat.objects(chat_history=chat_history, is_deleted=False) + .order_by("-timestamp") + .first() + ) + + chat_history_messages = DetailChat.objects(chat_history=chat_history).order_by('timestamp') + + + def convert_to_vn_time(timestamp): + return (timestamp + timedelta(hours=7)).strftime('%Y-%m-%dT%H:%M:%S') + + sorted_messages = sorted(chat_history_messages, key=lambda msg: msg.timestamp, reverse=True) + + formatted_messages = [ + { + "index": i + 1, # Đánh số từ 1 + "id": str(msg.id), + "you_message": msg.you_message, + "ai_message": msg.ai_message, + "timestamp": convert_to_vn_time(msg.timestamp) + } + for i, msg in enumerate(sorted_messages) + ] + + return jsonable_encoder({ + "new_message": { + "id": str(last_chat_result.id), + "you_message": last_chat_result.you_message, + "ai_message": last_chat_result.ai_message, + "timestamp": convert_to_vn_time(last_chat_result.timestamp) + }, + "previous_messages": formatted_messages + }) + +from bson import ObjectId + + +async def get_user_chat_history(user_id: int): + """ + API lấy danh sách tất cả các đoạn chat của một user_id. + """ + check = UserRepository.getUserByUserId(user_id) + if check is None: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MySQL") + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + chat_histories = ChatHistory.objects(user=user, is_deleted=False) + chat_list = [ + res_chat.ChatResponse( + chat_id=str(chat.id), + chat_name=chat.name_chat, + timestamp=chat.date_deleted if chat.is_deleted else chat.id.generation_time + ) + for chat in chat_histories + ] + + return res_chat.UserChatHistoryResponse( + user_id=user.user_id, + user_name=user.user_name, + chat_list=chat_list + ) + + +from bson import ObjectId + +async def get_chat_details_text(chat_id: str, user_id: int): + """ + 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. + """ + # Kiểm tra xem user có tồn tại không + user = User.objects(user_id=user_id, is_deleted=False).first() + if not user: + raise HTTPException(status_code=400, detail="User not found or has been deleted in MongoDB") + + # Kiểm tra xem chat history có tồn tại không + chat = ChatHistory.objects(_id=ObjectId(chat_id), user=user, is_deleted=False).first() + if not chat: + raise HTTPException(status_code=400, detail="Chat not found or has been deleted in MongoDB") + chat_details = DetailChat.objects(chat_history=chat, is_deleted=False).order_by('timestamp') + if not chat_details: + return list() + + # Gom tất cả các câu hỏi và câu trả lời vào danh sách + chat_text_list = [] + for index, detail in enumerate(chat_details,start=1): + chat_text_list.append({ + "order":str(index), + "timestamp": detail.timestamp.strftime("%Y-%m-%d %H:%M:%S"), + "you_message": detail.you_message, + "ai_message": detail.ai_message + }) + + return chat_text_list + + +import asyncio +import os +import subprocess +from datetime import datetime +from pathlib import Path +from function.analyze import main +from function.analyze.gemini import result_analyze +async def generate_and_save_code(question: str, user_id: int, role, languages: str, filename: str = "analyze_result.py"): + code_test, folder_name = await main.analyze( + question, + user_id, + languages, + role + ) + + code_clean = code_test.strip() + if code_clean.startswith("```python"): + code_clean = code_clean[9:].strip() + if code_clean.endswith("```"): + code_clean = code_clean[:-3].strip() + + # code_clean = code_clean.replace("else:", "").strip() + code_clean = code_clean.replace("os_path:", "os.path").strip() + encoding_fix = 'import sys\nsys.stdout.reconfigure(encoding="utf-8")\n\n' + encoding_fix1= 'import numpy as np\n\n' + code_clean = encoding_fix + encoding_fix1 + code_clean + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + output_dir = Path(f"./Temp/{folder_name}_{timestamp}") + output_dir.mkdir(parents=True, exist_ok=True) + + + file_path = output_dir / filename + with open(file_path, "w", encoding="utf-8") as f: + f.write(code_clean) + + + env = os.environ.copy() + env["OUTPUT_DIR"] = str(output_dir) + result = subprocess.run( + ["python", filename], + capture_output=True, + text=True, + env=env, + cwd=output_dir, + encoding="utf-8", + errors="replace" + ) + + output_folder = str(output_dir) + absolute_path = os.path.abspath(output_folder) + final_path = os.path.join(absolute_path, "test5") + result_final = result_analyze.generate(image_folder=final_path,question=question) + return result_final,final_path \ No newline at end of file diff --git a/service/GroupService.py b/service/GroupService.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/service/RedirectService.py b/service/RedirectService.py new file mode 100644 index 0000000000000000000000000000000000000000..b9c35d139c288e02dacb10f9c04c3dc493bd317b --- /dev/null +++ b/service/RedirectService.py @@ -0,0 +1,92 @@ +import httpx +import yaml + +from fastapi.responses import HTMLResponse, RedirectResponse +from urllib.parse import urlencode +with open("config.yaml", "r") as f: + config = yaml.safe_load(f) +base_backend_java = config["callback_urls"] +base_frontend = config["frontend"] + +async def android_vnpay_ipn_redirect(params): + if params.get("vnp_ResponseCode") != "00": + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=-49""") + else: + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=1""") + + +async def android_vnpay_group_ipn_redirect(params): + if params.get("vnp_ResponseCode") != "00": + return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=-49""") + else: + return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=1""") + + +async def web_vnpay_ipn_redirect(params): + query_string = urlencode(params) + + redirect_url = f"""{base_frontend["base"]}/{base_frontend["web_redirect_paths"]["vnpay"]}?{query_string}""" + return RedirectResponse(url=redirect_url) + + + +async def android_momo_redirect(query_params): + order_id = query_params.get("orderId") + result_code = query_params.get("resultCode") + print("order_id:", order_id) + print("result_code:", result_code) + if result_code != "0": + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=-49""") + else: + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=1""") + + +async def android_group_momo_redirect(query_params): + order_id = query_params.get("orderId") + result_code = query_params.get("resultCode") + print("order_id:", order_id) + print("result_code:", result_code) + if result_code != "0": + return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=-49""") + else: + return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=1""") + + +async def web_momo_redirect(query_params): + query_string = urlencode(query_params) + redirect_url = f"""{base_frontend["base"]}/{base_frontend["web_redirect_paths"]["momo"]}?{query_string}""" + return RedirectResponse(url=redirect_url) + +from CallBackService import zalo_callback + + +async def zalo_redirect( + status, apptransId +): + if int(status) != 1: + await zalo_callback(str(apptransId)) + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=-49""") + else : + return RedirectResponse(url=f"""{base_frontend["android_redirect_base"]}?status=1""") + + +async def zalo_group_redirect( + status, apptransId +): + + if int(status) != 1: + await zalo_callback(str(apptransId)) + return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=-49""") + else : + return RedirectResponse(url=f"""{base_frontend["android_group_redirect_base"]}?status=1""") + + +async def zalo_web_redirect(query_params): + base_url = f"""{base_frontend["base"]}/{base_frontend["web_redirect_paths"]["zalo"]}""" + query_string = urlencode(query_params) + redirect_url = f"{base_url}?{query_string}" + status = query_params.get("status") + apptransid = query_params.get("apptransid") + if int(status) != 1: + await zalo_callback(str(apptransid)) + return RedirectResponse(url=redirect_url) \ No newline at end of file diff --git a/service/RefundService.py b/service/RefundService.py new file mode 100644 index 0000000000000000000000000000000000000000..cbe630197327b11f784bb4c8a79718637c41d17e --- /dev/null +++ b/service/RefundService.py @@ -0,0 +1,224 @@ +from pydantic import BaseModel +import uuid, hmac, hashlib, requests + +# --- Thông tin định danh --- +PARTNER_CODE = "MOMO" +ACCESS_KEY = "F8BBA842ECF85" +SECRET_KEY = "K951B6PE1waDMi640xX08PD3vg6EkVlz" + +# --- Model yêu cầu hoàn tiền --- +class RefundRequest(BaseModel): + partnerCode: str + orderId: str + requestId: str + amount: int + transId: int + description: str + lang: str = "vi" + +# --- Tạo chữ ký --- +def generate_refund_signature(data: dict) -> str: + raw_data = ( + f"accessKey={ACCESS_KEY}&amount={data['amount']}&description={data['description']}" + f"&orderId={data['orderId']}&partnerCode={PARTNER_CODE}" + f"&requestId={data['requestId']}&transId={data['transId']}" + ) + return hmac.new(SECRET_KEY.encode(), raw_data.encode(), hashlib.sha256).hexdigest() + +def call_momo_refund(amount: int, trans_id: int, description: str = "Hoàn tiền giao dịch"): + order_id = str(uuid.uuid4()) + request_id = str(uuid.uuid4()) + + refund = RefundRequest( + partnerCode=PARTNER_CODE, + orderId=order_id, + requestId=request_id, + amount=amount, + transId=trans_id, + description=description, + ) + + payload = refund.dict() + payload["signature"] = generate_refund_signature(payload) + + response = requests.post( + "https://test-payment.momo.vn/v2/gateway/api/refund", + json=payload, + headers={"Content-Type": "application/json"}, + timeout=90, + ) + + return response.json() + + +# result = call_momo_refund(amount=100000, trans_id=4506777858) +# print(result) + + +import hmac +import hashlib +import requests +from datetime import datetime +import uuid + +# ====== CẤU HÌNH MẶC ĐỊNH ====== +VNP_URL = "https://sandbox.vnpayment.vn/merchant_webapi/api/transaction" +VNP_TMN_CODE = 'MCQ3SXQ1' +VNP_SECRET_KEY = '2QS3MSRVQQAFN36I5GA02DWB353KO4FD' +IP_ADDR = "127.0.0.1" + +# ====== HÀM TẠO CHECKSUM HMAC SHA512 ====== +def hmac_sha512(key: str, data: str) -> str: + return hmac.new(key.encode(), data.encode(), hashlib.sha512).hexdigest() + +# ====== HÀM HOÀN TIỀN VNPAY ====== +def call_vnpay_refund( + txn_ref: str, + amount: int, + transaction_date: str, + create_by: str, + order_info: str, + transaction_no: str = "", # optional + transaction_type: str = "02", # "02": full refund, "03": partial + vnp_url: str = VNP_URL, + vnp_tmn_code: str = VNP_TMN_CODE, + vnp_secret_key: str = VNP_SECRET_KEY, + ip_addr: str = IP_ADDR, +): + # Lấy thời gian hiện tại (vnp_CreateDate) + now = datetime.now().strftime("%Y%m%d%H%M%S") + + # Tạo request body + params = { + "vnp_RequestId": str(uuid.uuid4().hex[:32]), + "vnp_Version": "2.1.0", + "vnp_Command": "refund", + "vnp_TmnCode": vnp_tmn_code, + "vnp_TransactionType": transaction_type, + "vnp_TxnRef": txn_ref, + "vnp_Amount": str(amount), # nhân 100 nếu VNP yêu cầu + "vnp_TransactionNo": transaction_no, + "vnp_TransactionDate": transaction_date, + "vnp_CreateBy": create_by, + "vnp_CreateDate": now, + "vnp_IpAddr": ip_addr, + "vnp_OrderInfo": order_info + } + + sign_data = "|".join([ + params["vnp_RequestId"], + params["vnp_Version"], + params["vnp_Command"], + params["vnp_TmnCode"], + params["vnp_TransactionType"], + params["vnp_TxnRef"], + params["vnp_Amount"], + params["vnp_TransactionNo"], + params["vnp_TransactionDate"], + params["vnp_CreateBy"], + params["vnp_CreateDate"], + params["vnp_IpAddr"], + params["vnp_OrderInfo"] + ]) + + + params["vnp_SecureHash"] = hmac_sha512(vnp_secret_key, sign_data) + response = requests.post(vnp_url, json=params, headers={"Content-Type": "application/json"}) + return { + "status_code": response.status_code, + "data": response.json() + } + + + +import hmac +import hashlib +import requests +import time +from datetime import datetime +import random +import string + +# Cấu hình tài khoản ZaloPay +APP_ID = "2553" +KEY1 = "PcY4iZIKFCIdgZvA6ueMcMHHUbRLYjPL" +QUERY_ENDPOINT = "https://sb-openapi.zalopay.vn/v2/query" +REFUND_ENDPOINT = "https://sb-openapi.zalopay.vn/v2/refund" + +def generate_mac(data: str, key: str) -> str: + return hmac.new(key.encode(), data.encode(), hashlib.sha256).hexdigest() + +def generate_m_refund_id(): + date_part = datetime.now().strftime("%y%m%d") + random_part = ''.join(random.choices(string.digits, k=10)) + return f"{date_part}_{APP_ID}_{random_part}" + +def call_zalopay_refund(app_trans_id: str, amount: int, description: str, refund_fee_amount: int = None): + """ + Hàm thực hiện hoàn tiền giao dịch ZaloPay dựa trên app_trans_id. + + Args: + app_trans_id (str): Mã giao dịch của ứng dụng. + amount (int): Số tiền hoàn lại (đơn vị: VND). + description (str): Mô tả lý do hoàn tiền. + refund_fee_amount (int, optional): Số tiền phí hoàn (nếu có). + + Returns: + dict: Kết quả phản hồi từ ZaloPay. + """ + # Bước 1: Truy vấn để lấy zp_trans_id + mac_input = f"{APP_ID}|{app_trans_id}|{KEY1}" + mac = generate_mac(mac_input, KEY1) + + query_payload = { + "app_id": APP_ID, + "app_trans_id": app_trans_id, + "mac": mac + } + + try: + query_response = requests.post(QUERY_ENDPOINT, data=query_payload) + query_result = query_response.json() + zp_trans_id = query_result.get("zp_trans_id") + + if not zp_trans_id: + print("❌ Không tìm thấy zp_trans_id từ app_trans_id.") + return {"code": -1, "message": "Không tìm thấy zp_trans_id"} + + except Exception as e: + print("❌ Lỗi khi truy vấn giao dịch:", e) + return {"code": -2, "message": "Lỗi truy vấn giao dịch"} + + timestamp = int(time.time() * 1000) + m_refund_id = generate_m_refund_id() + + if refund_fee_amount is not None: + hmac_data = f"{APP_ID}|{zp_trans_id}|{amount}|{refund_fee_amount}|{description}|{timestamp}" + else: + hmac_data = f"{APP_ID}|{zp_trans_id}|{amount}|{description}|{timestamp}" + + refund_mac = generate_mac(hmac_data, KEY1) + + refund_data = { + "app_id": APP_ID, + "m_refund_id": m_refund_id, + "zp_trans_id": zp_trans_id, + "amount": amount, + "timestamp": timestamp, + "description": description, + "mac": refund_mac + } + + if refund_fee_amount is not None: + refund_data["refund_fee_amount"] = refund_fee_amount + + try: + headers = { + "Content-Type": "application/x-www-form-urlencoded" + } + refund_response = requests.post(REFUND_ENDPOINT, data=refund_data, headers=headers) + return refund_response.json() + + except Exception as e: + print("❌ Lỗi khi gửi hoàn tiền:", e) + return {"code": -3, "message": "Lỗi hoàn tiền"} \ No newline at end of file diff --git a/service/__init__.py b/service/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/service/__pycache__/CallBackService.cpython-311.pyc b/service/__pycache__/CallBackService.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2334f23083dd2f62f13cbf352532d36dbbcd4a61 Binary files /dev/null and b/service/__pycache__/CallBackService.cpython-311.pyc differ diff --git a/service/__pycache__/ChatService.cpython-311.pyc b/service/__pycache__/ChatService.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7947a711d711d3619a6893e4abe9bf3fdf2d20b2 Binary files /dev/null and b/service/__pycache__/ChatService.cpython-311.pyc differ diff --git a/service/__pycache__/RedirectService.cpython-311.pyc b/service/__pycache__/RedirectService.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c420717c473be05fadcb672b623afa051792465 Binary files /dev/null and b/service/__pycache__/RedirectService.cpython-311.pyc differ diff --git a/service/__pycache__/RefundService.cpython-311.pyc b/service/__pycache__/RefundService.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec7fb876c56d31f8e4d75774094df482b274068a Binary files /dev/null and b/service/__pycache__/RefundService.cpython-311.pyc differ diff --git a/service/__pycache__/__init__.cpython-311.pyc b/service/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52c354f635a12c7012cc0676e55c271f37ef1142 Binary files /dev/null and b/service/__pycache__/__init__.cpython-311.pyc differ