from typing import Any, Dict, List, Optional from supabase.client import create_client, Client from loguru import logger from .utils import timing_decorator_sync from .constants import VEHICLE_KEYWORD_TO_COLUMN class SupabaseClient: def __init__(self, url: str, key: str): """ Khởi tạo SupabaseClient với url và key. Input: url (str), key (str) Output: SupabaseClient instance. """ self.client: Client = create_client(url, key) @timing_decorator_sync def get_page_token(self, page_id: str): """ Lấy access token của Facebook page từ Supabase. Input: page_id (str) Output: access_token (str) hoặc None nếu không có. """ try: response = self.client.table('PageToken').select('token').eq('id', page_id).execute() if response.data and len(response.data) > 0: return response.data[0]['token'] return None except Exception as e: logger.error(f"Error getting page token: {e}") return None @timing_decorator_sync def match_documents(self, embedding: List[float], match_count: int = 20, vehicle_keywords: Optional[List[str]] = None): """ Truy vấn vector similarity search qua RPC match_documents. Input: embedding (list[float]), match_count (int), vehicle_keywords (list[str] hoặc None) Output: list[dict] kết quả truy vấn. """ try: payload = { 'query_embedding': embedding, 'match_threshold': 0.1, 'match_count': match_count } if vehicle_keywords: vehicle_columns = [VEHICLE_KEYWORD_TO_COLUMN[k] for k in vehicle_keywords if k in VEHICLE_KEYWORD_TO_COLUMN] if vehicle_columns: payload['vehicle_filters'] = vehicle_columns response = self.client.rpc( 'match_documents', payload ).execute() if response.data: return response.data return [] except Exception as e: logger.error(f"Error matching documents: {e}") return [] @timing_decorator_sync def store_embedding(self, text: str, embedding: List[float], metadata: Dict[str, Any]): """ Lưu embedding vào Supabase. Input: text (str), embedding (list[float]), metadata (dict) Output: bool (True nếu thành công, False nếu lỗi) """ try: response = self.client.table('embeddings').insert({ 'content': text, 'embedding': embedding, 'metadata': metadata }).execute() return bool(response.data) except Exception as e: logger.error(f"Error storing embedding: {e}") return False @timing_decorator_sync def store_document_chunk(self, chunk_data: Dict[str, Any]) -> bool: """ Lưu document chunk vào Supabase. Input: chunk_data (dict) - chứa tất cả thông tin chunk Output: bool (True nếu thành công, False nếu lỗi) """ try: response = self.client.table('document_chunks').insert(chunk_data).execute() if response.data: logger.info(f"Successfully stored chunk {chunk_data.get('id', 'unknown')}") return True else: logger.error(f"Failed to store chunk {chunk_data.get('id', 'unknown')}") return False except Exception as e: logger.error(f"Error storing document chunk: {e}") return False @timing_decorator_sync def delete_all_document_chunks(self) -> bool: """ Xóa toàn bộ bảng document_chunks. Output: bool (True nếu thành công, False nếu lỗi) """ try: response = self.client.table('document_chunks').delete().neq('id', '').execute() logger.info(f"Successfully deleted all document chunks") return True except Exception as e: logger.error(f"Error deleting all document chunks: {e}") return False @timing_decorator_sync def get_document_chunks_by_vanbanid(self, vanbanid: int) -> List[Dict[str, Any]]: """ Lấy tất cả chunks của một văn bản theo vanbanid. Input: vanbanid (int) Output: List[Dict] - danh sách chunks """ try: response = self.client.table('document_chunks').select('*').eq('vanbanid', vanbanid).execute() if response.data: logger.info(f"Found {len(response.data)} chunks for vanbanid {vanbanid}") return response.data return [] except Exception as e: logger.error(f"Error getting document chunks for vanbanid {vanbanid}: {e}") return [] @timing_decorator_sync def delete_document_chunks_by_vanbanid(self, vanbanid: int) -> bool: """ Xóa tất cả chunks của một văn bản theo vanbanid. Input: vanbanid (int) Output: bool (True nếu thành công, False nếu lỗi) """ try: response = self.client.table('document_chunks').delete().eq('vanbanid', vanbanid).execute() logger.info(f"Successfully deleted all chunks for vanbanid {vanbanid}") return True except Exception as e: logger.error(f"Error deleting chunks for vanbanid {vanbanid}: {e}") return False