File size: 4,524 Bytes
a7eb20d
d411917
a7eb20d
57cd8cf
a7eb20d
 
 
57cd8cf
 
e97e0b7
 
 
 
 
 
 
 
 
345d10b
57cd8cf
d411917
 
 
 
a814516
57cd8cf
d411917
 
 
 
 
57cd8cf
a7eb20d
 
1fe6e3b
57cd8cf
a7eb20d
20d5756
57cd8cf
a7eb20d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57cd8cf
a7eb20d
e97e0b7
a7eb20d
 
 
57cd8cf
a7eb20d
20d5756
57cd8cf
981d40d
 
20d5756
a702cfd
981d40d
 
a702cfd
981d40d
 
aad84fe
20d5756
 
e827c31
57cd8cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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)