coment / app.py
faris05's picture
Update app.py
79abc5d verified
Raw
History Blame Contribute Delete
7.64 kB
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"""
<div style="
font-family: 'Segoe UI', sans-serif;
background-color: #ffffff;
border-radius: 12px;
padding: 20px;
color: #000000 !important;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
">
<h3 style="text-align:center; color:#000000 !important; margin-top:0; font-weight:bold;">
πŸ“Š Hasil Analisis Sentimen
</h3>
<p style="text-align:center; color:#333333 !important; font-size:14px;">
Berdasarkan <b>{total}</b> ulasan terbaru
</p>
<div style="display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 20px;">
<div style="flex: 1; min-width: 250px;">
<table style="width:100%; color: #000000 !important;">
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 8px 0; color: #000000 !important;">🟒 Positif</td>
<td style="text-align:right; font-weight:bold; color:#15803d !important;">{pos}</td>
</tr>
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 8px 0; color: #000000 !important;">🟑 Netral</td>
<td style="text-align:right; font-weight:bold; color:#b45309 !important;">{neu}</td>
</tr>
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 8px 0; color: #000000 !important;">πŸ”΄ Negatif</td>
<td style="text-align:right; font-weight:bold; color:#b91c1c !important;">{neg}</td>
</tr>
<tr style="background-color: #f8f9fa;">
<td style="padding: 8px 0; font-weight:bold; color: #000000 !important;">Total Data</td>
<td style="text-align:right; font-weight:bold; color: #000000 !important;">{total}</td>
</tr>
</table>
</div>
<div style="flex: 1; min-width: 250px; text-align: center;">
<img src="data:image/png;base64,{img_base64}" style="max-width: 100%; height: auto;" />
</div>
</div>
</div>
"""
return html
def run_pipeline(app_id, max_reviews, language):
if not app_id:
return "⚠️ Masukkan Package Name dulu.", "<div></div>"
texts = scrape_reviews(app_id, max_reviews, language)
if not texts:
return "❌ Tidak ditemukan review.", "<div style='color:white'>Data Tidak Ditemukan</div>"
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)