import os import shutil from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from sentence_transformers import SentenceTransformer, util import torch import requests # Rate limit from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded # Inisialisasi FastAPI dan Limiter limiter = Limiter(key_func=get_remote_address) app = FastAPI() app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # ๐Ÿ” Paksa cache aman untuk Hugging Face Spaces HF_CACHE = "/tmp/hf" os.environ["TRANSFORMERS_CACHE"] = HF_CACHE os.environ["HF_HOME"] = HF_CACHE os.makedirs(HF_CACHE, exist_ok=True) # Bersihkan cache model jika terkunci if os.path.exists(f"{HF_CACHE}/models--sentence-transformers--paraphrase-MiniLM-L3-v2.lock"): os.remove(f"{HF_CACHE}/models--sentence-transformers--paraphrase-MiniLM-L3-v2.lock") if os.path.exists(f"{HF_CACHE}/models--sentence-transformers--paraphrase-MiniLM-L3-v2"): shutil.rmtree(f"{HF_CACHE}/models--sentence-transformers--paraphrase-MiniLM-L3-v2", ignore_errors=True) # Supabase SUPABASE_URL = "https://olbjfxlclotxtnpjvpfj.supabase.co" SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9sYmpmeGxjbG90eHRucGp2cGZqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTIyMzYwMDEsImV4cCI6MjA2NzgxMjAwMX0.7q_o5DCFEAAysnWXMChH4MI5qNhIVc4OgpT5JvgYxc0" # Model model = SentenceTransformer("sentence-transformers/paraphrase-MiniLM-L3-v2") # ๐Ÿ” Ambil FAQ berdasarkan UID def get_faq_from_supabase(uid): url = f"{SUPABASE_URL}/rest/v1/faq_texts?uid=eq.{uid}" headers = { "apikey": SUPABASE_KEY, "Authorization": f"Bearer {SUPABASE_KEY}", "Content-Type": "application/json" } try: r = requests.get(url, headers=headers) r.raise_for_status() data = r.json() return [{"q": d["question"], "a": d["answer"]} for d in data] except Exception as e: print("โŒ Supabase error:", e) return [] # ๐Ÿ”ฎ Endpoint prediksi jawaban dari pertanyaan user @app.post("/predict") @limiter.limit("5/minute") async def predict(request: Request): body = await request.json() uid, question = body.get("data", [None, None]) if not uid or not question: return {"data": ["UID atau pertanyaan tidak valid."]} faqs = get_faq_from_supabase(uid) if not faqs: return {"data": ["FAQ tidak ditemukan untuk UID ini."]} questions = [f["q"] for f in faqs] answers = [f["a"] for f in faqs] embeddings = model.encode(questions, convert_to_tensor=True) query_embedding = model.encode(question, convert_to_tensor=True) similarity = util.pytorch_cos_sim(query_embedding, embeddings) best_idx = torch.argmax(similarity).item() return {"data": [answers[best_idx]]} # ๐Ÿงน Hapus satu pesan berdasarkan ID @app.post("/delete_chat") async def delete_chat(request: Request): body = await request.json() message_id = body.get("id") if not message_id: return JSONResponse({"error": "ID pesan wajib diisi."}, status_code=400) url = f"{SUPABASE_URL}/rest/v1/chat_logs?id=eq.{message_id}" headers = { "apikey": SUPABASE_KEY, "Authorization": f"Bearer {SUPABASE_KEY}", "Content-Type": "application/json", "Prefer": "return=representation" } try: r = requests.delete(url, headers=headers) r.raise_for_status() return {"message": f"Pesan dengan ID {message_id} berhasil dihapus."} except Exception as e: print("โŒ Gagal hapus pesan:", e) return JSONResponse({"error": "Gagal menghapus pesan."}, status_code=500) # ๐Ÿงผ Hapus semua pesan milik user tertentu @app.post("/delete_all_by_uid") async def delete_all_by_uid(request: Request): body = await request.json() uid = body.get("uid") if not uid: return JSONResponse({"error": "UID wajib diisi."}, status_code=400) url = f"{SUPABASE_URL}/rest/v1/chat_logs?uid=eq.{uid}" headers = { "apikey": SUPABASE_KEY, "Authorization": f"Bearer {SUPABASE_KEY}", "Content-Type": "application/json", "Prefer": "return=representation" } try: r = requests.delete(url, headers=headers) r.raise_for_status() return {"message": f"Semua pesan dengan UID {uid} berhasil dihapus."} except Exception as e: return JSONResponse({"error": str(e)}, status_code=500)