PPloychor's picture
Create app.py
4a247f3 verified
# app.py — Sentiment Analysis with Copy & Export (CSV/XLSX)
import gradio as gr
from transformers import pipeline
import re
from functools import lru_cache
import logging
from typing import List, Dict, Tuple
import json
import os
import tempfile
# ===== NEW: pandas สำหรับ export CSV/XLSX =====
try:
import pandas as pd
except Exception:
pd = None
# ===== Logging =====
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ===== Model list =====
MODEL_LIST = [
("ZombitX64/MultiSent-E5-Pro", "🏆 MultiSent E5 Pro - แนะนำ (ความแม่นยำสูงสุด)"),
("ZombitX64/Thai-sentiment-e5", "🎯 Thai Sentiment E5 - เฉพาะภาษาไทย"),
("poom-sci/WangchanBERTa-finetuned-sentiment", "🔥 WangchanBERTa - โมเดลไทยยอดนิยม"),
("SandboxBhh/sentiment-thai-text-model", "✨ Sandbox Thai - เร็วและแม่นยำ"),
("ZombitX64/MultiSent-E5", "⚡ MultiSent E5 - รวดเร็ว"),
("Thaweewat/wangchanberta-hyperopt-sentiment-01", "🧠 WangchanBERTa Hyperopt"),
("cardiffnlp/twitter-xlm-roberta-base-sentiment", "🌐 XLM-RoBERTa - หลายภาษา"),
("phoner45/wangchan-sentiment-thai-text-model", "📱 Wangchan Mobile"),
("ZombitX64/Sentiment-01", "🔬 Sentiment v1"),
("ZombitX64/Sentiment-02", "🔬 Sentiment v2"),
("ZombitX64/Sentiment-03", "🔬 Sentiment v3"),
("ZombitX64/sentiment-103", "🔬 Sentiment 103"),
("ZombitX64/sentimentSumdata-v1", "🔬 sentimentSumdata-v1"),
("ZombitX64/wangchanberta-att-spm-uncased-sentiment", "wangchanberta-att-spm-uncased-sentiment"),
]
# ===== Cache model loading =====
@lru_cache(maxsize=3)
def get_nlp(model_name: str):
try:
return pipeline("sentiment-analysis", model=model_name)
except Exception as e:
logger.error(f"Error loading model {model_name}: {e}")
raise gr.Error(f"ไม่สามารถโหลดโมเดล {model_name} ได้: {str(e)}")
# ===== Label mappings =====
MODEL_LABEL_MAPPINGS = {
"ZombitX64/wangchanberta-att-spm-uncased-sentiment": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"ZombitX64/MultiSent-E5-Pro": {
"LABEL_0": {"code": 0, "name": "question", "emoji": "🤔", "color": "#60a5fa", "bg": "rgba(96,165,250,.2)", "description": "คำถาม"},
"LABEL_1": {"code": 1, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_2": {"code": 2, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_3": {"code": 3, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"ZombitX64/Thai-sentiment-e5": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"poom-sci/WangchanBERTa-finetuned-sentiment": {
"neg": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"neu": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"pos": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"SandboxBhh/sentiment-thai-text-model": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"ZombitX64/MultiSent-E5": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"Thaweewat/wangchanberta-hyperopt-sentiment-01": {
"neg": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"neu": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"pos": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"cardiffnlp/twitter-xlm-roberta-base-sentiment": {
"NEGATIVE": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"NEUTRAL": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"POSITIVE": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"phoner45/wangchan-sentiment-thai-text-model": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"ZombitX64/Sentiment-01": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"ZombitX64/Sentiment-02": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"ZombitX64/Sentiment-03": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"ZombitX64/sentiment-103": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
"ZombitX64/sentimentSumdata-v1": {
"LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248,113,113,.2)", "description": "เชิงลบ"},
"LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250,204,21,.2)", "description": "เป็นกลาง"},
"LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52,211,153,.2)", "description": "เชิงบวก"},
},
}
def get_label_info(label: str, model_name: str) -> Dict:
model_mappings = MODEL_LABEL_MAPPINGS.get(model_name, {})
if label in model_mappings:
return model_mappings[label]
return {
"code": -1, "name": label.lower(), "emoji": "🔍",
"color": "#64748b", "bg": "rgba(100,116,139,.2)",
"description": f"ไม่ทราบ ({label})"
}
# ===== Helpers =====
def split_sentences(text: str) -> List[str]:
sentences = re.split(r'[.!?।\n]+', text)
sentences = [s.strip() for s in sentences if s.strip() and len(s.strip()) > 2]
return sentences
def create_confidence_bar(score: float) -> str:
percentage = int(score * 100)
return f"""
<div style="display:flex;align-items:center;gap:10px;margin:8px 0;">
<div style="flex:1;height:8px;background:#334155;border-radius:4px;overflow:hidden;">
<div style="width:{percentage}%;height:100%;background:linear-gradient(90deg,#60a5fa,#3b82f6);"></div>
</div>
<span style="font-weight:600;color:#cbd5e1;min-width:50px;">{percentage}%</span>
</div>
"""
# ===== Main analyzer (HTML) — ใช้ของเดิมได้เลย =====
def analyze_text(text: str, model_name: str) -> str:
if not text or not text.strip():
return """
<div style="padding:20px;background:rgba(248,113,113,.2);border-radius:12px;border-left:4px solid #f87171;">
<div style="color:#f87171;font-weight:600;display:flex;align-items:center;gap:8px;">
<span style="font-size:20px;">⚠️</span> กรุณาใส่ข้อความที่ต้องการวิเคราะห์
</div>
</div>
"""
sentences = split_sentences(text)
if not sentences:
return """
<div style="padding:20px;background:rgba(248,113,113,.2);border-radius:12px;border-left:4px solid #f87171;">
<div style="color:#f87171;font-weight:600;display:flex;align-items:center;gap:8px;">
<span style="font-size:20px;">⚠️</span> ไม่พบประโยคที่สามารถวิเคราะห์ได้ กรุณาใส่ข้อความที่ยาวกว่านี้
</div>
</div>
"""
try:
nlp = get_nlp(model_name)
except Exception as e:
return f"""
<div style="padding:20px;background:rgba(248,113,113,.2);border-radius:12px;border-left:4px solid #f87171;">
<div style="color:#f87171;font-weight:600;display:flex;align-items:center;gap:8px;">
<span style="font-size:20px;">❌</span> เกิดข้อผิดพลาดในการโหลดโมเดล: {str(e)}
</div>
</div>
"""
html_parts = [f"""
<div style="background:linear-gradient(135deg,#1e3a8a 0%,#3b82f6 100%);color:#f8fafc;padding:24px;border-radius:16px 16px 0 0;margin-bottom:0;">
<h2 style="margin:0;font-size:24px;font-weight:700;display:flex;align-items:center;gap:12px;">
<span style="font-size:28px;">🧠</span> ผลการวิเคราะห์ความรู้สึก
</h2>
<p style="margin:8px 0 0 0;opacity:.9;font-size:14px;">โมเดล: {model_name.split('/')[-1]}</p>
</div>
"""]
sentiment_counts = {"positive": 0, "negative": 0, "neutral": 0, "question": 0, "other": 0}
total_confidence = 0
sentence_results = []
for i, sentence in enumerate(sentences, 1):
try:
result = nlp(sentence)[0]
label = result['label']; score = float(result['score'])
label_info = get_label_info(label, model_name)
label_name = label_info["name"]
if label_name in sentiment_counts:
sentiment_counts[label_name] += 1
else:
sentiment_counts["other"] += 1
total_confidence += score
sentence_results.append({
'sentence': sentence, 'label_info': label_info, 'score': score,
'index': i, 'original_label': label
})
except Exception as e:
logger.error(f"Error analyzing sentence {i}: {e}")
sentence_results.append({'sentence': sentence, 'error': str(e), 'index': i})
html_parts.append("""<div style="background:#0f172a;padding:0;border-radius:0 0 16px 16px;box-shadow:0 4px 20px rgba(0,0,0,.3);overflow:hidden;">""")
for r in sentence_results:
if 'error' in r:
html_parts.append(f"""
<div style="padding:20px;border-bottom:1px solid #1e293b;">
<div style="color:#f87171;font-weight:600;display:flex;align-items:center;gap:8px;">
<span style="font-size:18px;">❌</span> เกิดข้อผิดพลาดในการวิเคราะห์ประโยคที่ {r['index']}
</div>
<p style="color:#94a3b8;margin:8px 0 0 0;font-size:14px;">{r['error']}</p>
</div>
""")
else:
li = r['label_info']; conf = create_confidence_bar(r['score'])
html_parts.append(f"""
<div style="padding:20px;border-bottom:1px solid #1e293b;transition:.2s;" onmouseover="this.style.background='#1e293b'" onmouseout="this.style.background='#0f172a'">
<div style="display:flex;align-items:flex-start;gap:16px;">
<div style="background:{li['bg']};padding:12px;border-radius:50%;min-width:48px;height:48px;display:flex;align-items:center;justify-content:center;">
<span style="font-size:20px;">{li['emoji']}</span>
</div>
<div style="flex:1;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
<span style="background:{li['color']};color:#f8fafc;padding:4px 12px;border-radius:20px;font-size:12px;font-weight:600;text-transform:uppercase;">{li['description']}</span>
<span style="color:#94a3b8;font-size:12px;background:#1e293b;padding:2px 8px;border-radius:12px;">{r['original_label']}</span>
<span style="color:#94a3b8;font-size:14px;">ประโยคที่ {r['index']}</span>
</div>
<p style="color:#f8fafc;margin:0 0 12px 0;font-size:16px;line-height:1.5;">"{r['sentence'][:150]}{'...' if len(r['sentence'])>150 else ''}"</p>
<div style="color:#94a3b8;font-size:14px;margin-bottom:8px;">ความมั่นใจ:</div>
{conf}
</div>
</div>
</div>
""")
total_sentences = len(sentences)
avg_conf = total_confidence / total_sentences if total_sentences > 0 else 0
colors = {"positive":"#34d399","negative":"#f87171","neutral":"#facc15","question":"#60a5fa","other":"#64748b"}
emojis = {"positive":"😊","negative":"😢","neutral":"😐","question":"🤔","other":"🔍"}
chart_items = []
for s, c in sentiment_counts.items():
if c > 0:
pct = (c/total_sentences)*100
chart_items.append(f"""
<div style="display:flex;align-items:center;gap:12px;padding:12px;background:rgba(59,130,246,.1);border-radius:8px;">
<span style="font-size:24px;">{emojis.get(s,'🔍')}</span>
<div style="flex:1;">
<div style="font-weight:600;color:#f8fafc;text-transform:capitalize;">{s}</div>
<div style="color:#94a3b8;font-size:14px;">{c} ประโยค ({pct:.1f}%)</div>
</div>
<div style="width:60px;height:6px;background:#334155;border-radius:3px;overflow:hidden;">
<div style="width:{pct}%;height:100%;background:{colors.get(s,'#64748b')};"></div>
</div>
</div>
""")
html_parts.append(f"""
<div style="padding:24px;background:linear-gradient(135deg,#1e293b 0%,#0f172a 100%);">
<h3 style="color:#f8fafc;margin:0 0 20px 0;font-size:20px;font-weight:700;display:flex;align-items:center;gap:8px;">
<span style="font-size:24px;">📊</span> สรุปผลการวิเคราะห์
</h3>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:20px;">
<div style="background:#1e293b;padding:20px;border-radius:12px;text-align:center;">
<div style="font-size:32px;font-weight:700;color:#60a5fa;margin-bottom:4px;">{total_sentences}</div>
<div style="color:#94a3b8;font-size:14px;">ประโยคทั้งหมด</div>
</div>
<div style="background:#1e293b;padding:20px;border-radius:12px;text-align:center;">
<div style="font-size:32px;font-weight:700;color:#34d399;margin-bottom:4px;">{avg_conf*100:.0f}%</div>
<div style="color:#94a3b8;font-size:14px;">ความมั่นใจเฉลี่ย</div>
</div>
</div>
<div style="display:grid;gap:8px;">{"".join(chart_items)}</div>
</div>
</div>
""")
html_parts.append("</div>")
return "".join(html_parts)
# ===== NEW: คืน HTML + JSON โครงสร้าง =====
def analyze_text_with_data(text: str, model_name: str) -> Tuple[str, str]:
html = analyze_text(text, model_name)
sentences = split_sentences(text)
if not sentences:
return html, json.dumps({"model": model_name, "items": [], "summary": {}}, ensure_ascii=False)
try:
nlp = get_nlp(model_name)
except Exception:
return html, json.dumps({"model": model_name, "items": [], "summary": {}}, ensure_ascii=False)
items = []
sentiment_counts = {"positive": 0, "negative": 0, "neutral": 0, "question": 0, "other": 0}
for i, sentence in enumerate(sentences, 1):
try:
r = nlp(sentence)[0]
raw_label = r["label"]; score = float(r["score"])
label_info = get_label_info(raw_label, model_name)
label = label_info.get("name", "other")
if label not in sentiment_counts:
label = "other"
sentiment_counts[label] += 1
items.append({
"index": i, "sentence": sentence, "label": label,
"score": score, "raw_label": raw_label
})
except Exception as e:
items.append({
"index": i, "sentence": sentence, "label": "error",
"score": 0.0, "raw_label": f"error: {e}"
})
results_json = json.dumps({"model": model_name, "items": items, "summary": sentiment_counts}, ensure_ascii=False)
return html, results_json
# ===== NEW: ข้อความรวมตาม sentiment สำหรับ Copy =====
def build_copy_texts(results_json: str) -> Tuple[str, str, str, str, str]:
try:
data = json.loads(results_json)
except Exception:
return "", "", "", "", ""
buckets = {"positive": [], "negative": [], "neutral": [], "question": [], "other": []}
for it in data.get("items", []):
lb = it.get("label", "other")
if lb not in buckets:
lb = "other"
buckets[lb].append(f"{it.get('index','')}. {it.get('sentence','')}")
j = lambda xs: "\n".join(xs) if xs else ""
return j(buckets["positive"]), j(buckets["negative"]), j(buckets["neutral"]), j(buckets["question"]), j(buckets["other"])
# ===== NEW: Export CSV/XLSX =====
def export_csv(results_json: str) -> str:
data = json.loads(results_json)
items = data.get("items", [])
if pd is None:
import csv
path = os.path.join(tempfile.gettempdir(), "sentiment_results.csv")
with open(path, "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
w.writerow(["index","sentence","label","score","raw_label"])
for it in items:
w.writerow([it.get("index",""), it.get("sentence",""), it.get("label",""), it.get("score",""), it.get("raw_label","")])
return path
df = pd.DataFrame(items, columns=["index","sentence","label","score","raw_label"])
path = os.path.join(tempfile.gettempdir(), "sentiment_results.csv")
df.to_csv(path, index=False)
return path
def export_xlsx(results_json: str) -> str:
if pd is None:
raise gr.Error("ต้องติดตั้ง pandas/openpyxl ก่อนจึงจะส่งออก .xlsx ได้")
data = json.loads(results_json)
items = data.get("items", [])
df = pd.DataFrame(items, columns=["index","sentence","label","score","raw_label"])
path = os.path.join(tempfile.gettempdir(), "sentiment_results.xlsx")
with pd.ExcelWriter(path, engine="openpyxl") as writer:
df.to_excel(writer, index=False, sheet_name="all")
for s in ["positive","negative","neutral","question","other"]:
sdf = df[df["label"] == s]
if not sdf.empty:
sdf.to_excel(writer, index=False, sheet_name=s)
return path
# ===== CSS (ย่อเพื่อความกระชับ) =====
CUSTOM_CSS = """
* { font-family: 'Inter','Noto Sans Thai',sans-serif !important; }
body, .gradio-container { background: linear-gradient(135deg,#181f2a 0%,#232e3c 100%) !important; }
.main-uxui-card { background:#232e3c;border-radius:20px;border:1.5px solid #2d3a4d;padding:24px;color:#e3e8ef; }
.main-uxui-btn { padding:.9em 2em;border-radius:12px;font-weight:600;background:linear-gradient(90deg,#2563eb 0%,#1e293b 100%);color:#f8fafc;border:none; }
.main-uxui-input, .main-uxui-dropdown { border:1.5px solid #2d3a4d;background:#1e2533;color:#e3e8ef;padding:14px;border-radius:10px; }
.main-uxui-output { background:#1e2533;border:1.5px solid #2d3a4d;border-radius:14px;padding:18px; }
"""
# ===== UI =====
with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Base(), title="Sentiment Analysis") as demo:
with gr.Column(elem_classes="main-uxui-card"):
gr.HTML("<h1 style='text-align:center;margin:0 0 8px 0;'>Sentiment Analysis</h1><p style='text-align:center;color:#7da2e3;margin:0;'>วิเคราะห์ความรู้สึกหลายภาษา + Export ไฟล์</p>")
with gr.Row():
model_dropdown = gr.Dropdown(
choices=[(desc, name) for name, desc in MODEL_LIST], # label, value
value=MODEL_LIST[0][0],
label="เลือกโมเดล (Model)",
elem_classes="main-uxui-dropdown"
)
with gr.Row():
input_box = gr.Textbox(
lines=5,
placeholder="พิมพ์ข้อความ (รองรับหลายประโยค แยกด้วย ., ?, ! หรือขึ้นบรรทัดใหม่)",
label="ข้อความที่ต้องการวิเคราะห์",
elem_classes="main-uxui-input"
)
with gr.Row():
analyze_btn = gr.Button("วิเคราะห์", elem_classes="main-uxui-btn")
clear_btn = gr.Button("ล้างผลลัพธ์", elem_classes="main-uxui-btn")
with gr.Tab("ผลลัพธ์"):
output_html = gr.HTML(label="ผลลัพธ์", elem_classes="main-uxui-output")
with gr.Tab("Copy ตาม Sentiment"):
gr.Markdown("**คัดลอกข้อความที่จัดกลุ่มแล้วตาม sentiment**")
pos_copy = gr.Textbox(label="😊 Positive", lines=8, show_copy_button=True)
neg_copy = gr.Textbox(label="😢 Negative", lines=8, show_copy_button=True)
neu_copy = gr.Textbox(label="😐 Neutral", lines=8, show_copy_button=True)
q_copy = gr.Textbox(label="🤔 Question", lines=6, show_copy_button=True)
other_copy = gr.Textbox(label="🔍 Other/Unknown", lines=6, show_copy_button=True)
with gr.Tab("Export"):
results_json = gr.Textbox(visible=False)
with gr.Row():
export_csv_btn = gr.Button("⬇️ Export CSV", elem_classes="main-uxui-btn")
export_xlsx_btn = gr.Button("⬇️ Export Excel (.xlsx)", elem_classes="main-uxui-btn")
export_file = gr.File(label="ดาวน์โหลดไฟล์ที่นี่", interactive=False)
gr.Examples(
examples=[
["วันนี้อากาศดีมากๆ รู้สึกสดชื่นและมีความสุขมาก!"],
["เศร้ามากเลยวันนี้ งานเยอะเกินไป"],
["อาหารอร่อยดี แต่บริการช้ามาก"],
["คุณคิดอย่างไรกับเศรษฐกิจไทย?"],
["I love this product! It's amazing."],
["이 제품은 별로예요. 다시는 안 살 거예요."],
["This is the worst experience I've ever had."]
],
inputs=input_box,
label="ตัวอย่างข้อความ",
)
# ===== Callbacks =====
def on_analyze(text, model):
html, rjson = analyze_text_with_data(text, model)
pos, neg, neu, qn, other = build_copy_texts(rjson)
return html, rjson, pos, neg, neu, qn, other
analyze_btn.click(on_analyze, [input_box, model_dropdown],
[output_html, results_json, pos_copy, neg_copy, neu_copy, q_copy, other_copy])
input_box.submit(on_analyze, [input_box, model_dropdown],
[output_html, results_json, pos_copy, neg_copy, neu_copy, q_copy, other_copy])
model_dropdown.change(on_analyze, [input_box, model_dropdown],
[output_html, results_json, pos_copy, neg_copy, neu_copy, q_copy, other_copy])
clear_btn.click(lambda: ("", "", "", "", "", "", ""), None,
[output_html, results_json, pos_copy, neg_copy, neu_copy, q_copy, other_copy])
export_csv_btn.click(export_csv, inputs=results_json, outputs=export_file)
export_xlsx_btn.click(export_xlsx, inputs=results_json, outputs=export_file)
# ===== Launch =====
if __name__ == "__main__":
demo.queue(max_size=50, default_concurrency_limit=10).launch(
server_name="0.0.0.0",
server_port=7860,
share=True,
show_error=True,
show_api=False,
quiet=False,
ssl_verify=False,
app_kwargs={"docs_url": None, "redoc_url": None},
)