import os import json import logging from pathlib import Path import chromadb from supabase import create_client, Client logger = logging.getLogger(__name__) CURRENT_DIR = Path(os.path.dirname(os.path.abspath(__file__))) DB_PATH = CURRENT_DIR / "travel_db" # =========================================================================== # CẤU HÌNH KẾT NỐI SUPABASE # =========================================================================== # Lấy từ Environment Variables (Secrets trên Hugging Face) SUPABASE_URL = os.getenv("SUPABASE_URL") SUPABASE_KEY = os.getenv("SUPABASE_KEY") if not SUPABASE_URL or not SUPABASE_KEY: logger.warning("⚠️ SUPABASE_URL hoặc SUPABASE_KEY chưa được thiết lập. Hãy kiểm tra Secrets trên Hugging Face.") try: if SUPABASE_URL and SUPABASE_KEY: supabase_client: Client = create_client(SUPABASE_URL, SUPABASE_KEY) logger.info("🌐 Đã kết nối tới Supabase Cloud thành công!") except Exception as e: logger.error(f"❌ Không thể khởi tạo kết nối Supabase: {e}") # --------------------------------------------------------------------------- # 1. Khởi tạo ChromaDB Client (Singleton - Giữ nguyên bộ nhớ Vector) # --------------------------------------------------------------------------- chroma_client = None saigon_collection = None try: chroma_client = chromadb.PersistentClient(path=str(DB_PATH)) saigon_collection = chroma_client.get_collection(name="saigon_places") except Exception as e: logger.error(f"Lỗi ChromaDB: {e}. Vui lòng đảm bảo đã tạo Vector DB thành công!") # --------------------------------------------------------------------------- # 2. Bộ nạp dữ liệu từ Supabase thay thế hoàn toàn cho JSONL cũ # --------------------------------------------------------------------------- _food_db, _act_db, _drink_db = None, None, None def _load_supabase_table_to_dict(table_name: str) -> dict: """ Tải toàn bộ dữ liệu từ một bảng trên Supabase và chuẩn hóa thuộc tính """ db = {} logger.info(f"📥 Đang tải dữ liệu thời gian thực từ bảng Supabase: '{table_name}'...") page_size = 1000 offset = 0 all_records = [] # Thực hiện phân trang (Pagination) để kéo trọn vẹn dữ liệu từ Cloud về while True: try: response = supabase_client.table(table_name).select("*").range(offset, offset + page_size - 1).execute() data = response.data if not data: break all_records.extend(data) if len(data) < page_size: break offset += page_size except Exception as e: logger.error(f"⚠️ Không thể đọc bảng '{table_name}' từ Supabase: {e}") break # Chuẩn hóa dữ liệu sang cấu trúc Dictionary để các Agent gọi .get() tìm kiếm nhanh for record in all_records: # Cơ chế Fallback thông minh: Chấp nhận cả viết hoa (Name) lẫn viết thường (name) từ giao diện name = record.get("Name") or record.get("name") if not name: continue address = record.get("Address") or record.get("address") or "N/A" rating = record.get("Rating") or record.get("rating") avg_price = record.get("avg_price") district = record.get("district") or "N/A" place_id = record.get("id") # <--- LẤY ID TỪ SUPABASE ĐỂ LÀM external_place_id # Tạo cấu trúc đồng bộ tuyệt đối cho tất cả Agent normalized = { "id": place_id, "Name": name, "name": name, "Address": address, "address": address, "Rating": float(rating) if rating is not None else 0.0, "rating": float(rating) if rating is not None else 0.0, "district": district, "avg_price": int(avg_price) if avg_price is not None else 100000, "category": record.get("category", ""), "Latitude": record.get("Latitude") or record.get("latitude"), "Longitude": record.get("Longitude") or record.get("longitude"), } # Chuẩn hóa trường URL ảnh (Xử lý cả dạng mảng lẫn chuỗi đơn) url_val = record.get("URL") or record.get("url") or record.get("image_url") if isinstance(url_val, list): normalized["URL"] = url_val elif isinstance(url_val, str): normalized["URL"] = [url_val] else: normalized["URL"] = [None] # Chuẩn hóa trường tags JsonB phức tạp tags = record.get("tags", {}) if isinstance(tags, str): try: tags = json.loads(tags) except: tags = {} normalized["tags"] = tags if isinstance(tags, dict) else {} # Lưu vào dict bộ nhớ tạm với key là tên địa điểm db[name] = normalized logger.info(f" ↳ Kết nối thành công! Đã đồng bộ {len(db)} địa điểm từ '{table_name}'.") return db # --------------------------------------------------------------------------- # Các cổng xuất dữ liệu công khai (API Interface cho các Agent sử dụng) # --------------------------------------------------------------------------- def get_food_db() -> dict: global _food_db if _food_db is None: # Cập nhật tên bảng thức ăn chính xác _food_db = _load_supabase_table_to_dict("places_food") return _food_db def get_act_db() -> dict: global _act_db if _act_db is None: # Cập nhật tên bảng vui chơi chính xác _act_db = _load_supabase_table_to_dict("places_activity") return _act_db def get_drink_db() -> dict: global _drink_db if _drink_db is None: # Cập nhật tên bảng đồ uống chính xác _drink_db = _load_supabase_table_to_dict("places_drink") return _drink_db