Spaces:
Running
Running
Nattapong Tapachoom
Enhance CSS styles for improved visual consistency and responsiveness in Gradio app
cadbbaa
import gradio as gr | |
from transformers import pipeline | |
import re | |
from functools import lru_cache | |
import logging | |
from typing import List, Dict, Tuple | |
# Set up logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
# รายชื่อโมเดลที่ให้เลือก | |
MODEL_LIST = [ | |
"SandboxBhh/sentiment-thai-text-model", | |
"poom-sci/WangchanBERTa-finetuned-sentiment", | |
"Thaweewat/wangchanberta-hyperopt-sentiment-01", | |
"cardiffnlp/twitter-xlm-roberta-base-sentiment", | |
"phoner45/wangchan-sentiment-thai-text-model", | |
"ZombitX64/Sentiment-01", | |
"ZombitX64/Sentiment-02", | |
"ZombitX64/Sentiment-03", | |
"ZombitX64/MultiSent-E5-Pro", | |
"ZombitX64/MultiSent-E5", | |
"ZombitX64/Thai-sentiment-e5", | |
"ZombitX64/sentiment-103", | |
"nlptown/bert-base-multilingual-uncased-sentiment" | |
] | |
# ใช้ cache เพื่อไม่ต้องโหลดโมเดลซ้ำ | |
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)}") | |
# Enhanced label mapping with more comprehensive support | |
LABEL_MAPPINGS = { | |
# Standard labels | |
"LABEL_0": {"code": 0, "name": "question", "emoji": "❓", "color": "#2196F3", "bg": "#E3F2FD"}, | |
"LABEL_1": {"code": 1, "name": "negative", "emoji": "😔", "color": "#F44336", "bg": "#FFEBEE"}, | |
"LABEL_2": {"code": 2, "name": "neutral", "emoji": "😐", "color": "#FF9800", "bg": "#FFF3E0"}, | |
"LABEL_3": {"code": 3, "name": "positive", "emoji": "😊", "color": "#4CAF50", "bg": "#E8F5E8"}, | |
# Alternative label formats | |
"POSITIVE": {"code": 3, "name": "positive", "emoji": "😊", "color": "#4CAF50", "bg": "#E8F5E8"}, | |
"NEGATIVE": {"code": 1, "name": "negative", "emoji": "😔", "color": "#F44336", "bg": "#FFEBEE"}, | |
"NEUTRAL": {"code": 2, "name": "neutral", "emoji": "😐", "color": "#FF9800", "bg": "#FFF3E0"}, | |
# Numerical labels | |
"0": {"code": 0, "name": "negative", "emoji": "😔", "color": "#F44336", "bg": "#FFEBEE"}, | |
"1": {"code": 1, "name": "positive", "emoji": "😊", "color": "#4CAF50", "bg": "#E8F5E8"}, | |
} | |
def get_label_info(label: str) -> Dict: | |
"""Get label information with fallback for unknown labels""" | |
return LABEL_MAPPINGS.get(label, { | |
"code": -1, | |
"name": label.lower(), | |
"emoji": "🔍", | |
"color": "#666666", | |
"bg": "#F5F5F5" | |
}) | |
def split_sentences(text: str) -> List[str]: | |
"""Enhanced sentence splitting with better Thai support""" | |
# Split by various sentence endings and normalize | |
sentences = re.split(r'[.!?。\n]+', text) | |
# Clean and filter empty sentences | |
sentences = [s.strip() for s in sentences if s.strip() and len(s.strip()) > 2] | |
return sentences | |
def create_progress_bar(score: float, width: int = 20) -> str: | |
"""Create a visual progress bar""" | |
filled = int(score * width) | |
return "█" * filled + "░" * (width - filled) | |
def analyze_text(text: str, model_name: str) -> str: | |
"""Enhanced text analysis with better error handling and formatting""" | |
if not text or not text.strip(): | |
return "❗ กรุณาใส่ข้อความที่ต้องการวิเคราะห์" | |
sentences = split_sentences(text) | |
if not sentences: | |
return "❗ ไม่พบประโยคที่สามารถวิเคราะห์ได้ กรุณาใส่ข้อความที่ยาวกว่านี้" | |
try: | |
nlp = get_nlp(model_name) | |
except Exception as e: | |
return f"❌ เกิดข้อผิดพลาดในการโหลดโมเดล: {str(e)}" | |
results = [] | |
results.append("📊 **ผลการวิเคราะห์ความรู้สึก**\n" + "="*60 + "\n") | |
results.append(f"🤖 **โมเดล:** {model_name}\n") | |
sentiment_counts = {"positive": 0, "negative": 0, "neutral": 0, "question": 0, "other": 0} | |
total_confidence = 0 | |
for i, sentence in enumerate(sentences, 1): | |
try: | |
result = nlp(sentence)[0] | |
label = result['label'] | |
score = result['score'] | |
label_info = get_label_info(label) | |
label_name = label_info["name"] | |
# Count sentiments | |
if label_name in sentiment_counts: | |
sentiment_counts[label_name] += 1 | |
else: | |
sentiment_counts["other"] += 1 | |
total_confidence += score | |
# Create formatted result | |
progress_bar = create_progress_bar(score) | |
confidence_percent = score * 100 | |
result_text = f""" | |
🔸 **ประโยคที่ {i}:** "{sentence[:100]}{'...' if len(sentence) > 100 else ''}" | |
{label_info['emoji']} **ผลวิเคราะห์:** {label_name.upper()} (รหัส: {label_info['code']}) | |
📈 **ความมั่นใจ:** {score:.3f} ({confidence_percent:.1f}%) | |
{progress_bar} {score:.3f} | |
{'─' * 70} | |
""" | |
results.append(result_text) | |
except Exception as e: | |
logger.error(f"Error analyzing sentence {i}: {e}") | |
results.append(f"\n❌ เกิดข้อผิดพลาดในการวิเคราะห์ประโยคที่ {i}: {str(e)}\n{'─' * 70}\n") | |
# Enhanced summary | |
total_sentences = len(sentences) | |
avg_confidence = total_confidence / total_sentences if total_sentences > 0 else 0 | |
summary = f""" | |
📋 **สรุปผลการวิเคราะห์** | |
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
📊 จำนวนประโยคทั้งหมด: {total_sentences} ประโยค | |
📈 ความมั่นใจเฉลี่ย: {avg_confidence:.3f} ({avg_confidence*100:.1f}%) | |
🎯 **การกระจายของความรู้สึก:** | |
""" | |
for sentiment, count in sentiment_counts.items(): | |
if count > 0: | |
percentage = (count / total_sentences) * 100 | |
emoji = {"positive": "😊", "negative": "😔", "neutral": "😐", "question": "❓", "other": "🔍"} | |
summary += f" {emoji.get(sentiment, '🔍')} {sentiment.title()}: {count} ประโยค ({percentage:.1f}%)\n" | |
results.append(summary) | |
return "\n".join(results) | |
# Modern minimal CSS | |
CUSTOM_CSS = """ | |
body, .gradio-container { | |
background: #f7faff !important; | |
} | |
.gradio-container { | |
max-width: 1100px !important; | |
margin: 32px auto 32px auto !important; | |
border-radius: 22px !important; | |
box-shadow: 0 8px 32px 0 #bdbdbd55; | |
border: 1.5px solid #e3e3e3; | |
min-height: 96vh; | |
padding-bottom: 32px; | |
} | |
.main-card { | |
background: #fff !important; | |
border-radius: 18px; | |
box-shadow: 0 2px 12px 0 #bdbdbd55; | |
padding: 28px 24px 20px 24px; | |
margin: 18px 0; | |
border: 1.5px solid #d1d1d1; | |
color: #222 !important; | |
transition: box-shadow 0.2s; | |
} | |
.main-card:hover { | |
box-shadow: 0 6px 24px 0 #bdbdbd77; | |
} | |
.output-markdown { | |
font-family: 'Segoe UI', 'Noto Sans Thai', sans-serif !important; | |
line-height: 1.7; | |
font-size: 1.08em; | |
color: #222 !important; | |
} | |
.gr-button { | |
font-size: 1.13em; | |
padding: 0.9em 2.7em; | |
border-radius: 13px; | |
font-weight: 600; | |
transition: all 0.2s; | |
background: linear-gradient(90deg, #4a63e7 0%, #764ba2 100%); | |
color: #fff !important; | |
border: none; | |
box-shadow: 0 2px 8px #bdbdbd33; | |
} | |
.gr-button.secondary, .gr-button[variant="secondary"] { | |
background: #eaeaea !important; | |
color: #333 !important; | |
border: 1px solid #cccccc; | |
} | |
.gr-button:hover { | |
transform: translateY(-2px) scale(1.03); | |
box-shadow: 0 6px 18px #bdbdbd55; | |
filter: brightness(1.04); | |
} | |
.gr-textbox textarea { | |
font-size: 1.13em; | |
min-height: 150px; | |
font-family: 'Segoe UI', 'Noto Sans Thai', sans-serif; | |
background: #fff; | |
border-radius: 10px; | |
border: 1.5px solid #cccccc; | |
color: #222; | |
padding: 14px; | |
} | |
.gr-dropdown input { | |
font-size: 1.1em; | |
border-radius: 8px; | |
background: #fff; | |
border: 1.5px solid #cccccc; | |
color: #222; | |
} | |
.gr-markdown h1, .gr-markdown h3 { | |
color: #2a2a4d !important; | |
} | |
.gr-markdown, .gr-markdown ul, .gr-markdown p, .gr-markdown li { | |
color: #222 !important; | |
} | |
.gr-markdown ul { | |
margin-left: 1.2em; | |
} | |
@media (max-width: 900px) { | |
.gradio-container { max-width: 99vw !important; padding: 0 2vw; } | |
.main-card { padding: 16px 6px; } | |
} | |
""" | |
# Enhanced Gradio interface | |
with gr.Blocks( | |
theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple", neutral_hue="gray"), | |
css=CUSTOM_CSS, | |
title="Thai Sentiment Analyzer - AI วิเคราะห์ความรู้สึกภาษาไทย" | |
) as demo: | |
gr.Markdown(""" | |
<div style="text-align: center; padding: 38px 0 18px 0;"> | |
<h1 style="font-size:2.7em; margin-bottom: 0.18em; color:#3b3b6d; letter-spacing:0.5px;">🧠 Thai Sentiment Analyzer</h1> | |
<div style="font-size:1.18em; color:#4a4a7d; opacity:0.92;"> | |
AI วิเคราะห์ความรู้สึกในข้อความภาษาไทย รองรับหลายโมเดลและหลายประโยค | |
</div> | |
</div> | |
""") | |
with gr.Row(): | |
with gr.Column(scale=1): | |
gr.Markdown(""" | |
<div class='main-card'> | |
<h3 style='color:#3b3b6d; margin-bottom:15px; font-size:1.18em;'>🤖 เลือกโมเดลวิเคราะห์</h3> | |
<p style='color:#666; font-size:0.97em; margin-bottom:10px;'>เลือกโมเดล AI ที่ต้องการใช้ในการวิเคราะห์ความรู้สึก</p> | |
</div> | |
""") | |
model_dropdown = gr.Dropdown( | |
choices=MODEL_LIST, | |
value="ZombitX64/MultiSent-E5-Pro", | |
label="โมเดลที่ต้องการใช้", | |
info="แนะนำ: MultiSent-E5-Pro สำหรับความแม่นยำสูง" | |
) | |
gr.Markdown(""" | |
<div class='main-card' style='margin-top:20px;'> | |
<h3 style='color:#3b3b6d; margin-bottom:15px; font-size:1.13em;'>📖 คำแนะนำการใช้งาน</h3> | |
<ul style='color:#555; font-size:1em; line-height:1.7; margin-bottom:0;'> | |
<li>พิมพ์ข้อความภาษาไทยที่ต้องการวิเคราะห์</li> | |
<li>แยกประโยคด้วยจุด (.) หรือขึ้นบรรทัดใหม่</li> | |
<li>รองรับการวิเคราะห์หลายประโยคพร้อมกัน</li> | |
<li>ผลลัพธ์จะแสดงความมั่นใจและสรุปภาพรวม</li> | |
</ul> | |
</div> | |
""") | |
with gr.Column(scale=2): | |
gr.Markdown(""" | |
<div class='main-card'> | |
<h3 style='color:#3b3b6d; margin-bottom:15px; font-size:1.15em;'>📝 ข้อความที่ต้องการวิเคราะห์</h3> | |
</div> | |
""") | |
text_input = gr.Textbox( | |
lines=8, | |
placeholder="ตัวอย่าง:\nวันนี้อากาศดีมาก ฉันรู้สึกมีความสุข\nแต่การจราจรติดมาก น่าเบื่อจริงๆ\nโดยรวมแล้วก็โอเคนะ", | |
label="", | |
show_label=False | |
) | |
with gr.Row(): | |
analyze_btn = gr.Button("🔍 วิเคราะห์ข้อความ", variant="primary", size="lg") | |
clear_btn = gr.Button("🗑️ ล้างข้อความ", variant="secondary") | |
output_box = gr.Textbox( | |
label="📊 ผลการวิเคราะห์ความรู้สึก", | |
lines=20, | |
show_copy_button=True, | |
show_label=True | |
) | |
# Enhanced examples | |
examples = gr.Examples( | |
examples=[ | |
["วันนี้อากาศดีมาก ฉันรู้สึกมีความสุขมาก สีฟ้าสวยจริงๆ"], | |
["ฉันไม่ชอบอาหารนี้เลย รสชาติแปลกมาก เค็มเกินไป"], | |
["วันนี้เป็นยังไงบ้าง\nเรียนหนังสือกันไหม\nมีงานอะไรให้ช่วยไหม"], | |
["บริการดีมาก พนักงานใจดีและเป็นกันเอง\nแต่ของมีราคาแพงไปหน่อย\nโดยรวมแล้วพอใจครับ แนะนำให้เพื่อนมาลอง"], | |
["เมื่อไหร่จะได้เจอกันอีก คิดถึงมากเลย\nแต่ตอนนี้ต้องทำงานหนักก่อน เพื่ออนาคตที่ดี"] | |
], | |
inputs=[text_input], | |
label="💡 คลิกเพื่อลองใช้ตัวอย่าง" | |
) | |
# Sentiment legend with enhanced styling | |
gr.Markdown(""" | |
<div class='main-card' style='margin-top:30px; background: #5b3b6d;'> | |
<h3 style='color:#3b3b6d; text-align:center; margin-bottom:20px;'>🎯 คำอธิบายผลการวิเคราะห์</h3> | |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 18px; padding: 12px 0;"> | |
<div style="text-align: center; padding: 13px; background: rgba(76, 175, 80, 0.08); border-radius: 11px;"> | |
<div style="font-size: 26px; margin-bottom: 7px;">😊</div> | |
<strong style="color: #4CAF50; font-size: 1.08em;">Positive</strong><br> | |
<small style="color: #666;">ความรู้สึกเชิงบวก<br>ดี, สุข, ชอบ</small> | |
</div> | |
<div style="text-align: center; padding: 13px; background: rgba(244, 67, 54, 0.08); border-radius: 11px;"> | |
<div style="font-size: 26px; margin-bottom: 7px;">😔</div> | |
<strong style="color: #F44336; font-size: 1.08em;">Negative</strong><br> | |
<small style="color: #666;">ความรู้สึกเชิงลบ<br>เศร้า, โกรธ, ไม่ชอบ</small> | |
</div> | |
<div style="text-align: center; padding: 13px; background: rgba(255, 152, 0, 0.08); border-radius: 11px;"> | |
<div style="font-size: 26px; margin-bottom: 7px;">😐</div> | |
<strong style="color: #FF9800; font-size: 1.08em;">Neutral</strong><br> | |
<small style="color: #666;">ความรู้สึกเป็นกลาง<br>ปกติ, พอใช้ได้</small> | |
</div> | |
<div style="text-align: center; padding: 13px; background: rgba(33, 150, 243, 0.08); border-radius: 11px;"> | |
<div style="font-size: 26px; margin-bottom: 7px;">❓</div> | |
<strong style="color: #2196F3; font-size: 1.08em;">Question</strong><br> | |
<small style="color: #666;">ประโยคคำถาม<br>อะไร, ไหน, เมื่อไหร่</small> | |
</div> | |
</div> | |
</div> | |
""") | |
# Enhanced event handlers | |
def analyze_wrapper(text, model_name): | |
if not text.strip(): | |
return "❗ กรุณาใส่ข้อความที่ต้องการวิเคราะห์" | |
return analyze_text(text, model_name) | |
def clear_text(): | |
return "" | |
# Connect events | |
analyze_btn.click(analyze_wrapper, inputs=[text_input, model_dropdown], outputs=output_box) | |
text_input.submit(analyze_wrapper, inputs=[text_input, model_dropdown], outputs=output_box) | |
model_dropdown.change(analyze_wrapper, inputs=[text_input, model_dropdown], outputs=output_box) | |
clear_btn.click(clear_text, outputs=text_input) | |
# Launch with enhanced settings | |
if __name__ == "__main__": | |
demo.queue(max_size=20).launch( | |
server_name="0.0.0.0", | |
server_port=7860, | |
share=False, # Set to True if you want to create a public link | |
show_error=True, | |
favicon_path=None, # Add your favicon path here | |
ssl_verify=False | |
) | |