""" SentiMeter — IndoBERT Sentiment Analysis Backend Uses mdhugol/indonesia-bert-sentiment-classification """ import os import sys import traceback from flask import Flask, request, jsonify, send_from_directory from flask_cors import CORS app = Flask(__name__, static_folder='.', static_url_path='') CORS(app) # ─── Model Loading ──────────────────────────────────── MODEL_NAME = "mdhugol/indonesia-bert-sentiment-classification" LABEL_MAP = { 'LABEL_0': 'Positif', 'LABEL_1': 'Netral', 'LABEL_2': 'Negatif', } sentiment_pipeline = None model_error = None def load_model(): """Load the IndoBERT sentiment model. Returns True on success.""" global sentiment_pipeline, model_error try: print(f"[IndoBERT] Loading model: {MODEL_NAME} ...") from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME) sentiment_pipeline = pipeline( "sentiment-analysis", model=model, tokenizer=tokenizer, truncation=True, max_length=512, ) model_error = None print("[IndoBERT] Model loaded successfully!") return True except Exception as e: model_error = str(e) sentiment_pipeline = None print(f"[IndoBERT] ERROR loading model: {e}", file=sys.stderr) traceback.print_exc() return False # ─── API Routes ─────────────────────────────────────── @app.route('/api/health', methods=['GET']) def health(): """Check if the model is loaded and ready.""" if sentiment_pipeline is not None: return jsonify({"status": "ok", "model": MODEL_NAME}) else: return jsonify({ "status": "error", "model": MODEL_NAME, "error": model_error or "Model not loaded" }), 503 @app.route('/api/sentiment', methods=['POST']) def analyze_sentiment(): """ Analyze sentiment for a batch of texts. Expects JSON: { "texts": ["text1", "text2", ...] } Returns JSON: [ {"label": "Positif", "score": 0.95}, ... ] """ if sentiment_pipeline is None: return jsonify({ "error": True, "message": f"Model IndoBERT gagal dimuat: {model_error or 'Unknown error'}. " "Pastikan koneksi internet aktif dan dependensi sudah terinstal.", }), 503 data = request.get_json(silent=True) if not data or 'texts' not in data: return jsonify({"error": True, "message": "Request harus berisi field 'texts'."}), 400 texts = data['texts'] if not isinstance(texts, list) or len(texts) == 0: return jsonify({"error": True, "message": "'texts' harus berupa array string yang tidak kosong."}), 400 # Limit batch size to prevent OOM MAX_BATCH = 64 results = [] try: for i in range(0, len(texts), MAX_BATCH): batch = texts[i:i + MAX_BATCH] # Ensure all texts are non-empty strings batch = [str(t).strip() or "." for t in batch] preds = sentiment_pipeline(batch) for pred in preds: label = LABEL_MAP.get(pred['label'], pred['label']) results.append({ "label": label, "score": round(pred['score'], 4), }) except Exception as e: return jsonify({ "error": True, "message": f"Error saat proses sentimen: {str(e)}" }), 500 return jsonify(results) # ─── Static File Serving ───────────────────────────── @app.route('/') def serve_index(): return send_from_directory(app.static_folder, 'index.html') @app.errorhandler(404) def handle_404(e): path = request.path.lstrip('/') if not path: return send_from_directory(app.static_folder, 'index.html') # Try serving the file as is (for static assets) if os.path.isfile(os.path.join(app.static_folder, path)): return send_from_directory(app.static_folder, path) # Try adding .html (for clean URLs) html_path = path + ".html" if os.path.isfile(os.path.join(app.static_folder, html_path)): return send_from_directory(app.static_folder, html_path) # Fallback to index.html return send_from_directory(app.static_folder, 'index.html') # ─── Main ──────────────────────────────────────────── if __name__ == '__main__': load_model() print("\n" + "=" * 50) if sentiment_pipeline: print(" SentiMeter Server — IndoBERT Ready") else: print(" SentiMeter Server — WARNING: Model failed to load!") print(f" Error: {model_error}") print(f" Open: http://localhost:7860") print("=" * 50 + "\n") app.run(host='0.0.0.0', port=7860, debug=False)