Spaces:
Running
Running
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 | |
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 | |
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 | |
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) |