import os import json import re import csv import io from flask import Flask, request, jsonify, render_template_string from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch app = Flask(__name__) MODEL_PATH = "./hate_speech_model" tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH) model = AutoModelForSequenceClassification.from_pretrained(MODEL_PATH) threshold = 0.5 if os.path.exists(os.path.join(MODEL_PATH, "training_config.json")): try: with open(os.path.join(MODEL_PATH, "training_config.json"), "r") as f: config = json.load(f) threshold = config.get("optimal_threshold", 0.5) print(f"Loaded optimal threshold from config: {threshold:.4f}") except Exception as e: print(f"Gagal memuat training_config.json: {e}. Menggunakan threshold default 0.5") dataset_cache = {"data": None, "loaded": False} def preprocess_text(text): text = re.sub(r'@[^\s]+', '@USER', text) text = re.sub(r'https?://[^\s]+', 'HTTPURL', text) text = re.sub(r'\s+', ' ', text).strip() return text WEB_UI_HTML = r""" Hate Speech Detector

Hate Speech Detector

Klasifikasi teks ujaran kebencian

0 karakter
Non-Hate 0% Hate

                

Memuat dataset IndoDiscourse dari HuggingFace...

""" @app.route("/") def home(): return render_template_string(WEB_UI_HTML, threshold=threshold) @app.route("/api/dataset") def get_dataset(): if dataset_cache["loaded"]: return jsonify({"data": dataset_cache["data"]}) try: import urllib.request url = "https://huggingface.co/datasets/Exqrch/IndoDiscourse/resolve/main/indotoxic2024_annotated_data_v2_final.csv" req = urllib.request.Request(url, headers={"User-Agent": "HateSpeechDetector/1.0"}) with urllib.request.urlopen(req, timeout=30) as response: raw = response.read().decode("utf-8") reader = csv.DictReader(io.StringIO(raw)) rows = [] for row in reader: text = row.get("text", "").strip() if not text: continue rows.append({"text": text}) dataset_cache["data"] = rows dataset_cache["loaded"] = True return jsonify({"data": rows}) except Exception as e: return jsonify({"error": f"Gagal memuat dataset: {str(e)}"}), 500 @app.route("/predict", methods=["POST"]) def predict(): api_key = request.headers.get("X-API-Key") if api_key != "sh1eld-hate-speech-key-2026": return jsonify({"error": "Akses ditolak: API Key tidak valid atau tidak disediakan"}), 401 data = request.get_json() if not data or "text" not in data: return jsonify({"error": "Silakan kirimkan parameter 'text'"}), 400 text = data["text"] text_clean = preprocess_text(text) inputs = tokenizer(text_clean, return_tensors="pt", truncation=True, padding=True) with torch.no_grad(): outputs = model(**inputs) probs = torch.nn.functional.softmax(outputs.logits, dim=1) hate_prob = probs[0][1].item() clean_text_for_len = text.strip() if len(clean_text_for_len) > 500: clean_text_for_len = clean_text_for_len[:500] text_len = len(clean_text_for_len) if text_len < 80: effective_threshold = 0.92 elif text_len < 200: effective_threshold = 0.82 else: effective_threshold = threshold pred_label = 1 if hate_prob >= effective_threshold else 0 label = "Hate" if pred_label == 1 else "Non-Hate" confidence = hate_prob if pred_label == 1 else (1 - hate_prob) return jsonify({ "label": label, "confidence": confidence, "hate_probability": float(hate_prob), "text_length": len(text), "threshold_used": effective_threshold, "probabilities": { "Non-Hate": float(probs[0][0]), "Hate": float(probs[0][1]) } }) if __name__ == "__main__": app.run(host="0.0.0.0", port=7860)