import gradio as gr from google_play_scraper import Sort, reviews from transformers import pipeline from collections import Counter import matplotlib import threading # <--- Tambahan Penting import base64 from io import BytesIO import torch # =============================== # 1. SETUP BACKEND & LOCK # =============================== # Set backend ke 'Agg' AGAR TIDAK ERROR DI SERVER matplotlib.use('Agg') import matplotlib.pyplot as plt # Kunci untuk mencegah Matplotlib crash saat diakses banyak user/thread plot_lock = threading.Lock() # =============================== # 2. SETUP MODEL # =============================== device = 0 if torch.cuda.is_available() else -1 MODEL_NAME = "Aardiiiiy/indobertweet-base-Indonesian-sentiment-analysis" print("⏳ Sedang memuat model... Harap tunggu sebentar.") try: sentiment_model = pipeline( "text-classification", model=MODEL_NAME, tokenizer=MODEL_NAME, device=device ) print("✅ Model siap digunakan!") except Exception as e: print(f"Error loading model: {e}") # =============================== # 3. LOGIC FUNCTIONS # =============================== def scrape_reviews(app_id, max_reviews, language): try: result, _ = reviews( app_id, lang=language, country="id", sort=Sort.NEWEST, count=max_reviews ) return [r["content"].strip() for r in result if r.get("content")] except Exception as e: print(f"Error scraping: {e}") return [] def analyze_sentiment_batch(texts): if not texts: return Counter(), 0 try: results = sentiment_model(texts, batch_size=16, truncation=True, max_length=512) labels = [res["label"].lower() for res in results] return Counter(labels), len(labels) except Exception as e: print(f"Error analyzing: {e}") return Counter(), 0 def generate_plot(pos, neu, neg, total): # Gunakan LOCK agar thread tidak bertabrakan saat menggambar with plot_lock: try: colors = ['#4ade80', '#fbbf24', '#f87171'] labels = ["Positif", "Netral", "Negatif"] sizes = [pos, neu, neg] fig = plt.figure(figsize=(6, 4)) fig.patch.set_alpha(0.0) plt.pie( sizes, labels=labels, autopct="%1.1f%%", startangle=140, colors=colors, # Pastikan teks hitam textprops={'color':"#000000", 'weight':'bold', 'fontsize': 10}, wedgeprops={'edgecolor': 'white', 'linewidth': 2} ) plt.axis('equal') plt.tight_layout() buf = BytesIO() plt.savefig(buf, format="png", bbox_inches='tight', transparent=True) # Bersihkan memori segera plt.close(fig) plt.close('all') return base64.b64encode(buf.getvalue()).decode() except Exception as e: print(f"Error generating plot: {e}") return "" def generate_html_dashboard(pos, neu, neg, total, img_base64): # HTML dengan CSS '!important' agar teks tetap HITAM di tema gelap html = f"""

📊 Hasil Analisis Sentimen

Berdasarkan {total} ulasan terbaru

🟢 Positif {pos}
🟡 Netral {neu}
🔴 Negatif {neg}
Total Data {total}
""" return html def run_pipeline(app_id, max_reviews, language): if not app_id: return "⚠️ Masukkan Package Name dulu.", "
" texts = scrape_reviews(app_id, max_reviews, language) if not texts: return "❌ Tidak ditemukan review.", "
Data Tidak Ditemukan
" raw_output = "\n".join(f"[{i+1}] {t}" for i, t in enumerate(texts)) counter, total = analyze_sentiment_batch(texts) pos = counter.get("positive", 0) neu = counter.get("neutral", 0) neg = counter.get("negative", 0) img_base64 = generate_plot(pos, neu, neg, total) html_result = generate_html_dashboard(pos, neu, neg, total, img_base64) return raw_output, html_result # =============================== # 4. GRADIO UI LAYOUT # =============================== theme = gr.themes.Soft( primary_hue="blue", secondary_hue="slate", ).set( body_background_fill="#0f172a", block_background_fill="#1e293b", block_label_text_color="#e2e8f0", body_text_color="#e2e8f0" ) with gr.Blocks(theme=theme, title="Play Store Analyzer") as demo: with gr.Row(): gr.Markdown("# 📱 Google Play Review Analyzer") with gr.Group(): with gr.Row(): app_id_input = gr.Textbox(label="Package Name", placeholder="com.whatsapp", scale=2) language_input = gr.Dropdown(choices=["id", "en"], value="id", label="Bahasa", scale=1) max_reviews_input = gr.Slider(minimum=10, maximum=500, step=10, value=50, label="Jumlah", scale=1) analyze_btn = gr.Button("🔍 Analisis", variant="primary") with gr.Row(): with gr.Column(scale=3): analysis_box = gr.HTML(label="Dashboard") with gr.Column(scale=2): raw_box = gr.Textbox(label="Data Mentah", lines=18) analyze_btn.click( run_pipeline, inputs=[app_id_input, max_reviews_input, language_input], outputs=[raw_box, analysis_box] ) if __name__ == "__main__": # Matikan SSR dan queue untuk stabilitas demo.launch(ssr_mode=False)