Spaces:
Sleeping
Sleeping
| 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 |