entropy25's picture
Update app.py
7f90963 verified
import torch
import gradio as gr
from transformers import BertTokenizer, BertForSequenceClassification
import matplotlib.pyplot as plt
import numpy as np
from wordcloud import WordCloud
from collections import Counter, defaultdict
import re
import json
import csv
import io
import tempfile
from datetime import datetime
import logging
from functools import lru_cache, wraps
from dataclasses import dataclass
from typing import List, Dict, Optional, Tuple, Any, Callable
from contextlib import contextmanager
import gc
import pandas as pd
from lime.lime_text import LimeTextExplainer
import shap
import base64
@dataclass
class _C7x9:
_m1: int = 1000
_b2: int = 50
_t3: int = 512
_w4: int = 2
_c5: int = 128
_p6: int = 8
_fs1: Tuple[int, int] = (8, 5)
_fs2: Tuple[int, int] = (12, 8)
_ws: Tuple[int, int] = (10, 5)
_th = {
'default': {'pos': '#4ecdc4', 'neg': '#ff6b6b'},
'ocean': {'pos': '#0077be', 'neg': '#ff6b35'},
'forest': {'pos': '#228b22', 'neg': '#dc143c'},
'sunset': {'pos': '#ff8c00', 'neg': '#8b0000'}
}
_sw = {
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to',
'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be',
'been', 'have', 'has', 'had', 'will', 'would', 'could', 'should'
}
_cfg = _C7x9()
_log = logging.getLogger(__name__)
def _err_wrap(_def_ret=None):
def _dec(_func: Callable) -> Callable:
@wraps(_func)
def _wrap(*args, **kwargs):
try:
return _func(*args, **kwargs)
except Exception as e:
_log.error(f"{_func.__name__} failed: {e}")
return _def_ret if _def_ret is not None else f"Error: {str(e)}"
return _wrap
return _dec
@contextmanager
def _fig_mgr(*args, **kwargs):
_f = plt.figure(*args, **kwargs)
try:
yield _f
finally:
plt.close(_f)
gc.collect()
class _Th7:
def __init__(self, _t: str = 'default'):
self._t = _t
self._c = _cfg._th.get(_t, _cfg._th['default'])
class _MM9:
_inst = None
_mdl = None
_tok = None
_dev = None
def __new__(cls):
if cls._inst is None:
cls._inst = super().__new__(cls)
return cls._inst
@property
def _m(self):
if self._mdl is None:
self._load()
return self._mdl
@property
def _t(self):
if self._tok is None:
self._load()
return self._tok
@property
def _d(self):
if self._dev is None:
self._dev = torch.device("cuda" if torch.cuda.is_available() else "cpu")
return self._dev
def _load(self):
try:
self._dev = torch.device("cuda" if torch.cuda.is_available() else "cpu")
_mp = base64.b64decode("ZW50cm9weTI1L3NlbnRpbWVudGFuYWx5c2lz").decode()
self._tok = BertTokenizer.from_pretrained(_mp)
self._mdl = BertForSequenceClassification.from_pretrained(_mp)
self._mdl.to(self._dev)
_log.info(f"Model loaded on {self._dev}")
except Exception as e:
_log.error(f"Model loading failed: {e}")
raise
class _TP8:
@staticmethod
@lru_cache(maxsize=_cfg._c5)
def _clean(_txt: str) -> Tuple[str, ...]:
_w = re.findall(r'\b\w{3,}\b', _txt.lower())
return tuple(w for w in _w if w not in _cfg._sw)
class _HM2:
def __init__(self):
self._h = []
def _add(self, _e: Dict):
self._h.append({**_e, 'timestamp': datetime.now().isoformat()})
if len(self._h) > _cfg._m1:
self._h = self._h[-_cfg._m1:]
def _get(self) -> List[Dict]:
return self._h.copy()
def _clr(self) -> int:
_cnt = len(self._h)
self._h.clear()
return _cnt
def _sz(self) -> int:
return len(self._h)
class _SE3:
def __init__(self):
self._mm = _MM9()
self._le = LimeTextExplainer(class_names=['Negative', 'Positive'])
self._se = None
def _pred(self, _txts):
if isinstance(_txts, str):
_txts = [_txts]
_inp = self._mm._t(
_txts, return_tensors="pt", padding=True,
truncation=True, max_length=_cfg._t3
).to(self._mm._d)
with torch.no_grad():
_out = self._mm._m(**_inp)
_probs = torch.nn.functional.softmax(_out.logits, dim=-1).cpu().numpy()
return _probs
@_err_wrap({'sentiment': 'Unknown', 'confidence': 0.0})
def _fast(self, _txt: str) -> Dict:
if not _txt.strip():
raise ValueError("Empty text")
_probs = self._pred([_txt])[0]
_sent = "Positive" if _probs[1] > _probs[0] else "Negative"
return {
'sentiment': _sent,
'confidence': float(_probs.max()),
'pos_prob': float(_probs[1]),
'neg_prob': float(_probs[0])
}
def _lime_kw(self, _txt: str, _k: int = 10) -> List[Tuple[str, float]]:
try:
_exp = self._le.explain_instance(
_txt, self._pred, num_features=_k, num_samples=200
)
_ws = []
for _w, _s in _exp.as_list():
if len(_w.strip()) >= _cfg._w4:
_ws.append((_w.strip().lower(), abs(_s)))
_ws.sort(key=lambda x: x[1], reverse=True)
return _ws[:_k]
except Exception as e:
_log.error(f"LIME extraction failed: {e}")
return []
def _shap_kw(self, _txt: str, _k: int = 10) -> List[Tuple[str, float]]:
try:
_words = _txt.split()
_ws = []
_base = self._pred([_txt])[0][1]
for i, _w in enumerate(_words):
_mod_w = _words[:i] + _words[i+1:]
_mod_t = ' '.join(_mod_w)
if _mod_t.strip():
_mod_p = self._pred([_mod_t])[0][1]
_imp = abs(_base - _mod_p)
_clean_w = re.sub(r'[^\w]', '', _w.lower())
if len(_clean_w) >= _cfg._w4:
_ws.append((_clean_w, _imp))
_uniq = {}
for _w, _s in _ws:
if _w in _uniq:
_uniq[_w] = max(_uniq[_w], _s)
else:
_uniq[_w] = _s
_sorted = sorted(_uniq.items(), key=lambda x: x[1], reverse=True)
return _sorted[:_k]
except Exception as e:
_log.error(f"SHAP extraction failed: {e}")
return []
def _heatmap(self, _txt: str, _ws: Dict[str, float]) -> str:
_words = _txt.split()
_html = ['<div style="font-family: Arial; font-size: 16px; line-height: 1.6;">']
if _ws:
_max = max(abs(_s) for _s in _ws.values())
_min = min(_ws.values())
else:
_max = _min = 0
for _w in _words:
_clean = re.sub(r'[^\w]', '', _w.lower())
_score = _ws.get(_clean, 0)
if _score > 0:
_int = min(255, int(180 * (_score / _max) if _max > 0 else 0))
_color = f"rgba(0, {_int}, 0, 0.3)"
elif _score < 0:
_int = min(255, int(180 * (abs(_score) / abs(_min)) if _min < 0 else 0))
_color = f"rgba({_int}, 0, 0, 0.3)"
else:
_color = "transparent"
_html.append(
f'<span style="background-color: {_color}; padding: 2px; margin: 1px; '
f'border-radius: 3px;" title="Score: {_score:.3f}">{_w}</span> '
)
_html.append('</div>')
return ''.join(_html)
@_err_wrap({'sentiment': 'Unknown', 'confidence': 0.0, 'lime_words': [], 'shap_words': [], 'heatmap_html': ''})
def _adv(self, _txt: str) -> Dict:
if not _txt.strip():
raise ValueError("Empty text")
_probs = self._pred([_txt])[0]
_sent = "Positive" if _probs[1] > _probs[0] else "Negative"
_lime = self._lime_kw(_txt)
_shap = self._shap_kw(_txt)
_ws_dict = dict(_lime)
_heat = self._heatmap(_txt, _ws_dict)
return {
'sentiment': _sent,
'confidence': float(_probs.max()),
'pos_prob': float(_probs[1]),
'neg_prob': float(_probs[0]),
'lime_words': _lime,
'shap_words': _shap,
'heatmap_html': _heat
}
@_err_wrap([])
def _batch(self, _txts: List[str], _prog=None) -> List[Dict]:
if len(_txts) > _cfg._b2:
_txts = _txts[:_cfg._b2]
_res = []
_bs = _cfg._p6
for i in range(0, len(_txts), _bs):
_b = _txts[i:i+_bs]
if _prog:
_prog((i + len(_b)) / len(_txts))
_inp = self._mm._t(
_b, return_tensors="pt", padding=True,
truncation=True, max_length=_cfg._t3
).to(self._mm._d)
with torch.no_grad():
_out = self._mm._m(**_inp)
_probs = torch.nn.functional.softmax(_out.logits, dim=-1).cpu().numpy()
for _txt, _prob in zip(_b, _probs):
_sent = "Positive" if _prob[1] > _prob[0] else "Negative"
_res.append({
'text': _txt[:50] + '...' if len(_txt) > 50 else _txt,
'full_text': _txt,
'sentiment': _sent,
'confidence': float(_prob.max()),
'pos_prob': float(_prob[1]),
'neg_prob': float(_prob[0])
})
return _res
class _PF4:
@staticmethod
@_err_wrap(None)
def _bars(_probs: np.ndarray, _th: _Th7) -> plt.Figure:
with _fig_mgr(figsize=_cfg._fs1) as _f:
_ax = _f.add_subplot(111)
_lbl = ["Negative", "Positive"]
_clr = [_th._c['neg'], _th._c['pos']]
_b = _ax.bar(_lbl, _probs, color=_clr, alpha=0.8)
_ax.set_title("Sentiment Probabilities", fontweight='bold')
_ax.set_ylabel("Probability")
_ax.set_ylim(0, 1)
for _bar, _prob in zip(_b, _probs):
_ax.text(_bar.get_x() + _bar.get_width()/2., _bar.get_height() + 0.02,
f'{_prob:.3f}', ha='center', va='bottom', fontweight='bold')
_f.tight_layout()
return _f
@staticmethod
@_err_wrap(None)
def _gauge(_conf: float, _sent: str, _th: _Th7) -> plt.Figure:
with _fig_mgr(figsize=_cfg._fs1) as _f:
_ax = _f.add_subplot(111)
_theta = np.linspace(0, np.pi, 100)
_clr = [_th._c['neg'] if i < 50 else _th._c['pos'] for i in range(100)]
for i in range(len(_theta)-1):
_ax.fill_between([_theta[i], _theta[i+1]], [0, 0], [0.8, 0.8],
color=_clr[i], alpha=0.7)
_pos = np.pi * (0.5 + (0.4 if _sent == 'Positive' else -0.4) * _conf)
_ax.plot([_pos, _pos], [0, 0.6], 'k-', linewidth=6)
_ax.plot(_pos, 0.6, 'ko', markersize=10)
_ax.set_xlim(0, np.pi)
_ax.set_ylim(0, 1)
_ax.set_title(f'{_sent} - Confidence: {_conf:.3f}', fontweight='bold')
_ax.set_xticks([0, np.pi/2, np.pi])
_ax.set_xticklabels(['Negative', 'Neutral', 'Positive'])
_ax.axis('off')
_f.tight_layout()
return _f
@staticmethod
@_err_wrap(None)
def _lime_chart(_lw: List[Tuple[str, float]], _sent: str, _th: _Th7) -> Optional[plt.Figure]:
if not _lw:
return None
with _fig_mgr(figsize=_cfg._fs1) as _f:
_ax = _f.add_subplot(111)
_w = [_word for _word, _score in _lw]
_s = [_score for _word, _score in _lw]
_clr = _th._c['pos'] if _sent == 'Positive' else _th._c['neg']
_b = _ax.barh(range(len(_w)), _s, color=_clr, alpha=0.7)
_ax.set_yticks(range(len(_w)))
_ax.set_yticklabels(_w)
_ax.set_xlabel('LIME Attention Weight')
_ax.set_title(f'LIME: Top Contributing Words ({_sent})', fontweight='bold')
for i, (_bar, _score) in enumerate(zip(_b, _s)):
_ax.text(_bar.get_width() + 0.001, _bar.get_y() + _bar.get_height()/2.,
f'{_score:.3f}', ha='left', va='center', fontsize=9)
_ax.invert_yaxis()
_ax.grid(axis='x', alpha=0.3)
_f.tight_layout()
return _f
@staticmethod
@_err_wrap(None)
def _shap_chart(_sw: List[Tuple[str, float]], _sent: str, _th: _Th7) -> Optional[plt.Figure]:
if not _sw:
return None
with _fig_mgr(figsize=_cfg._fs1) as _f:
_ax = _f.add_subplot(111)
_w = [_word for _word, _score in _sw]
_s = [_score for _word, _score in _sw]
_clr = _th._c['pos'] if _sent == 'Positive' else _th._c['neg']
_b = _ax.barh(range(len(_w)), _s, color=_clr, alpha=0.7)
_ax.set_yticks(range(len(_w)))
_ax.set_yticklabels(_w)
_ax.set_xlabel('SHAP Value')
_ax.set_title(f'SHAP: Top Contributing Words ({_sent})', fontweight='bold')
for i, (_bar, _score) in enumerate(zip(_b, _s)):
_ax.text(_bar.get_width() + 0.001, _bar.get_y() + _bar.get_height()/2.,
f'{_score:.3f}', ha='left', va='center', fontsize=9)
_ax.invert_yaxis()
_ax.grid(axis='x', alpha=0.3)
_f.tight_layout()
return _f
@staticmethod
@_err_wrap(None)
def _cloud(_txt: str, _sent: str, _th: _Th7) -> Optional[plt.Figure]:
if len(_txt.split()) < 3:
return None
_cm = 'Greens' if _sent == 'Positive' else 'Reds'
_wc = WordCloud(width=800, height=400, background_color='white',
colormap=_cm, max_words=30).generate(_txt)
with _fig_mgr(figsize=_cfg._ws) as _f:
_ax = _f.add_subplot(111)
_ax.imshow(_wc, interpolation='bilinear')
_ax.axis('off')
_ax.set_title(f'{_sent} Word Cloud', fontweight='bold')
_f.tight_layout()
return _f
@staticmethod
@_err_wrap(None)
def _batch_viz(_res: List[Dict], _th: _Th7) -> plt.Figure:
with _fig_mgr(figsize=_cfg._fs2) as _f:
_gs = _f.add_gridspec(2, 2, hspace=0.3, wspace=0.3)
_ax1 = _f.add_subplot(_gs[0, 0])
_sc = Counter([_r['sentiment'] for _r in _res])
_clr = [_th._c['pos'], _th._c['neg']]
_ax1.pie(_sc.values(), labels=_sc.keys(),
autopct='%1.1f%%', colors=_clr[:len(_sc)])
_ax1.set_title('Sentiment Distribution')
_ax2 = _f.add_subplot(_gs[0, 1])
_confs = [_r['confidence'] for _r in _res]
_ax2.hist(_confs, bins=8, alpha=0.7, color='skyblue', edgecolor='black')
_ax2.set_title('Confidence Distribution')
_ax2.set_xlabel('Confidence')
_ax3 = _f.add_subplot(_gs[1, :])
_pp = [_r['pos_prob'] for _r in _res]
_idx = range(len(_res))
_cs = [_th._c['pos'] if _r['sentiment'] == 'Positive'
else _th._c['neg'] for _r in _res]
_ax3.scatter(_idx, _pp, c=_cs, alpha=0.7, s=60)
_ax3.axhline(y=0.5, color='gray', linestyle='--', alpha=0.5)
_ax3.set_title('Sentiment Progression')
_ax3.set_xlabel('Review Index')
_ax3.set_ylabel('Positive Probability')
return _f
class _DH5:
@staticmethod
@_err_wrap((None, "Export failed"))
def _exp(_data: List[Dict], _fmt: str) -> Tuple[Optional[str], str]:
if not _data:
return None, "No data to export"
_tf = tempfile.NamedTemporaryFile(mode='w', delete=False,
suffix=f'.{_fmt}', encoding='utf-8')
if _fmt == 'csv':
_w = csv.writer(_tf)
_w.writerow(['Timestamp', 'Text', 'Sentiment', 'Confidence', 'Pos_Prob', 'Neg_Prob'])
for _e in _data:
_w.writerow([
_e.get('timestamp', ''),
_e.get('text', ''),
_e.get('sentiment', ''),
f"{_e.get('confidence', 0):.4f}",
f"{_e.get('pos_prob', 0):.4f}",
f"{_e.get('neg_prob', 0):.4f}"
])
elif _fmt == 'json':
json.dump(_data, _tf, indent=2, ensure_ascii=False)
_tf.close()
return _tf.name, f"Exported {len(_data)} entries"
@staticmethod
@_err_wrap("")
def _proc(_file) -> str:
if not _file:
return ""
try:
_fp = _file.name
if _fp.endswith('.csv'):
for _enc in ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']:
try:
_df = pd.read_csv(_fp, encoding=_enc)
_tc = []
for _col in _df.columns:
_sv = _df[_col].dropna().head(10)
if len(_sv) > 0:
_cnt = sum(1 for _val in _sv
if isinstance(_val, str) and len(str(_val).strip()) > 10)
if _cnt > len(_sv) * 0.7:
_tc.append(_col)
if _tc:
_sc = _tc[0]
else:
_sc = _df.columns[0]
_rev = _df[_sc].dropna().astype(str).tolist()
_cr = []
for _r in _rev:
_r = _r.strip()
if len(_r) > 10 and _r.lower() != 'nan':
_cr.append(_r)
if _cr:
_log.info(f"Successfully read {len(_cr)} reviews from CSV")
return '\n'.join(_cr)
except Exception as e:
continue
return "Error: Could not read CSV file. Please check the file format and encoding."
else:
for _enc in ['utf-8', 'latin-1', 'cp1252']:
try:
with open(_fp, 'r', encoding=_enc) as _f:
_cont = _f.read().strip()
if _cont:
return _cont
except Exception as e:
continue
return "Error: Could not read text file. Please check the file encoding."
except Exception as e:
_log.error(f"File processing error: {e}")
return f"Error processing file: {str(e)}"
class _SA6:
def __init__(self):
self._eng = _SE3()
self._hist = _HM2()
self._dh = _DH5()
self._ex = [
["While the film's visual effects were undeniably impressive, the story lacked emotional weight, and the pacing felt inconsistent throughout."],
["An extraordinary achievement in filmmaking — the direction was masterful, the script was sharp, and every performance added depth and realism."],
["Despite a promising start, the film quickly devolved into a series of clichés, with weak character development and an ending that felt rushed and unearned."],
["A beautifully crafted story with heartfelt moments and a soundtrack that perfectly captured the emotional tone of each scene."],
["The movie was far too long, with unnecessary subplots and dull dialogue that made it difficult to stay engaged until the end."]
]
@_err_wrap(("Please enter text", None, None, None))
def _fast_ana(self, _txt: str, _th: str = 'default'):
if not _txt.strip():
return "Please enter text", None, None, None
_res = self._eng._fast(_txt)
self._hist._add({
'text': _txt[:100],
'full_text': _txt,
**_res
})
_thc = _Th7(_th)
_probs = np.array([_res['neg_prob'], _res['pos_prob']])
_pp = _PF4._bars(_probs, _thc)
_gp = _PF4._gauge(_res['confidence'], _res['sentiment'], _thc)
_cp = _PF4._cloud(_txt, _res['sentiment'], _thc)
_rt = f"Sentiment: {_res['sentiment']} (Confidence: {_res['confidence']:.3f})"
return _rt, _pp, _gp, _cp
@_err_wrap(("Please enter text", None, None, None))
def _adv_ana(self, _txt: str, _th: str = 'default'):
if not _txt.strip():
return "Please enter text", None, None, None
_res = self._eng._adv(_txt)
self._hist._add({
'text': _txt[:100],
'full_text': _txt,
**_res
})
_thc = _Th7(_th)
_lp = _PF4._lime_chart(_res['lime_words'], _res['sentiment'], _thc)
_sp = _PF4._shap_chart(_res['shap_words'], _res['sentiment'], _thc)
_lws = ", ".join([f"{_w}({_s:.3f})" for _w, _s in _res['lime_words'][:5]])
_sws = ", ".join([f"{_w}({_s:.3f})" for _w, _s in _res['shap_words'][:5]])
_rt = (f"Sentiment: {_res['sentiment']} (Confidence: {_res['confidence']:.3f})\n"
f"LIME Key Words: {_lws}\n"
f"SHAP Key Words: {_sws}")
return _rt, _lp, _sp, _res['heatmap_html']
@_err_wrap(None)
def _batch_ana(self, _revs: str, _prog=None):
if not _revs.strip():
return None
_txts = [_r.strip() for _r in _revs.split('\n') if _r.strip()]
if len(_txts) < 2:
return None
_res = self._eng._batch(_txts, _prog)
for _r in _res:
self._hist._add(_r)
_thc = _Th7('default')
return _PF4._batch_viz(_res, _thc)
@_err_wrap((None, "No history available"))
def _hist_plot(self, _th: str = 'default'):
_hist = self._hist._get()
if len(_hist) < 2:
return None, f"Need at least 2 analyses for trends. Current: {len(_hist)}"
_thc = _Th7(_th)
with _fig_mgr(figsize=(12, 8)) as _f:
_gs = _f.add_gridspec(2, 1, hspace=0.3)
_idx = list(range(len(_hist)))
_pp = [_item['pos_prob'] for _item in _hist]
_confs = [_item['confidence'] for _item in _hist]
_ax1 = _f.add_subplot(_gs[0, 0])
_clr = [_thc._c['pos'] if _p > 0.5 else _thc._c['neg']
for _p in _pp]
_ax1.scatter(_idx, _pp, c=_clr, alpha=0.7, s=60)
_ax1.plot(_idx, _pp, alpha=0.5, linewidth=2)
_ax1.axhline(y=0.5, color='gray', linestyle='--', alpha=0.5)
_ax1.set_title('Sentiment History')
_ax1.set_ylabel('Positive Probability')
_ax1.grid(True, alpha=0.3)
_ax2 = _f.add_subplot(_gs[1, 0])
_ax2.bar(_idx, _confs, alpha=0.7, color='lightblue', edgecolor='navy')
_ax2.set_title('Confidence Over Time')
_ax2.set_xlabel('Analysis Number')
_ax2.set_ylabel('Confidence')
_ax2.grid(True, alpha=0.3)
_f.tight_layout()
return _f, f"History: {len(_hist)} analyses"
def _create_ui():
_app = _SA6()
with gr.Blocks(theme=gr.themes.Soft(), title="Movie Sentiment Analyzer") as _demo:
gr.Markdown("# 🎬 AI Movie Sentiment Analyzer")
gr.Markdown("Fast sentiment analysis with advanced deep learning explanations")
with gr.Tab("Quick Analysis"):
with gr.Row():
with gr.Column():
_ti = gr.Textbox(
label="Movie Review",
placeholder="Enter your movie review...",
lines=5
)
with gr.Row():
_ab = gr.Button("Analyze", variant="primary")
_ts = gr.Dropdown(
choices=list(_cfg._th.keys()),
value="default",
label="Theme"
)
gr.Examples(
examples=_app._ex,
inputs=_ti
)
with gr.Column():
_ro = gr.Textbox(label="Result", lines=3)
with gr.Row():
_pp = gr.Plot(label="Probabilities")
_gp = gr.Plot(label="Confidence")
with gr.Row():
_wp = gr.Plot(label="Word Cloud")
with gr.Tab("Advanced Analysis"):
with gr.Row():
with gr.Column():
_ati = gr.Textbox(
label="Movie Review",
placeholder="Enter your movie review for deep analysis...",
lines=5
)
with gr.Row():
_aab = gr.Button("Deep Analyze", variant="primary")
_ats = gr.Dropdown(
choices=list(_cfg._th.keys()),
value="default",
label="Theme"
)
gr.Examples(
examples=_app._ex,
inputs=_ati
)
with gr.Column():
_aro = gr.Textbox(label="Analysis Result", lines=4)
with gr.Row():
_lp = gr.Plot(label="LIME: Key Contributing Words")
_sp = gr.Plot(label="SHAP: Key Contributing Words")
with gr.Row():
_ho = gr.HTML(label="Word Importance Heatmap (LIME-based)")
with gr.Tab("Batch Analysis"):
with gr.Row():
with gr.Column():
_fu = gr.File(label="Upload File", file_types=[".csv", ".txt"])
_bi = gr.Textbox(
label="Reviews (one per line)",
lines=8
)
with gr.Column():
_lb = gr.Button("Load File")
_bb = gr.Button("Analyze Batch", variant="primary")
_bp = gr.Plot(label="Batch Results")
with gr.Tab("History & Export"):
with gr.Row():
_rb = gr.Button("Refresh")
_cb = gr.Button("Clear", variant="stop")
with gr.Row():
_csvb = gr.Button("Export CSV")
_jb = gr.Button("Export JSON")
_hs = gr.Textbox(label="Status")
_hp = gr.Plot(label="History Trends")
_csvf = gr.File(label="CSV Download", visible=True)
_jf = gr.File(label="JSON Download", visible=True)
# Event handlers
_ab.click(
_app._fast_ana,
inputs=[_ti, _ts],
outputs=[_ro, _pp, _gp, _wp]
)
_aab.click(
_app._adv_ana,
inputs=[_ati, _ats],
outputs=[_aro, _lp, _sp, _ho]
)
_lb.click(_app._dh._proc, inputs=_fu, outputs=_bi)
_bb.click(_app._batch_ana, inputs=_bi, outputs=_bp)
_rb.click(
lambda _th: _app._hist_plot(_th),
inputs=_ts,
outputs=[_hp, _hs]
)
_cb.click(
lambda: f"Cleared {_app._hist._clr()} entries",
outputs=_hs
)
_csvb.click(
lambda: _app._dh._exp(_app._hist._get(), 'csv'),
outputs=[_csvf, _hs]
)
_jb.click(
lambda: _app._dh._exp(_app._hist._get(), 'json'),
outputs=[_jf, _hs]
)
return _demo
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
_demo = _create_ui()
_demo.launch(share=True)