Spaces:
Running
Running
| 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 | |
| # Set up logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Enhanced model list with descriptions | |
| 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 for model loading | |
| 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)}") | |
| # Model-specific label mappings | |
| MODEL_LABEL_MAPPINGS = { | |
| # wangchanberta-att-spm-uncased-sentiment - 3 classes ( negative, neutral, positive) | |
| "ZombitX64/wangchanberta-att-spm-uncased-sentiment": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| # MultiSent E5 Pro - 4 classes (question, negative, neutral, positive) | |
| "ZombitX64/MultiSent-E5-Pro": { | |
| "LABEL_0": {"code": 0, "name": "question", "emoji": "🤔", "color": "#60a5fa", "bg": "rgba(96, 165, 250, 0.2)", "description": "คำถาม"}, | |
| "LABEL_1": {"code": 1, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_2": {"code": 2, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_3": {"code": 3, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| # Thai Sentiment E5 - 3 classes (negative, neutral, positive) | |
| "ZombitX64/Thai-sentiment-e5": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| # WangchanBERTa - usually neg/neu/pos | |
| "poom-sci/WangchanBERTa-finetuned-sentiment": { | |
| "neg": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "neu": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "pos": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| # Sandbox Thai - 3 classes | |
| "SandboxBhh/sentiment-thai-text-model": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| # MultiSent E5 - 3 classes | |
| "ZombitX64/MultiSent-E5": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| # WangchanBERTa Hyperopt | |
| "Thaweewat/wangchanberta-hyperopt-sentiment-01": { | |
| "neg": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "neu": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "pos": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| # Twitter XLM-RoBERTa - NEGATIVE/NEUTRAL/POSITIVE | |
| "cardiffnlp/twitter-xlm-roberta-base-sentiment": { | |
| "NEGATIVE": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "NEUTRAL": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "POSITIVE": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| # Wangchan Mobile | |
| "phoner45/wangchan-sentiment-thai-text-model": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| # ZombitX64 Sentiment models - 3 classes | |
| "ZombitX64/Sentiment-01": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| "ZombitX64/Sentiment-02": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| "ZombitX64/Sentiment-03": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| "ZombitX64/sentiment-103": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| "ZombitX64/sentimentSumdata-v1": { | |
| "LABEL_0": {"code": 0, "name": "negative", "emoji": "😢", "color": "#f87171", "bg": "rgba(248, 113, 113, 0.2)", "description": "เชิงลบ"}, | |
| "LABEL_1": {"code": 1, "name": "neutral", "emoji": "😐", "color": "#facc15", "bg": "rgba(250, 204, 21, 0.2)", "description": "เป็นกลาง"}, | |
| "LABEL_2": {"code": 2, "name": "positive", "emoji": "😊", "color": "#34d399", "bg": "rgba(52, 211, 153, 0.2)", "description": "เชิงบวก"}, | |
| }, | |
| } | |
| def get_label_info(label: str, model_name: str) -> Dict: | |
| """Get label information for specific model with fallback for unknown labels""" | |
| model_mappings = MODEL_LABEL_MAPPINGS.get(model_name, {}) | |
| if label in model_mappings: | |
| return model_mappings[label] | |
| # Fallback for unknown labels | |
| return { | |
| "code": -1, | |
| "name": label.lower(), | |
| "emoji": "🔍", | |
| "color": "#64748b", | |
| "bg": "rgba(100, 116, 139, 0.2)", | |
| "description": f"ไม่ทราบ ({label})" | |
| } | |
| def split_sentences(text: str) -> List[str]: | |
| """Enhanced sentence splitting with better Thai support""" | |
| 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: | |
| """Create a modern confidence visualization""" | |
| 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); transition: all 0.3s ease;"></div> | |
| </div> | |
| <span style="font-weight: 600; color: #cbd5e1; min-width: 50px;">{percentage}%</span> | |
| </div> | |
| """ | |
| def analyze_text(text: str, model_name: str) -> str: | |
| """Enhanced text analysis with modern HTML formatting""" | |
| if not text or not text.strip(): | |
| return """ | |
| <div style="padding: 20px; background: rgba(248, 113, 113, 0.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, 0.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, 0.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> | |
| """ | |
| # Header | |
| 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: 0.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 = [] | |
| # Analyze each sentence | |
| for i, sentence in enumerate(sentences, 1): | |
| try: | |
| result = nlp(sentence)[0] | |
| label = result['label'] | |
| score = 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 | |
| # Store result for display | |
| 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 | |
| }) | |
| # Results container | |
| html_parts.append(""" | |
| <div style="background: #0f172a; padding: 0; border-radius: 0 0 16px 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); overflow: hidden;"> | |
| """) | |
| # Individual sentence results | |
| for result in sentence_results: | |
| if 'error' in result: | |
| 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> | |
| เกิดข้อผิดพลาดในการวิเคราะห์ประโยคที่ {result['index']} | |
| </div> | |
| <p style="color: #94a3b8; margin: 8px 0 0 0; font-size: 14px;">{result['error']}</p> | |
| </div> | |
| """) | |
| else: | |
| label_info = result['label_info'] | |
| confidence_bar = create_confidence_bar(result['score']) | |
| html_parts.append(f""" | |
| <div style="padding: 20px; border-bottom: 1px solid #1e293b; transition: all 0.2s ease;" onmouseover="this.style.background='#1e293b'" onmouseout="this.style.background='#0f172a'"> | |
| <div style="display: flex; align-items: flex-start; gap: 16px;"> | |
| <div style="background: {label_info['bg']}; padding: 12px; border-radius: 50%; min-width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;"> | |
| <span style="font-size: 20px;">{label_info['emoji']}</span> | |
| </div> | |
| <div style="flex: 1;"> | |
| <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;"> | |
| <span style="background: {label_info['color']}; color: #f8fafc; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; text-transform: uppercase;"> | |
| {label_info['description']} | |
| </span> | |
| <span style="color: #94a3b8; font-size: 12px; background: #1e293b; padding: 2px 8px; border-radius: 12px;"> | |
| {result['original_label']} | |
| </span> | |
| <span style="color: #94a3b8; font-size: 14px;">ประโยคที่ {result['index']}</span> | |
| </div> | |
| <p style="color: #f8fafc; margin: 0 0 12px 0; font-size: 16px; line-height: 1.5;"> | |
| "{result['sentence'][:150]}{'...' if len(result['sentence']) > 150 else ''}" | |
| </p> | |
| <div style="color: #94a3b8; font-size: 14px; margin-bottom: 8px;">ความมั่นใจ:</div> | |
| {confidence_bar} | |
| </div> | |
| </div> | |
| </div> | |
| """) | |
| # Summary section | |
| total_sentences = len(sentences) | |
| avg_confidence = total_confidence / total_sentences if total_sentences > 0 else 0 | |
| # Create chart data for summary | |
| chart_items = [] | |
| colors = {"positive": "#34d399", "negative": "#f87171", "neutral": "#facc15", "question": "#60a5fa", "other": "#64748b"} | |
| emojis = {"positive": "😊", "negative": "😢", "neutral": "😐", "question": "🤔", "other": "🔍"} | |
| for sentiment, count in sentiment_counts.items(): | |
| if count > 0: | |
| percentage = (count / total_sentences) * 100 | |
| chart_items.append(f""" | |
| <div style="display: flex; align-items: center; gap: 12px; padding: 12px; background: rgba(59, 130, 246, 0.1); border-radius: 8px;"> | |
| <span style="font-size: 24px;">{emojis.get(sentiment, '🔍')}</span> | |
| <div style="flex: 1;"> | |
| <div style="font-weight: 600; color: #f8fafc; text-transform: capitalize;">{sentiment}</div> | |
| <div style="color: #94a3b8; font-size: 14px;">{count} ประโยค ({percentage:.1f}%)</div> | |
| </div> | |
| <div style="width: 60px; height: 6px; background: #334155; border-radius: 3px; overflow: hidden;"> | |
| <div style="width: {percentage}%; height: 100%; background: {colors.get(sentiment, '#64748b')}; transition: all 0.3s ease;"></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; box-shadow: 0 2px 8px rgba(0,0,0,0.3);"> | |
| <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; box-shadow: 0 2px 8px rgba(0,0,0,0.3);"> | |
| <div style="font-size: 32px; font-weight: 700; color: #34d399; margin-bottom: 4px;">{avg_confidence*100:.0f}%</div> | |
| <div style="color: #94a3b8; font-size: 14px;">ความมั่นใจเฉลี่ย</div> | |
| </div> | |
| </div> | |
| <div style="display: grid; gap: 8px;"> | |
| {"".join(chart_items)} | |
| </div> | |
| </div> | |
| """) | |
| html_parts.append("</div>") | |
| return "".join(html_parts) | |
| # Modern CSS with dark blue theme | |
| CUSTOM_CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); | |
| * { font-family: 'Inter', 'Noto Sans Thai', sans-serif !important; } | |
| body, .gradio-container { | |
| background: linear-gradient(135deg, #181f2a 0%, #232e3c 100%) !important; | |
| min-height: 100vh; | |
| } | |
| .main-uxui-card { | |
| background: #232e3c !important; | |
| border-radius: 20px; | |
| box-shadow: 0 6px 32px rgba(0,0,0,0.22); | |
| border: 1.5px solid #2d3a4d; | |
| padding: 32px 28px 28px 28px; | |
| margin: 0 0 32px 0; | |
| color: #e3e8ef !important; | |
| transition: box-shadow 0.2s; | |
| } | |
| .main-uxui-card:hover { | |
| box-shadow: 0 12px 36px rgba(0,0,0,0.28); | |
| } | |
| .main-uxui-header { | |
| text-align: center; | |
| margin-bottom: 32px; | |
| } | |
| .main-uxui-header h1 { | |
| font-size: 2.8em; | |
| color: #e3e8ef; | |
| font-weight: 800; | |
| margin-bottom: 0.2em; | |
| letter-spacing: 0.5px; | |
| } | |
| .main-uxui-header p { | |
| color: #7da2e3; | |
| font-size: 1.25em; | |
| margin-top: 0; | |
| margin-bottom: 0.5em; | |
| } | |
| .main-uxui-section-title { | |
| font-size: 1.18em; | |
| color: #7da2e3; | |
| font-weight: 700; | |
| margin-bottom: 12px; | |
| letter-spacing: 0.2px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .main-uxui-btn { | |
| font-size: 1.13em; | |
| padding: 0.9em 2.7em; | |
| border-radius: 13px; | |
| font-weight: 600; | |
| background: linear-gradient(90deg, #2563eb 0%, #1e293b 100%); | |
| color: #f8fafc !important; | |
| border: none; | |
| box-shadow: 0 2px 8px #1e253355; | |
| transition: all 0.2s; | |
| } | |
| .main-uxui-btn:hover { | |
| filter: brightness(1.08); | |
| box-shadow: 0 6px 18px #1e253377; | |
| transform: translateY(-2px) scale(1.03); | |
| } | |
| .main-uxui-btn.secondary { | |
| background: #232e3c; | |
| color: #7da2e3 !important; | |
| border: 1.5px solid #2d3a4d; | |
| } | |
| .main-uxui-input, .main-uxui-dropdown { | |
| font-size: 1.13em; | |
| border-radius: 10px; | |
| border: 1.5px solid #2d3a4d; | |
| background: #1e2533; | |
| color: #e3e8ef; | |
| padding: 14px; | |
| margin-bottom: 10px; | |
| } | |
| .main-uxui-dropdown { min-width: 220px; } | |
| .main-uxui-output { | |
| background: #1e2533; | |
| border-radius: 14px; | |
| border: 1.5px solid #2d3a4d; | |
| color: #e3e8ef; | |
| padding: 22px 18px; | |
| font-size: 1.08em; | |
| min-height: 180px; | |
| margin-bottom: 0; | |
| } | |
| .main-uxui-legend { | |
| background: #232e3c; | |
| border-radius: 16px; | |
| border: 1.5px solid #2d3a4d; | |
| color: #7da2e3; | |
| padding: 24px 18px; | |
| margin-top: 32px; | |
| font-size: 1.05em; | |
| } | |
| .main-uxui-legend .legend-row { | |
| display: flex; | |
| gap: 24px; | |
| flex-wrap: wrap; | |
| margin-top: 12px; | |
| } | |
| .main-uxui-legend .legend-item { | |
| flex: 1 1 180px; | |
| background: #1e2533; | |
| border-radius: 10px; | |
| padding: 16px 10px; | |
| margin-bottom: 10px; | |
| text-align: center; | |
| border: 1px solid #2d3a4d; | |
| } | |
| .main-uxui-legend .legend-item strong { | |
| color: #e3e8ef; | |
| font-size: 1.08em; | |
| } | |
| .main-uxui-legend .legend-item small { | |
| color: #7da2e3; | |
| } | |
| @media (max-width: 900px) { | |
| .main-uxui-card { padding: 16px 6px; } | |
| .main-uxui-header h1 { font-size: 2em; } | |
| .main-uxui-section-title { font-size: 1em; } | |
| } | |
| """ | |
| # Gradio Blocks app definition | |
| with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Base()) as demo: | |
| with gr.Column(elem_classes="main-uxui-card"): | |
| with gr.Row(): | |
| gr.HTML(""" | |
| <div class='main-uxui-header'> | |
| <h1>Sentiment Analysis</h1> | |
| <p>วิเคราะห์ความรู้สึกภาษาไทย และอื่นๆ etc. รองรับหลายโมเดล</p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| model_dropdown = gr.Dropdown( | |
| choices=[(desc, name) for name, desc in MODEL_LIST], | |
| value=MODEL_LIST[0][0], | |
| label="เลือกโมเดล (Model)", | |
| elem_classes="main-uxui-dropdown" | |
| ) | |
| with gr.Row(): | |
| input_box = gr.Textbox( | |
| lines=4, | |
| 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 secondary") | |
| with gr.Row(): | |
| output_html = gr.HTML(label="ผลลัพธ์", elem_classes="main-uxui-output") | |
| with gr.Row(): | |
| gr.Examples([ | |
| ["วันนี้อากาศดีมากๆ รู้สึกสดชื่นและมีความสุขมาก!"], | |
| ["เศร้ามากเลยวันนี้ งานเยอะเกินไป"], | |
| ["อาหารอร่อยดี แต่บริการช้ามาก"], | |
| ["คุณคิดอย่างไรกับเศรษฐกิจไทย?"], | |
| ["I love this product! It's amazing."], | |
| ["이 제품에는 매우 나쁩니다."], | |
| ["इस उत्पाद के लिए बहुत बुरा.."], | |
| ["This is the worst experience I've ever had."] | |
| ], | |
| inputs=input_box, | |
| label="ตัวอย่างข้อความ", | |
| ) | |
| with gr.Row(): | |
| gr.HTML(""" | |
| <div class='main-uxui-legend'> | |
| <div class='main-uxui-section-title'> | |
| <span>🗂️</span> คำอธิบายผลลัพธ์ | |
| </div> | |
| <div class='legend-row'> | |
| <div class='legend-item'><strong>😊 เชิงบวก</strong><br><small>Positive</small></div> | |
| <div class='legend-item'><strong>😢 เชิงลบ</strong><br><small>Negative</small></div> | |
| <div class='legend-item'><strong>😐 เป็นกลาง</strong><br><small>Neutral</small></div> | |
| <div class='legend-item'><strong>🤔 คำถาม</strong><br><small>Question</small></div> | |
| </div> | |
| </div> | |
| """) | |
| def on_analyze(text, model): | |
| return analyze_text(text, model) | |
| analyze_btn.click(on_analyze, [input_box, model_dropdown], output_html) | |
| input_box.submit(on_analyze, [input_box, model_dropdown], output_html) | |
| model_dropdown.change(on_analyze, [input_box, model_dropdown], output_html) | |
| clear_btn.click(lambda: (""), None, output_html) | |
| # Launch configuration | |
| 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, | |
| favicon_path=None, | |
| ssl_verify=False, | |
| app_kwargs={ | |
| "docs_url": None, | |
| "redoc_url": None, | |
| } | |
| ) |