| |
| import sys |
| class ModuleProtector: |
| def __init__(self, module_name): |
| self.module_name = module_name |
| self.original_module = sys.modules.get(module_name) |
| |
| def __enter__(self): |
| if self.module_name in sys.modules: |
| self.original_module = sys.modules[self.module_name] |
| sys.modules[self.module_name] = None |
| |
| def __exit__(self, *args): |
| if self.original_module is not None: |
| sys.modules[self.module_name] = self.original_module |
|
|
| |
| with ModuleProtector('torch.classes'): |
| import streamlit as st |
|
|
| |
| st.set_page_config( |
| page_title="Multilingual Toxicity Analyzer", |
| page_icon="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNoaWVsZC1wbHVzLWljb24gbHVjaWRlLXNoaWVsZC1wbHVzIj48cGF0aCBkPSJNMjAgMTNjMCA1LTMuNSA3LjUtNy42NiA4Ljk1YTEgMSAwIDAgMS0uNjctLjAxQzcuNSAyMC41IDQgMTggNCAxM1Y2YTEgMSAwIDAgMSAxLTFjMiAwIDQuNS0xLjIgNi4yNC0yLjcyYTEuMTcgMS4xNyAwIDAgMSAxLjUyIDBDMTQuNTEgMy44MSAxNyA1IDE5IDVhMSAxIDAgMCAxIDEgMXoiLz48cGF0aCBkPSJNOSAxMmg2Ii8+PHBhdGggZD0iTTEyIDl2NiIvPjwvc3ZnPg==", |
| layout="wide", |
| initial_sidebar_state="expanded" |
| ) |
|
|
| |
| import torch |
| import os |
| import plotly.graph_objects as go |
| import pandas as pd |
| from model.inference_optimized import OptimizedToxicityClassifier |
| import langid |
| from typing import List, Dict |
| import time |
| import psutil |
| import platform |
| try: |
| import cpuinfo |
| except ImportError: |
| cpuinfo = None |
| from streamlit_extras.colored_header import colored_header |
| from streamlit_extras.add_vertical_space import add_vertical_space |
| from streamlit_extras.stylable_container import stylable_container |
| from streamlit_extras.card import card |
| from streamlit_extras.metric_cards import style_metric_cards |
|
|
| |
| ONNX_MODEL_PATH = os.environ.get("ONNX_MODEL_PATH", "weights/toxic_classifier.onnx") |
| PYTORCH_MODEL_DIR = os.environ.get("PYTORCH_MODEL_DIR", "weights/toxic_classifier_xlm-roberta-large") |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" |
|
|
| |
| def get_gpu_info(): |
| if DEVICE == "cuda": |
| try: |
| gpu_name = torch.cuda.get_device_name(0) |
| gpu_memory_total = torch.cuda.get_device_properties(0).total_memory / 1024**3 |
| gpu_memory_allocated = torch.cuda.memory_allocated(0) / 1024**3 |
| cuda_version = torch.version.cuda |
| |
| memory_info = f"{gpu_memory_allocated:.1f}/{gpu_memory_total:.1f} GB" |
| return f"{gpu_name} (CUDA {cuda_version}, Memory: {memory_info})" |
| except Exception as e: |
| return "CUDA device" |
| return "CPU" |
|
|
| |
| def get_cpu_info(): |
| try: |
| cpu_percent = psutil.cpu_percent(interval=0.1) |
| cpu_count = psutil.cpu_count(logical=True) |
| cpu_freq = psutil.cpu_freq() |
| |
| if cpu_freq: |
| freq_info = f"{cpu_freq.current/1000:.2f} GHz" |
| else: |
| freq_info = "Unknown" |
| |
| |
| cpu_model = None |
| |
| |
| try: |
| with open('/proc/cpuinfo', 'r') as f: |
| for line in f: |
| if 'model name' in line: |
| cpu_model = line.split(':', 1)[1].strip() |
| break |
| except: |
| pass |
| |
| |
| if not cpu_model: |
| cpu_model = platform.processor() |
| |
| |
| if not cpu_model or cpu_model == '': |
| cpu_model = platform.machine() |
| |
| |
| if not cpu_model or cpu_model == '': |
| try: |
| import cpuinfo |
| cpu_model = cpuinfo.get_cpu_info()['brand_raw'] |
| except: |
| pass |
| |
| |
| if cpu_model: |
| |
| replacements = [ |
| '(R)', '(TM)', '(r)', '(tm)', 'CPU', '@', ' ', 'Processor' |
| ] |
| for r in replacements: |
| cpu_model = cpu_model.replace(r, ' ') |
| |
| cpu_model = ' '.join(cpu_model.split()) |
| |
| if len(cpu_model) > 40: |
| cpu_model = cpu_model[:37] + "..." |
| else: |
| cpu_model = "Unknown CPU" |
| |
| return { |
| "name": cpu_model, |
| "cores": cpu_count, |
| "freq": freq_info, |
| "usage": f"{cpu_percent:.1f}%" |
| } |
| except Exception as e: |
| return { |
| "name": "CPU", |
| "cores": "Unknown", |
| "freq": "Unknown", |
| "usage": "Unknown" |
| } |
|
|
| |
| def get_ram_info(): |
| try: |
| ram = psutil.virtual_memory() |
| ram_total = ram.total / (1024**3) |
| ram_used = ram.used / (1024**3) |
| ram_percent = ram.percent |
| |
| return { |
| "total": f"{ram_total:.1f} GB", |
| "used": f"{ram_used:.1f} GB", |
| "percent": f"{ram_percent:.1f}%" |
| } |
| except Exception as e: |
| return { |
| "total": "Unknown", |
| "used": "Unknown", |
| "percent": "Unknown" |
| } |
|
|
| |
| def update_system_resources(): |
| cpu_info = get_cpu_info() |
| ram_info = get_ram_info() |
| |
| return { |
| "cpu": cpu_info, |
| "ram": ram_info |
| } |
|
|
| |
| GPU_INFO = get_gpu_info() |
| SYSTEM_INFO = update_system_resources() |
|
|
| |
| def update_gpu_info(): |
| if DEVICE == "cuda": |
| try: |
| gpu_memory_allocated = torch.cuda.memory_allocated(0) / 1024**3 |
| gpu_memory_total = torch.cuda.get_device_properties(0).total_memory / 1024**3 |
| return f"{gpu_memory_allocated:.1f}/{gpu_memory_total:.1f} GB" |
| except: |
| return "N/A" |
| return "N/A" |
|
|
| |
| def hex_to_rgba(hex_color, alpha=1.0): |
| hex_color = hex_color.lstrip('#') |
| r = int(hex_color[0:2], 16) |
| g = int(hex_color[2:4], 16) |
| b = int(hex_color[4:6], 16) |
| return f'rgba({r}, {g}, {b}, {alpha})' |
|
|
| |
| SUPPORTED_LANGUAGES = { |
| 'en': {'name': 'English', 'flag': '🇺🇸'}, |
| 'ru': {'name': 'Russian', 'flag': '🇷🇺'}, |
| 'tr': {'name': 'Turkish', 'flag': '🇹🇷'}, |
| 'es': {'name': 'Spanish', 'flag': '🇪🇸'}, |
| 'fr': {'name': 'French', 'flag': '🇫🇷'}, |
| 'it': {'name': 'Italian', 'flag': '🇮🇹'}, |
| 'pt': {'name': 'Portuguese', 'flag': '🇵🇹'} |
| } |
|
|
| |
| LANGUAGE_EXAMPLES = { |
| 'en': { |
| 'toxic': [ |
| "You are such an idiot, nobody likes your stupid content.", |
| "Shut up you worthless piece of garbage. Everyone hates you.", |
| "This is the most pathetic thing I've ever seen. Only losers would think this is good.", |
| "Just kill yourself already, the world would be better without you." |
| ], |
| 'non_toxic': [ |
| "I disagree with your opinion, but I appreciate your perspective.", |
| "This content could use some improvement, but I see the effort you put into it.", |
| "While I don't personally enjoy this type of content, others might find it valuable.", |
| "Thank you for sharing your thoughts on this complex topic." |
| ] |
| }, |
| 'ru': { |
| 'toxic': [ |
| "Ты полный придурок, твой контент никому не нравится.", |
| "Заткнись, бесполезный кусок мусора. Все тебя ненавидят.", |
| "Это самая жалкая вещь, которую я когда-либо видел. Только неудачники думают, что это хорошо.", |
| "Почему бы тебе просто не исчезнуть нахрен? Никто не будет скучать по тебе." |
| ], |
| 'non_toxic': [ |
| "Я не согласен с вашим мнением, но уважаю вашу точку зрения.", |
| "Этот контент можно улучшить, но я вижу, сколько усилий вы в него вложили.", |
| "Хотя мне лично не нравится такой контент, другие могут найти его полезным.", |
| "Спасибо, что поделились своими мыслями на эту сложную тему." |
| ] |
| }, |
| 'tr': { |
| 'toxic': [ |
| "Sen tam bir aptalsın, kimse senin aptalca içeriğini beğenmiyor.", |
| "Kapa çeneni değersiz çöp parçası. Herkes senden nefret ediyor.", |
| "Bu gördüğüm en acıklı şey. Sadece lanet olası kaybedenler bunun iyi olduğunu düşünür.", |
| "Dünya sensiz daha iyi olurdu, kaybol git." |
| ], |
| 'non_toxic': [ |
| "Fikrinize katılmıyorum ama bakış açınızı takdir ediyorum.", |
| "Bu içerik biraz geliştirilebilir, ancak gösterdiğiniz çabayı görüyorum.", |
| "Şahsen bu tür içerikten hoşlanmasam da, başkaları bunu değerli bulabilir.", |
| "Bu karmaşık konu hakkındaki düşüncelerinizi paylaştığınız için teşekkür ederim." |
| ] |
| }, |
| 'es': { |
| 'toxic': [ |
| "Eres un completo idiota y nadie te quiere.", |
| "Cállate, pedazo de basura inútil. Todos te odian.", |
| "Esto es lo más patético que he visto nunca. Solo los perdedores pensarían que esto es bueno.", |
| "El mundo estaría mejor sin ti, deberías desaparecer, joder." |
| ], |
| 'non_toxic': [ |
| "No estoy de acuerdo con tu opinión, pero aprecio tu perspectiva.", |
| "Este contenido podría mejorarse, pero veo el esfuerzo que has puesto en él.", |
| "Aunque personalmente no disfruto este tipo de contenido, otros podrían encontrarlo valioso.", |
| "Gracias por compartir tus pensamientos sobre este tema tan complejo." |
| ] |
| }, |
| 'fr': { |
| 'toxic': [ |
| "Tu es tellement stupide, personne n'aime ton contenu minable.", |
| "Ferme-la, espèce de déchet inutile. Tout le monde te déteste.", |
| "C'est la chose la plus pathétique que j'ai jamais vue. Seuls les loosers penseraient que c'est bien.", |
| "Le monde serait meilleur sans toi, connard, va-t'en." |
| ], |
| 'non_toxic': [ |
| "Je ne suis pas d'accord avec ton opinion, mais j'apprécie ta perspective.", |
| "Ce contenu pourrait être amélioré, mais je vois l'effort que tu y as mis.", |
| "Bien que personnellement je n'apprécie pas ce type de contenu, d'autres pourraient le trouver précieux.", |
| "Merci d'avoir partagé tes réflexions sur ce sujet complexe." |
| ] |
| }, |
| 'it': { |
| 'toxic': [ |
| "Sei un tale idiota, a nessuno piace il tuo contenuto stupido.", |
| "Chiudi quella bocca, pezzo di spazzatura inutile. Tutti ti odiano.", |
| "Questa è la cosa più patetica che abbia mai visto. Solo i perdenti penserebbero che sia buona.", |
| "Il mondo sarebbe migliore senza di te, sparisci." |
| ], |
| 'non_toxic': [ |
| "Non sono d'accordo con la tua opinione, ma apprezzo la tua prospettiva.", |
| "Questo contenuto potrebbe essere migliorato, ma vedo lo sforzo che ci hai messo.", |
| "Anche se personalmente non apprezzo questo tipo di contenuto, altri potrebbero trovarlo utile.", |
| "Grazie per aver condiviso i tuoi pensieri su questo argomento complesso." |
| ] |
| }, |
| 'pt': { |
| 'toxic': [ |
| "Você é um idiota completo, ninguém gosta do seu conteúdo estúpido.", |
| "Cale a boca, seu pedaço de lixo inútil. Todos te odeiam.", |
| "Isso é a coisa mais patética que eu já vi. Só perdedores pensariam que isso é bom.", |
| "O mundo seria melhor sem você, desapareça." |
| ], |
| 'non_toxic': [ |
| "Eu discordo da sua opinião, mas aprecio sua perspectiva.", |
| "Este conteúdo poderia ser melhorado, mas vejo o esforço que você colocou nele.", |
| "Embora eu pessoalmente não goste deste tipo de conteúdo, outros podem achá-lo valioso.", |
| "Obrigado por compartilhar seus pensamentos sobre este tema complexo." |
| ] |
| } |
| } |
|
|
| |
| THEME = { |
| "primary": "#2D3142", |
| "background": "#FFFFFF", |
| "surface": "#FFFFFF", |
| "text": "#000000", |
| "text_secondary": "#FFFFFF", |
| "button": "#000000", |
| "toxic": "#E53935", |
| "non_toxic": "#2E7D32", |
| "warning": "#F57C00", |
| "info": "#1976D2", |
| "sidebar_bg": "#FFFFFF", |
| "card_bg": "white", |
| "input_bg": "#F8F9FA" |
| } |
|
|
| |
| st.markdown(f""" |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap'); |
| |
| :root, html, body, [class*="css"] {{ |
| font-family: 'Space Grotesk', sans-serif; |
| color: {THEME["text"]} !important; |
| border: 1px solid {THEME["text"]} !important; |
| overflow-y: scroll; |
| overflow-x: hidden; |
| }} |
| |
| svg, path{{ |
| color: {THEME["text"]}; |
| }} |
| |
| /* Heading font styling */ |
| h1, h2, h3, h4, h5, h6 {{ |
| font-family: 'Space Grotesk', sans-serif; |
| letter-spacing: -0.02em; |
| color: {THEME["text"]}; |
| }} |
| |
| .st-emotion{{ |
| background-color: {THEME["background"]}; |
| }} |
| |
| [data-testid="stMarkdownContainer"] h1, |
| [data-testid="stMarkdownContainer"] h2, |
| [data-testid="stMarkdownContainer"] h3 {{ |
| font-family: 'Space Grotesk', sans-serif; |
| font-weight: 600; |
| color: {THEME["text"]}; |
| }} |
| |
| /* Examples section styling */ |
| .examples-section {{ |
| margin-top: 15px; |
| color: {THEME["text"]}; |
| }} |
| |
| .example-button {{ |
| text-align: left; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| margin-bottom: 5px; |
| transition: all 0.2s ease; |
| background-color: {THEME["input_bg"]}; |
| border-radius: 8px; |
| color: {THEME["text"]}; |
| }} |
| |
| .example-button:hover {{ |
| transform: translateX(3px); |
| background-color: {hex_to_rgba(THEME["primary"], 0.1)}; |
| }} |
| |
| /* Style tab content */ |
| .stTabs [data-baseweb="tab-panel"] {{ |
| padding-top: 1rem; |
| color: {THEME["text"]}; |
| }} |
| |
| /* Tab content styling */ |
| .stTabs [data-baseweb="tab"] {{ |
| font-family: 'Space Grotesk', sans-serif; |
| font-weight: 500; |
| color: {THEME["text"]}; |
| }} |
| |
| /* Style expandable sections */ |
| div[data-testid="stExpander"] {{ |
| margin-bottom: 10px !important; |
| background-color: {THEME["card_bg"]}; |
| border: 1px solid {hex_to_rgba(THEME["text"], 0.1)}; |
| color: {THEME["text"]} !important; |
| }} |
| |
| div[data-testid="stExpander"] div[data-testid="stExpanderContent"] {{ |
| max-height: 300px; |
| overflow-y: auto; |
| padding: 5px 10px; |
| }} |
| |
| /* Hardware info styles */ |
| .hardware-info {{ |
| background-color: {hex_to_rgba(THEME["primary"], 0.05)}; |
| border-radius: 10px; |
| padding: 12px; |
| margin: 8px 0; |
| border-left: 3px solid {THEME["primary"]}; |
| color: {THEME["text"]}; |
| }} |
| |
| .hardware-title {{ |
| font-family: 'Space Grotesk', sans-serif; |
| font-weight: 600; |
| font-size: 1.1rem; |
| margin-bottom: 8px; |
| display: flex; |
| align-items: center; |
| color: {THEME["primary"]}; |
| }} |
| |
| /* Override Streamlit's default background */ |
| .stApp {{ |
| background-color: {THEME["background"]}; |
| }} |
| |
| .st-emotion-cache-h4xjwg{{ |
| background-color: {THEME["background"]}; |
| color: {THEME["text"]}; |
| }} |
| |
| /* Code editor and text areas */ |
| .stTextInput>div>div>input, .stTextArea>div>div>textarea {{ |
| background-color: {THEME["input_bg"]}; |
| color: {THEME["text"]}; |
| font-family: 'Space Grotesk', sans-serif; |
| }} |
| |
| /* Sidebar styling */ |
| section[data-testid="stSidebar"] {{ |
| background-color: {THEME["sidebar_bg"]}; |
| color: {THEME["text"]}; |
| }} |
| |
| section[data-testid="stSidebar"] [data-testid="stMarkdown"] {{ |
| color: {THEME["text"]}; |
| background-color: {THEME["background"]}; |
| }} |
| |
| section[data-testid="stSidebar"] .stSelectbox label, |
| section[data-testid="stSidebar"] .stButton label {{ |
| color: {THEME["text"]} !important; |
| background-color: {THEME["background"]} !important; |
| }} |
| |
| section[data-testid="stSidebar"] h3 {{ |
| color: {THEME["text"]}; |
| background-color: {THEME["background"]}; |
| }} |
| |
| section[data-testid="stSidebar"] .main-title {{ |
| color: {THEME["text"]}; |
| -webkit-text-fill-color: {THEME["text"]}; |
| background-color: {THEME["background"]}; |
| }} |
| |
| section[data-testid="stSidebar"] h1 {{ |
| color: {THEME["text"]}; |
| background-color: {THEME["background"]}; |
| }} |
| |
| .main-title {{ |
| font-family: 'Space Grotesk', sans-serif; |
| font-size: 2.8rem; |
| font-weight: 700; |
| color: {THEME["text"]}; |
| margin-bottom: 1rem; |
| letter-spacing: -0.03em; |
| }} |
| |
| .subtitle {{ |
| font-family: 'Space Grotesk', sans-serif; |
| font-size: 1.2rem; |
| font-weight: 400; |
| color: {THEME["text"]}; |
| margin-bottom: 2rem; |
| }} |
| |
| .category-label {{ |
| font-weight: 600; |
| }} |
| |
| .toxic-category {{ |
| padding: 3px 8px; |
| border-radius: 12px; |
| background-color: {hex_to_rgba(THEME["toxic"], 0.13)}; |
| border: 1px solid {hex_to_rgba(THEME["toxic"], 0.31)}; |
| margin-right: 5px; |
| font-weight: 500; |
| display: inline-block; |
| margin-bottom: 5px; |
| font-size: 0.9rem; |
| color: {THEME["toxic"]}; |
| transition: all 0.3s ease; |
| }} |
| |
| .toxic-result {{ |
| font-family: 'Space Grotesk', sans-serif; |
| font-size: 1.2rem; |
| font-weight: 700; |
| padding: 4px 12px; |
| border-radius: 8px; |
| display: inline-block; |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); |
| transition: all 0.3s ease; |
| letter-spacing: -0.02em; |
| }} |
| |
| .model-info {{ |
| border-left: 3px solid {THEME["primary"]}; |
| padding-left: 10px; |
| transition: all 0.3s ease; |
| }} |
| |
| .stButton button {{ |
| font-family: 'Space Grotesk', sans-serif; |
| border-radius: 8px; |
| border: none; |
| font-weight: 600; |
| transition: all 0.3s ease; |
| box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
| background-color: {THEME["button"]} !important; |
| color: {THEME["text_secondary"]} !important; |
| padding: 0.75rem 2rem; |
| font-size: 1.1rem; |
| letter-spacing: 0.02em; |
| }} |
| |
| .stButton button:hover {{ |
| transform: translateY(-2px); |
| box-shadow: 0 4px 10px rgba(0,0,0,0.2); |
| background-color: {hex_to_rgba(THEME["button"], 0.9)} !important; |
| }} |
| |
| |
| |
| /* Cards for metrics at top */ |
| div[data-testid="metric-container"] {{ |
| background-color: {THEME["card_bg"]}; |
| border-radius: 10px; |
| padding: 15px; |
| box-shadow: 0 2px 6px rgba(0,0,0,0.05); |
| border: 1px solid {hex_to_rgba(THEME["text"], 0.1)}; |
| }} |
| |
| /* Fix metric label colors - more specific selectors */ |
| div[data-testid="metric-container"] > div:first-child {{ |
| color: {THEME["text"]} !important; |
| font-family: 'Space Grotesk', sans-serif; |
| font-weight: 500; |
| }} |
| |
| div[data-testid="metric-container"] > div:first-child > label {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Target the specific label element */ |
| div[data-testid="stMetricLabel"] {{ |
| color: {THEME["text"]} !important; |
| font-weight: 500 !important; |
| }} |
| |
| div[data-testid="stMetricLabel"] > div {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Ensure metric value is also properly colored */ |
| div[data-testid="stMetricValue"] {{ |
| color: {THEME["text"]} !important; |
| font-weight: 600 !important; |
| }} |
| |
| /* Target all text within metric containers */ |
| div[data-testid="metric-container"] * {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Additional specific targeting for metric labels */ |
| [data-testid="metric-container-label"] {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| [data-testid="metric-container-label-value"] {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Force black color on metric labels */ |
| div[data-testid="metric-container"] label, |
| div[data-testid="metric-container"] div[role="button"] {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Remove default Streamlit menu and footer */ |
| #MainMenu {{visibility: hidden;}} |
| footer {{visibility: hidden;}} |
| |
| /* Style dataframe */ |
| .stDataFrame {{ |
| background-color: {THEME["card_bg"]}; |
| }} |
| |
| .footer {{ |
| text-align: center; |
| opacity: 0.7; |
| padding: 20px; |
| transition: all 0.3s ease; |
| color: {THEME["text"]}; |
| font-family: 'Space Grotesk', sans-serif; |
| }} |
| |
| .footer:hover {{ |
| opacity: 1; |
| }} |
| |
| /* Fix dropdown text color */ |
| div[data-baseweb="select"] div[class*="valueContainer"] {{ |
| color: {THEME["text_secondary"]} !important; |
| }} |
| |
| div[data-baseweb="select"] div[class*="placeholder"] {{ |
| color: {THEME["text_secondary"]} !important; |
| }} |
| |
| div[data-baseweb="select"] div[class*="singleValue"] {{ |
| color: {THEME["text_secondary"]} !important; |
| }} |
| |
| /* Fix button text color */ |
| .stButton > button {{ |
| background-color: {THEME["button"]} !important; |
| color: {THEME["text_secondary"]} !important; |
| font-family: 'Space Grotesk', sans-serif !important; |
| font-weight: 600 !important; |
| font-size: 1.1rem !important; |
| padding: 0.75rem 2rem !important; |
| border: none !important; |
| border-radius: 8px !important; |
| transition: all 0.2s ease !important; |
| box-shadow: 0 2px 5px rgba(0,0,0,0.1) !important; |
| letter-spacing: 0.02em !important; |
| }} |
| |
| .stButton > button:hover {{ |
| transform: translateY(-2px) !important; |
| box-shadow: 0 4px 10px rgba(0,0,0,0.2) !important; |
| background-color: {hex_to_rgba(THEME["button"], 0.9)} !important; |
| color: {THEME["text_secondary"]} !important; |
| }} |
| |
| /* Fix dropdown styling */ |
| div[data-baseweb="select"] {{ |
| background-color: {THEME["button"]} !important; |
| border-color: {hex_to_rgba(THEME["button"], 0.5)} !important; |
| border-radius: 8px !important; |
| }} |
| |
| /* Fix dropdown options */ |
| div[data-baseweb="popover"] {{ |
| background-color: {THEME["text"]} !important; |
| color: {THEME["text"]} !important; |
| }} |
| |
| div[data-baseweb="popover"] div[role="option"] {{ |
| color: {THEME["text_secondary"]} !important; |
| }} |
| |
| div[data-baseweb="popover"] div[role="option"]:hover {{ |
| background-color: {hex_to_rgba(THEME["button"], 0.7)} !important; |
| }} |
| |
| /* Fix dropdown arrow color */ |
| div[data-baseweb="select"] svg {{ |
| color: {THEME["text_secondary"]} !important; |
| }} |
| |
| /* Fix input label color */ |
| .stTextArea label {{ |
| color: {THEME["text"]} !important; |
| font-weight: 500 !important; |
| }} |
| |
| /* Fix selectbox label */ |
| .stSelectbox label {{ |
| color: {THEME["text"]} !important; |
| font-weight: 500 !important; |
| }} |
| |
| /* Ensure consistent black color */ |
| .stButton > button {{ |
| background-color: #000000 !important; |
| }} |
| |
| div[data-baseweb="select"] {{ |
| background-color: #000000 !important; |
| }} |
| |
| div[data-baseweb="popover"] {{ |
| background-color: #000000 !important; |
| }} |
| |
| /* Style How to section */ |
| .usage-step {{ |
| background-color: {THEME["card_bg"]}; |
| border: 1px solid {hex_to_rgba(THEME["text"], 0.1)}; |
| border-radius: 10px; |
| padding: 1rem; |
| margin-bottom: 1rem; |
| display: flex; |
| align-items: center; |
| transition: all 0.2s ease; |
| }} |
| |
| .usage-step:hover {{ |
| transform: translateX(5px); |
| border-color: {hex_to_rgba(THEME["button"], 0.3)}; |
| box-shadow: 0 2px 8px rgba(0,0,0,0.05); |
| }} |
| |
| .step-number {{ |
| background-color: {THEME["button"]}; |
| color: {THEME["text_secondary"]}; |
| font-family: 'Space Grotesk', sans-serif; |
| font-size: 1rem; |
| font-weight: 600; |
| min-width: 2.5rem; |
| height: 2.5rem; |
| border-radius: 30%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| margin-right: 1.2rem; |
| }} |
| |
| .usage-step div:last-child {{ |
| font-family: 'Space Grotesk', sans-serif; |
| font-size: 1rem; |
| color: {THEME["text"]}; |
| flex: 1; |
| line-height: 1.4; |
| }} |
| |
| /* Style the How to section header */ |
| [data-testid="stHeader"] {{ |
| background-color: transparent !important; |
| }} |
| |
| .colored-header {{ |
| margin: 2rem 0 1.5rem 0; |
| }} |
| |
| .colored-header h1 {{ |
| font-family: 'Space Grotesk', sans-serif; |
| font-size: 2rem; |
| font-weight: 700; |
| color: {THEME["text"]}; |
| margin-bottom: 0.5rem; |
| }} |
| |
| .colored-header p {{ |
| font-family: 'Space Grotesk', sans-serif; |
| font-size: 1.1rem; |
| color: {hex_to_rgba(THEME["text"], 0.8)}; |
| }} |
| |
| /* Fix plotly chart axis labels */ |
| .js-plotly-plot .plotly .g-gtitle {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| .js-plotly-plot .plotly .xtitle, |
| .js-plotly-plot .plotly .ytitle {{ |
| fill: {THEME["text"]} !important; |
| color: {THEME["text"]} !important; |
| }} |
| |
| .js-plotly-plot .plotly .xtick text, |
| .js-plotly-plot .plotly .ytick text {{ |
| fill: {THEME["text"]} !important; |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Fix tab text color */ |
| button[data-baseweb="tab"] {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| button[data-baseweb="tab"][aria-selected="true"] {{ |
| color: {THEME["primary"]} !important; |
| }} |
| |
| /* Fix expander arrow color */ |
| div[data-testid="stExpander"] svg {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Ensure plotly modebar buttons are visible */ |
| .modebar-btn path {{ |
| fill: {THEME["text"]} !important; |
| }} |
| |
| /* Fix any remaining white text on white background issues */ |
| .element-container, .stMarkdown, .stText {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Ensure text inputs have black text */ |
| .stTextInput input, .stTextArea textarea {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Fix plotly legend text */ |
| .js-plotly-plot .plotly .legend text {{ |
| fill: {THEME["text"]} !important; |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Fix success message color */ |
| .stSuccess {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Fix success icon color */ |
| .stSuccess svg {{ |
| fill: {THEME["text"]} !important; |
| }} |
| |
| /* Ensure all alert messages have proper text color */ |
| div[data-baseweb="notification"] {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Fix input area text color */ |
| textarea {{ |
| color: {THEME["text"]} !important; |
| }} |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| |
| st.markdown(f""" |
| <style> |
| /* Direct targeting of metric labels */ |
| [data-testid="stMetricLabel"] {{ |
| color: {THEME["text"]} !important; |
| font-weight: 500 !important; |
| }} |
| |
| [data-testid="stMetricLabel"] span {{ |
| color: {THEME["text"]} !important; |
| font-weight: 500 !important; |
| }} |
| |
| /* Target the label content directly */ |
| [data-testid="stMetricLabel"] div {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Target every element inside a metric label */ |
| [data-testid="stMetricLabel"] * {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Style the value too */ |
| [data-testid="stMetricValue"] {{ |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Extremely specific selector to ensure it overrides everything */ |
| div[data-testid="metric-container"] div[data-testid="stMetricLabel"] {{ |
| color: {THEME["text"]} !important; |
| }} |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| |
| @st.cache_resource |
| def load_classifier(): |
| try: |
| if os.path.exists(ONNX_MODEL_PATH): |
| classifier = OptimizedToxicityClassifier(onnx_path=ONNX_MODEL_PATH, device=DEVICE) |
| st.session_state['model_type'] = 'Loaded' |
| return classifier |
| elif os.path.exists(PYTORCH_MODEL_DIR): |
| classifier = OptimizedToxicityClassifier(pytorch_path=PYTORCH_MODEL_DIR, device=DEVICE) |
| st.session_state['model_type'] = 'Loaded' |
| return classifier |
| else: |
| st.error(f"❌ No model found at {ONNX_MODEL_PATH} or {PYTORCH_MODEL_DIR}") |
| return None |
| except Exception as e: |
| st.error(f"Error loading model: {str(e)}") |
| import traceback |
| st.error(traceback.format_exc()) |
| return None |
|
|
| def detect_language(text: str) -> str: |
| """Detect language of input text""" |
| try: |
| lang, _ = langid.classify(text) |
| return lang if lang in SUPPORTED_LANGUAGES else 'en' |
| except: |
| return 'en' |
|
|
| def predict_toxicity(text: str, selected_language: str = "Auto-detect") -> Dict: |
| """Predict toxicity of input text""" |
| if not text or not text.strip(): |
| return { |
| "error": "Please enter some text to analyze.", |
| "results": None |
| } |
| |
| if not st.session_state.get('model_loaded', False): |
| return { |
| "error": "Model not loaded. Please check logs.", |
| "results": None |
| } |
| |
| |
| with st.spinner("Analyzing text..."): |
| |
| start_time = time.time() |
| |
| |
| if selected_language == "Auto-detect": |
| lang_detection_start = time.time() |
| lang_code = detect_language(text) |
| lang_detection_time = time.time() - lang_detection_start |
| detected = True |
| else: |
| |
| selected_name = selected_language.split(' ')[1] if len(selected_language.split(' ')) > 1 else selected_language |
| lang_code = next((code for code, info in SUPPORTED_LANGUAGES.items() |
| if info['name'] == selected_name), 'en') |
| lang_detection_time = 0 |
| detected = False |
| |
| |
| try: |
| model_inference_start = time.time() |
| results = classifier.predict([text], langs=[lang_code])[0] |
| model_inference_time = time.time() - model_inference_start |
| total_time = time.time() - start_time |
| |
| return { |
| "results": results, |
| "detected": detected, |
| "lang_code": lang_code, |
| "performance": { |
| "total_time": total_time, |
| "lang_detection_time": lang_detection_time, |
| "model_inference_time": model_inference_time |
| } |
| } |
| except Exception as e: |
| import traceback |
| traceback.print_exc() |
| return { |
| "error": f"Error processing text: {str(e)}", |
| "results": None |
| } |
|
|
| |
| def set_example(lang_code, example_type, example_index=0): |
| st.session_state['use_example'] = True |
| |
| example = LANGUAGE_EXAMPLES[lang_code][example_type][example_index] |
| st.session_state['example_text'] = example |
| st.session_state['detected_lang'] = lang_code |
| st.session_state['example_info'] = { |
| 'type': example_type, |
| 'lang': lang_code, |
| 'index': example_index |
| } |
|
|
| |
| if 'use_example' not in st.session_state: |
| st.session_state['use_example'] = False |
| st.session_state['example_text'] = "" |
| st.session_state['detected_lang'] = "Auto-detect" |
| st.session_state['example_info'] = None |
|
|
| |
| with st.sidebar: |
| st.markdown("<h1 class='main-title'>Multilingual Toxicity Analyzer</h1>", unsafe_allow_html=True) |
| |
| st.markdown(""" |
| #### This app analyzes text for different types of toxicity across multiple languages with high accuracy. |
| """) |
| |
| |
| st.markdown("#### Supported Languages:") |
| lang_cols = st.columns(2) |
| |
| for i, (code, info) in enumerate(SUPPORTED_LANGUAGES.items()): |
| col_idx = i % 2 |
| with lang_cols[col_idx]: |
| st.markdown(f"<div class='language-option'><span class='language-flag'>{info['flag']}</span> {info['name']}</div>", |
| unsafe_allow_html=True) |
| |
| st.divider() |
| |
| |
| st.markdown("### 🌐 Select Language") |
| language_options = ["Auto-detect"] + [f"{info['flag']} {info['name']}" for code, info in SUPPORTED_LANGUAGES.items()] |
| selected_language = st.selectbox( |
| "Choose language or use auto-detect", |
| language_options, |
| index=0, |
| key="selected_language", |
| help="Choose a specific language or use auto-detection" |
| ) |
| |
| |
| st.markdown("### 📝 Try with examples:") |
| |
| |
| example_tabs = st.tabs(["Toxic Examples", "Non-Toxic Examples"]) |
| |
| |
| ordered_langs = ['en', 'es', 'fr', 'pt', 'it', 'ru', 'tr'] |
| |
| |
| with example_tabs[0]: |
| st.markdown('<div class="examples-section">', unsafe_allow_html=True) |
| for lang_code in ordered_langs: |
| info = SUPPORTED_LANGUAGES[lang_code] |
| with st.expander(f"{info['flag']} {info['name']} examples"): |
| for i, example in enumerate(LANGUAGE_EXAMPLES[lang_code]['toxic']): |
| |
| preview = example[:40] + "..." if len(example) > 40 else example |
| button_key = f"toxic_{lang_code}_{i}" |
| button_help = f"Try with this {info['name']} toxic example" |
| |
| |
| if st.button(f"Example {i+1}: {preview}", |
| key=button_key, |
| use_container_width=True, |
| help=button_help): |
| set_example(lang_code, 'toxic', i) |
| st.markdown('</div>', unsafe_allow_html=True) |
| |
| |
| with example_tabs[1]: |
| st.markdown('<div class="examples-section">', unsafe_allow_html=True) |
| for lang_code in ordered_langs: |
| info = SUPPORTED_LANGUAGES[lang_code] |
| with st.expander(f"{info['flag']} {info['name']} examples"): |
| for i, example in enumerate(LANGUAGE_EXAMPLES[lang_code]['non_toxic']): |
| |
| preview = example[:40] + "..." if len(example) > 40 else example |
| button_key = f"non_toxic_{lang_code}_{i}" |
| button_help = f"Try with this {info['name']} non-toxic example" |
| |
| if st.button(f"Example {i+1}: {preview}", |
| key=button_key, |
| use_container_width=True, |
| help=button_help): |
| set_example(lang_code, 'non_toxic', i) |
| st.markdown('</div>', unsafe_allow_html=True) |
| |
| st.divider() |
| |
| |
| st.markdown("### 💻 System Information", unsafe_allow_html=True) |
| |
| |
| current_sys_info = update_system_resources() |
| |
| |
| if DEVICE == "cuda": |
| st.markdown(""" |
| <div class="hardware-info"> |
| <div class="hardware-title"><span class="icon">🎮</span> GPU</div> |
| <div class="hardware-resource"> |
| """, unsafe_allow_html=True) |
| |
| gpu_name = GPU_INFO.split(" (")[0] |
| st.markdown(f"<div class='hardware-stat'><span class='label'>Model:</span> <span class='value'>{gpu_name}</span></div>", unsafe_allow_html=True) |
| |
| cuda_version = "Unknown" |
| if "CUDA" in GPU_INFO: |
| cuda_version = GPU_INFO.split("CUDA ")[1].split(",")[0] |
| st.markdown(f"<div class='hardware-stat'><span class='label'>CUDA:</span> <span class='value'>{cuda_version}</span></div>", unsafe_allow_html=True) |
| |
| current_gpu_memory = update_gpu_info() |
| st.markdown(f"<div class='hardware-stat'><span class='label'>Memory:</span> <span class='value'>{current_gpu_memory}</span></div>", unsafe_allow_html=True) |
| |
| st.markdown("</div></div>", unsafe_allow_html=True) |
| |
| |
| st.markdown(""" |
| <div class="hardware-info"> |
| <div class="hardware-title"><span class="icon">⚙️</span> CPU</div> |
| <div class="hardware-resource"> |
| """, unsafe_allow_html=True) |
| |
| cpu_info = current_sys_info["cpu"] |
| st.markdown(f"<div class='hardware-stat'><span class='label'>Model:</span> <span class='value'>{cpu_info['name']}</span></div>", unsafe_allow_html=True) |
| st.markdown(f"<div class='hardware-stat'><span class='label'>Cores:</span> <span class='value'>{cpu_info['cores']}</span></div>", unsafe_allow_html=True) |
| st.markdown(f"<div class='hardware-stat'><span class='label'>Frequency:</span> <span class='value'>{cpu_info['freq']}</span></div>", unsafe_allow_html=True) |
| st.markdown(f"<div class='hardware-stat'><span class='label'>Usage:</span> <span class='value'>{cpu_info['usage']}</span></div>", unsafe_allow_html=True) |
| |
| st.markdown("</div></div>", unsafe_allow_html=True) |
| |
| |
| st.markdown(""" |
| <div class="hardware-info"> |
| <div class="hardware-title"><span class="icon">🧠</span> RAM</div> |
| <div class="hardware-resource"> |
| """, unsafe_allow_html=True) |
| |
| ram_info = current_sys_info["ram"] |
| st.markdown(f"<div class='hardware-stat'><span class='label'>Total:</span> <span class='value'>{ram_info['total']}</span></div>", unsafe_allow_html=True) |
| st.markdown(f"<div class='hardware-stat'><span class='label'>Used:</span> <span class='value'>{ram_info['used']}</span></div>", unsafe_allow_html=True) |
| st.markdown(f"<div class='hardware-stat'><span class='label'>Usage:</span> <span class='value'>{ram_info['percent']}</span></div>", unsafe_allow_html=True) |
| |
| st.markdown("</div></div>", unsafe_allow_html=True) |
| |
| st.divider() |
| |
| |
| st.markdown("### ⚙️ Toxicity Thresholds") |
| st.markdown(""" |
| <div class='threshold-bg'> |
| The model uses language-specific thresholds to determine if a text is toxic: |
| |
| - **Toxic**: 60% |
| - **Severe Toxic**: 54% |
| - **Obscene**: 60% |
| - **Threat**: 48% |
| - **Insult**: 60% |
| - **Identity Hate**: 50% |
| |
| These increased thresholds reduce false positives but may miss borderline toxic content. |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| if 'model_loaded' not in st.session_state: |
| with st.spinner("🔄 Loading model..."): |
| classifier = load_classifier() |
| if classifier: |
| st.session_state['model_loaded'] = True |
| st.success(f"✅ Model loaded successfully on {GPU_INFO}") |
| else: |
| st.session_state['model_loaded'] = False |
| st.error("❌ Failed to load model. Please check logs.") |
| else: |
| |
| classifier = load_classifier() |
|
|
| |
| st.markdown(""" |
| <h1 class='main-title'> |
| <svg xmlns="http://www.w3.org/2000/svg" style="padding-bottom: 10px;" width="45" height="45" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield-plus-icon lucide-shield-plus"> |
| <path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/> |
| <path d="M9 12h6"/> |
| <path d="M12 9v6"/> |
| </svg> |
| Multilingual Toxicity Analyzer |
| </h1> |
| """, unsafe_allow_html=True) |
| st.markdown(""" |
| <p class='subtitle'>Detect toxic content in multiple languages with state-of-the-art accuracy</p> |
| """, unsafe_allow_html=True) |
|
|
| |
| with stylable_container( |
| key="text_input_container", |
| css_styles=f""" |
| {{ |
| border-radius: 10px; |
| overflow: hidden; |
| transition: all 0.3s ease; |
| box-shadow: 0 2px 8px rgba(0,0,0,0.15); |
| background-color: {THEME["card_bg"]}; |
| padding: 10px; |
| margin-bottom: 15px; |
| }} |
| |
| textarea {{ |
| caret-color: black !important; |
| color: {THEME["text"]} !important; |
| }} |
| |
| /* Ensure the text input cursor is visible */ |
| .stTextArea textarea {{ |
| caret-color: black !important; |
| }} |
| """ |
| ): |
| |
| current_example = st.session_state.get('example_text', '') |
| |
| |
| text_input = st.text_area( |
| "Enter text to analyze", |
| height=80, |
| value=current_example if st.session_state.get('use_example', False) else st.session_state.get('text_input', ''), |
| key="text_input", |
| help="Enter text in any supported language to analyze for toxicity" |
| ) |
| |
| |
| if st.session_state.get('use_example', False) and text_input != current_example: |
| |
| st.session_state['use_example'] = False |
| st.session_state['example_text'] = "" |
| st.session_state['example_info'] = None |
|
|
| |
| col1, col2, col3 = st.columns([1, 2, 1]) |
| with col2: |
| analyze_button = st.button( |
| "Analyze Text", |
| type="primary", |
| use_container_width=True, |
| help="Click to analyze the entered text for toxicity" |
| ) |
|
|
| |
| if analyze_button or (text_input and 'last_analyzed' not in st.session_state or st.session_state.get('last_analyzed') != text_input): |
| if text_input: |
| st.session_state['last_analyzed'] = text_input |
| |
| |
| pre_prediction_resources = update_system_resources() |
| |
| |
| prediction = predict_toxicity(text_input, selected_language) |
| |
| |
| post_prediction_resources = update_system_resources() |
| |
| |
| resource_delta = { |
| "cpu_usage": float(post_prediction_resources["cpu"]["usage"].rstrip("%")) - float(pre_prediction_resources["cpu"]["usage"].rstrip("%")), |
| "ram_usage": float(post_prediction_resources["ram"]["percent"].rstrip("%")) - float(pre_prediction_resources["ram"]["percent"].rstrip("%")) |
| } |
| |
| |
| if DEVICE == "cuda": |
| new_memory_info = update_gpu_info() |
| |
| |
| |
| |
| |
| st.session_state['is_analysis_complete'] = True |
| st.session_state['analysis_has_error'] = "error" in prediction and prediction["error"] |
| |
| if "error" in prediction and prediction["error"]: |
| st.error(prediction["error"]) |
| elif prediction["results"]: |
| |
| |
| |
| results = prediction["results"] |
| performance = prediction.get("performance", {}) |
| |
| |
| is_toxic = results["is_toxic"] |
| result_color = THEME["toxic"] if is_toxic else THEME["non_toxic"] |
| result_text = "TOXIC" if is_toxic else "NON-TOXIC" |
| |
| |
| lang_code = prediction["lang_code"] |
| lang_info = SUPPORTED_LANGUAGES.get(lang_code, {"name": lang_code, "flag": "🌐"}) |
| |
| |
| toxic_count = len(results["toxic_categories"]) if is_toxic else 0 |
| |
| |
| categories = [] |
| probabilities = [] |
| statuses = [] |
| |
| |
| category_thresholds = { |
| 'toxic': 0.60, |
| 'severe_toxic': 0.54, |
| 'obscene': 0.60, |
| 'threat': 0.48, |
| 'insult': 0.60, |
| 'identity_hate': 0.50 |
| } |
| |
| for label, prob in results["probabilities"].items(): |
| categories.append(label.replace('_', ' ').title()) |
| probabilities.append(round(prob * 100, 1)) |
| threshold = category_thresholds.get(label, 0.5) * 100 |
| statuses.append("DETECTED" if prob * 100 >= threshold else "Not Detected") |
| |
| |
| chart_data = sorted(zip(categories, probabilities, statuses), key=lambda x: x[1], reverse=True) |
| chart_cats, chart_probs, chart_statuses = zip(*chart_data) |
| |
| |
| col1, col2 = st.columns([3, 2]) |
| |
| with col1: |
| |
| with stylable_container( |
| key="result_card", |
| css_styles=f""" |
| {{ |
| border-radius: 10px; |
| padding: 10px 15px; |
| background-color: {THEME["card_bg"]}; |
| border-left: 5px solid {result_color}; |
| margin-bottom: 10px; |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); |
| overflow: hidden; |
| }} |
| """ |
| ): |
| |
| st.markdown(f""" |
| <div style="display: flex; align-items: center; margin-bottom: 5px;"> |
| <h3 style="margin: 0; margin-right: 10px;">Analysis Result:</h3> |
| <span style='background-color: {hex_to_rgba(result_color, 0.13)}; color: {result_color}; font-family: "Space Grotesk", sans-serif; font-size: 1.1rem; font-weight: 700; padding: 2px 10px; border-radius: 6px;'>{result_text}</span> |
| </div> |
| <div style="margin: 5px 0; font-size: 0.95rem;"> |
| <b>Language:</b> {lang_info['flag']} {lang_info['name']} {'(detected)' if prediction["detected"] else ''} |
| </div> |
| <div style="margin: 5px 0 12px 0; font-size: 0.95rem;"> |
| <b>Toxic Categories:</b> {", ".join([f'<span class="toxic-category" style="padding: 2px 6px; font-size: 0.8rem; display: inline-block;">{category.replace("_", " ").title()}</span>' for category in results["toxic_categories"]]) if is_toxic and toxic_count > 0 else '<span style="color: #666; font-size: 0.9rem;">None</span>'} |
| </div> |
| """, unsafe_allow_html=True) |
| |
| |
| st.markdown("<h4 style='overflow: hidden; margin-top: 4px; margin-bottom: 4px;'>Toxicity Probabilities:</h4>", unsafe_allow_html=True) |
| |
| |
| fig = go.Figure() |
| |
| |
| for i, (cat, prob, status) in enumerate(zip(chart_cats, chart_probs, chart_statuses)): |
| color = THEME["toxic"] if status == "DETECTED" else THEME["non_toxic"] |
| border_color = hex_to_rgba(color, 0.85) |
| |
| fig.add_trace(go.Bar( |
| y=[cat], |
| x=[prob], |
| orientation='h', |
| name=cat, |
| marker=dict( |
| color=color, |
| line=dict( |
| color=border_color, |
| width=2 |
| ) |
| ), |
| text=[f"{prob}%"], |
| textposition='outside', |
| textfont=dict(size=16, weight='bold'), |
| hoverinfo='text', |
| hovertext=[f"{cat}: {prob}%"] |
| )) |
| |
| |
| fig.update_layout( |
| title=None, |
| xaxis_title="Probability (%)", |
| yaxis_title=None, |
| height=340, |
| margin=dict(l=10, r=40, t=20, b=40), |
| xaxis=dict( |
| range=[0, 115], |
| gridcolor=hex_to_rgba(THEME["text"], 0.15), |
| zerolinecolor=hex_to_rgba(THEME["text"], 0.3), |
| color=THEME["text"], |
| tickfont=dict(size=15), |
| title_font=dict(size=16, family="Space Grotesk, sans-serif") |
| ), |
| yaxis=dict( |
| gridcolor=hex_to_rgba(THEME["text"], 0.15), |
| color=THEME["text"], |
| tickfont=dict(size=15, family="Space Grotesk, sans-serif", weight='bold'), |
| automargin=True |
| ), |
| bargap=0.3, |
| paper_bgcolor='rgba(0,0,0,0)', |
| plot_bgcolor='rgba(0,0,0,0)', |
| font=dict( |
| family="Space Grotesk, sans-serif", |
| color=THEME["text"], |
| size=15 |
| ), |
| showlegend=False |
| ) |
| |
| |
| fig.update_xaxes( |
| showgrid=True, |
| gridwidth=1.5, |
| gridcolor=hex_to_rgba(THEME["text"], 0.15), |
| dtick=20 |
| ) |
| |
| |
| st.plotly_chart(fig, use_container_width=True, config={ |
| 'displayModeBar': False, |
| 'displaylogo': False |
| }) |
| |
| with col2: |
| |
| if performance: |
| with stylable_container( |
| key="performance_metrics_card", |
| css_styles=f""" |
| {{ |
| border-radius: 10px; |
| padding: 20px; |
| background-color: {THEME["card_bg"]}; |
| border-left: 3px solid {THEME["primary"]}; |
| height: 100%; |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); |
| }} |
| """ |
| ): |
| st.markdown("<h3 style='margin-top: 0;'>Performance Metrics</h3>", unsafe_allow_html=True) |
| total_time = performance.get("total_time", 0) |
| inference_time = performance.get("model_inference_time", 0) |
| lang_detection_time = performance.get("lang_detection_time", 0) |
| |
| |
| perf_tab1, perf_tab2 = st.tabs(["Time Metrics", "Resource Usage"]) |
| |
| with perf_tab1: |
| time_cols = st.columns(1) |
| with time_cols[0]: |
| |
| total_time_val = f"{total_time:.3f}s" |
| inference_time_val = f"{inference_time:.3f}s" |
| lang_detection_time_val = f"{lang_detection_time:.3f}s" |
| |
| st.markdown(f""" |
| <div style="background-color: white; border-left: 3px solid {THEME["primary"]}; border: 1px solid {hex_to_rgba(THEME["primary"], 0.2)}; border-radius: 10px; padding: 10px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);"> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 500; font-size: 0.85rem; margin-bottom: 3px;"> |
| Total Time |
| </div> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 600; font-size: 1.2rem;"> |
| {total_time_val} |
| </div> |
| </div> |
| |
| <div style="background-color: white; border-left: 3px solid {THEME["primary"]}; border: 1px solid {hex_to_rgba(THEME["primary"], 0.2)}; border-radius: 10px; padding: 10px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);"> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 500; font-size: 0.85rem; margin-bottom: 3px;"> |
| Model Inference |
| </div> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 600; font-size: 1.2rem;"> |
| {inference_time_val} |
| </div> |
| </div> |
| |
| <div style="background-color: white; border-left: 3px solid {THEME["primary"]}; border: 1px solid {hex_to_rgba(THEME["primary"], 0.2)}; border-radius: 10px; padding: 10px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);"> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 500; font-size: 0.85rem; margin-bottom: 3px;"> |
| Language Detection |
| </div> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 600; font-size: 1.2rem;"> |
| {lang_detection_time_val} |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
| |
| with perf_tab2: |
| |
| current_sys_info = update_system_resources() |
| |
| |
| cpu_usage = current_sys_info["cpu"]["usage"] |
| cpu_delta = f"{resource_delta['cpu_usage']:+.1f}%" if abs(resource_delta['cpu_usage']) > 0.1 else None |
| cpu_delta_display = f" ({cpu_delta})" if cpu_delta else "" |
| |
| ram_usage = current_sys_info["ram"]["percent"] |
| ram_delta = f"{resource_delta['ram_usage']:+.1f}%" if abs(resource_delta['ram_usage']) > 0.1 else None |
| ram_delta_display = f" ({ram_delta})" if ram_delta else "" |
| |
| if DEVICE == "cuda": |
| gpu_memory = update_gpu_info() |
| memory_display = f"GPU Memory: {gpu_memory}" |
| else: |
| memory_display = f"System RAM: {current_sys_info['ram']['used']} / {current_sys_info['ram']['total']}" |
| |
| st.markdown(f""" |
| <div style="background-color: white; border-left: 3px solid {THEME["primary"]}; border: 1px solid {hex_to_rgba(THEME["primary"], 0.2)}; border-radius: 10px; padding: 10px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);"> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 500; font-size: 0.85rem; margin-bottom: 3px;"> |
| CPU Usage |
| </div> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 600; font-size: 1.2rem;"> |
| {cpu_usage}<span style="font-size: 0.9rem; color: {THEME["primary"]};">{cpu_delta_display}</span> |
| </div> |
| </div> |
| |
| <div style="background-color: white; border-left: 3px solid {THEME["primary"]}; border: 1px solid {hex_to_rgba(THEME["primary"], 0.2)}; border-radius: 10px; padding: 10px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);"> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 500; font-size: 0.85rem; margin-bottom: 3px;"> |
| RAM Usage |
| </div> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 600; font-size: 1.2rem;"> |
| {ram_usage}<span style="font-size: 0.9rem; color: {THEME["primary"]};">{ram_delta_display}</span> |
| </div> |
| </div> |
| |
| <div style="background-color: white; border-left: 3px solid {THEME["primary"]}; border: 1px solid {hex_to_rgba(THEME["primary"], 0.2)}; border-radius: 10px; padding: 10px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);"> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 500; font-size: 0.85rem; margin-bottom: 3px;"> |
| Memory |
| </div> |
| <div style="color: {THEME["text"]}; font-family: 'Space Grotesk', sans-serif; font-weight: 600; font-size: 1.2rem;"> |
| {memory_display} |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
| else: |
| pass |
|
|
| |
| st.divider() |
| colored_header( |
| label="How to use this AI Model", |
| description="Follow these steps to analyze text for toxicity", |
| color_name="blue-70" |
| ) |
|
|
| |
| st.markdown(""" |
| <div class='usage-step'> |
| <div class='step-number'>1</div> |
| <div>Enter text in the input box above. You can type directly or paste from another source.</div> |
| </div> |
| |
| <div class='usage-step'> |
| <div class='step-number'>2</div> |
| <div>Select a specific language from the sidebar or use the auto-detect feature if you're unsure.</div> |
| </div> |
| |
| <div class='usage-step'> |
| <div class='step-number'>3</div> |
| <div>Click "Analyze Text" to get detailed toxicity analysis results.</div> |
| </div> |
| |
| <div class='usage-step'> |
| <div class='step-number'>4</div> |
| <div>Examine the breakdown of toxicity categories, probabilities, and visualization.</div> |
| </div> |
| |
| <div class='usage-step'> |
| <div class='step-number'>5</div> |
| <div>Try different examples from the sidebar to see how the model performs with various languages.</div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| st.markdown(""" |
| <div class='footer'> |
| <div>Powered by XLM-RoBERTa | Streamlit UI</div> |
| <div style='font-size: 0.9rem; margin-top: 5px;'>Made with ❤️ by Deeptanshu, Nauman, Sara and Soham</div> |
| </div> |
| """, unsafe_allow_html=True) |