Spaces:
Running
Running
| from __future__ import annotations | |
| import functools | |
| import html | |
| import re | |
| from typing import Optional | |
| import gradio as gr | |
| import torch | |
| from transformers import AutoModel, AutoTokenizer | |
| # ββ constants βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| MODEL_ID = "fromziro/JetonCount" | |
| DEFAULT_VOCAB_SIZE = 32_000 | |
| PUNCTUATION_CHARS = set(r""".,!?;:'"`~@#$%^&*()-_=+[]{}<>/\|""") | |
| SYMBOL_CHARS = set(r"""@#$%^&*()-_=+[]{}<>/\|~`""") | |
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| if DEVICE.type == "cuda": | |
| torch.backends.cudnn.benchmark = True | |
| # ββ model / tokenizer loading βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_model(): | |
| model = AutoModel.from_pretrained(MODEL_ID, trust_remote_code=True) | |
| model.eval() | |
| model.to(DEVICE) | |
| return model | |
| def load_tokenizer(tokenizer_id: str): | |
| return AutoTokenizer.from_pretrained(tokenizer_id, use_fast=True) | |
| def get_vocab_size(tokenizer) -> int: | |
| vs = getattr(tokenizer, "vocab_size", None) | |
| if isinstance(vs, int) and vs > 0: | |
| return vs | |
| try: | |
| return int(len(tokenizer)) | |
| except Exception: | |
| return DEFAULT_VOCAB_SIZE | |
| # ββ feature computation βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def compute_stats(text: str, vocab_size: int) -> dict: | |
| chars = len(text) | |
| words_list = re.findall(r"\b\w+\b", text, flags=re.UNICODE) | |
| words = len(words_list) | |
| avg_cw = (sum(len(w) for w in words_list) / words) if words else 0.0 | |
| longest = max((len(w) for w in words_list), default=0) | |
| if chars: | |
| punct = sum(1 for ch in text if ch in PUNCTUATION_CHARS) / chars | |
| sym = sum(1 for ch in text if ch in SYMBOL_CHARS) / chars | |
| else: | |
| punct = sym = 0.0 | |
| return dict( | |
| chars=float(chars), words=float(words), | |
| avg_chars_per_word=float(avg_cw), punctuation_ratio=float(punct), | |
| symbol_ratio=float(sym), longest_word_chars=float(longest), | |
| vocab_size=float(vocab_size), | |
| ) | |
| # ββ inference βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def predict(stats: dict) -> float: | |
| x = torch.tensor( | |
| [[stats["chars"], stats["words"], stats["avg_chars_per_word"], | |
| stats["punctuation_ratio"], stats["symbol_ratio"], | |
| stats["longest_word_chars"], stats["vocab_size"]]], | |
| dtype=torch.float32, device=DEVICE, | |
| ) | |
| out = load_model()(input_features=x) | |
| return max(0.0, float(out.logits.squeeze().item())) | |
| # ββ event handlers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def on_text_change(text: str): | |
| text = text or "" | |
| chars = len(text) | |
| words = len(re.findall(r"\b\w+\b", text, flags=re.UNICODE)) | |
| return f"<div class='live-counter'><span>{chars:,} chars</span><span class='sep'>Β·</span><span>{words:,} words</span></div>" | |
| def on_tokenizer_change(tokenizer_id: str): | |
| tid = (tokenizer_id or "").strip() | |
| if not tid: | |
| return gr.update(interactive=True), _status(""), None | |
| try: | |
| tok = load_tokenizer(tid) | |
| vs = get_vocab_size(tok) | |
| return ( | |
| gr.update(value=vs, interactive=False), | |
| _status(f"Locked to <b>{html.escape(tid)}</b> β vocab {vs:,}", kind="ok"), | |
| vs, | |
| ) | |
| except Exception as exc: | |
| return ( | |
| gr.update(interactive=True), | |
| _status(f"Could not load <b>{html.escape(tid)}</b>: {html.escape(str(exc)[:120])}", kind="err"), | |
| None, | |
| ) | |
| def on_clear(current_vocab): | |
| return gr.update(value=""), gr.update(interactive=True), None, _status("") | |
| def run(text: str, vocab_size_val, tokenizer_id: str, locked_vocab: Optional[int]): | |
| text = (text or "").strip() | |
| tid = (tokenizer_id or "").strip() | |
| actual_count: Optional[int] = None | |
| tok_error: Optional[str] = None | |
| if tid: | |
| try: | |
| tok = load_tokenizer(tid) | |
| resolved_vocab = locked_vocab if locked_vocab is not None else get_vocab_size(tok) | |
| ids = tok(text, add_special_tokens=False).input_ids | |
| actual_count = len(ids) | |
| except Exception as exc: | |
| tok_error = str(exc) | |
| resolved_vocab = _safe_int(vocab_size_val, DEFAULT_VOCAB_SIZE) | |
| else: | |
| resolved_vocab = _safe_int(vocab_size_val, DEFAULT_VOCAB_SIZE) | |
| stats = compute_stats(text, resolved_vocab) | |
| try: | |
| pred = predict(stats) | |
| except Exception as exc: | |
| return _render_error(str(exc)), None | |
| result_data = dict( | |
| prediction=pred, actual_count=actual_count, | |
| vocab_size=resolved_vocab, tokenizer_id=tid, | |
| stats=stats, tok_error=tok_error, | |
| ) | |
| return _render_results(result_data), result_data | |
| def _safe_int(val, default: int) -> int: | |
| try: | |
| return int(float(val)) | |
| except Exception: | |
| return default | |
| def _status(msg: str, kind: str = "") -> str: | |
| if not msg: | |
| return "" | |
| icon = "β" if kind == "ok" else ("β" if kind == "err" else "βΉ") | |
| cls = f"status-{kind}" if kind else "" | |
| return f"<div class='status-pill {cls}'><span class='status-icon'>{icon}</span>{msg}</div>" | |
| # ββ HTML rendering βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _render_error(msg: str) -> str: | |
| return f""" | |
| <div class="result-wrap"> | |
| <div class="error-banner"> | |
| <span class="err-icon">β </span> | |
| <div> | |
| <div class="err-title">Something went wrong</div> | |
| <div class="err-body">{html.escape(msg)}</div> | |
| </div> | |
| </div> | |
| </div>""" | |
| def _render_results(r: dict) -> str: | |
| pred = r["prediction"] | |
| actual = r["actual_count"] | |
| vocab = r["vocab_size"] | |
| tid_raw = r["tokenizer_id"] | |
| stats = r["stats"] | |
| tok_error = r["tok_error"] | |
| tid = html.escape(tid_raw) if tid_raw else None | |
| pred_int = round(pred) | |
| chars_int = int(stats["chars"]) | |
| words_int = int(stats["words"]) | |
| # ββ comparison section ββ | |
| if actual is not None: | |
| diff = pred_int - actual | |
| abs_diff = abs(diff) | |
| pct = abs_diff / max(actual, 1) * 100 | |
| accuracy = max(0.0, 100.0 - pct) | |
| bar_w = min(100, round(accuracy)) | |
| if abs_diff == 0: | |
| diff_label = "exact match" | |
| diff_cls = "diff-exact" | |
| diff_sign = "" | |
| else: | |
| sign = "+" if diff > 0 else "β" | |
| diff_sign = f"{sign}{abs_diff:,}" | |
| diff_label = f"{pct:.1f}% off" | |
| diff_cls = "diff-over" if diff > 0 else "diff-under" | |
| bar_color = "#4ade80" if accuracy >= 95 else ("#facc15" if accuracy >= 80 else "#f87171") | |
| comparison = f""" | |
| <div class="compare-block"> | |
| <div class="compare-cards"> | |
| <div class="ccard predicted"> | |
| <div class="ccard-label">Predicted</div> | |
| <div class="ccard-num">{pred_int:,}</div> | |
| </div> | |
| <div class="ccard-divider">vs</div> | |
| <div class="ccard actual"> | |
| <div class="ccard-label">Actual</div> | |
| <div class="ccard-num">{actual:,}</div> | |
| </div> | |
| </div> | |
| <div class="accuracy-row"> | |
| <div class="accuracy-bar-bg"> | |
| <div class="accuracy-bar-fill" style="width:{bar_w}%; background:{bar_color};"></div> | |
| </div> | |
| <div class="accuracy-meta"> | |
| <span class="accuracy-pct">{accuracy:.1f}% accuracy</span> | |
| <span class="{diff_cls}">{diff_sign and diff_sign + " Β· "}{diff_label}</span> | |
| </div> | |
| </div> | |
| </div>""" | |
| elif tid_raw and tok_error: | |
| comparison = f""" | |
| <div class="tok-error"> | |
| <span class="tok-err-icon">β </span> | |
| Tokenizer error: {html.escape((tok_error or "")[:120])} | |
| </div>""" | |
| else: | |
| comparison = "" | |
| # ββ feature chips ββ | |
| def chip(label, value): | |
| return f'<div class="chip"><span class="chip-label">{label}</span><span class="chip-val">{value}</span></div>' | |
| chips = "".join([ | |
| chip("chars", f"{chars_int:,}"), | |
| chip("words", f"{words_int:,}"), | |
| chip("avg chars/wd", f"{stats['avg_chars_per_word']:.2f}"), | |
| chip("longest word", f"{int(stats['longest_word_chars'])}"), | |
| chip("punct ratio", f"{stats['punctuation_ratio']:.4f}"), | |
| chip("symbol ratio", f"{stats['symbol_ratio']:.4f}"), | |
| ]) | |
| meta_tok = f'<span class="meta-tag">{tid}</span>' if tid else '<span class="meta-tag muted">no tokenizer</span>' | |
| meta_vocab = f'<span class="meta-tag">{vocab:,} vocab</span>' | |
| device_tag = f'<span class="meta-tag">{DEVICE.type.upper()}</span>' | |
| return f""" | |
| <div class="result-wrap"> | |
| <div class="result-header"> | |
| <div class="result-meta">{meta_tok}{meta_vocab}{device_tag}</div> | |
| </div> | |
| <div class="pred-hero"> | |
| <div class="pred-label">Estimated token count</div> | |
| <div class="pred-number">{pred_int:,}</div> | |
| <div class="pred-raw">{pred:.5f} Β· {chars_int / max(pred_int,1):.2f} chars/token</div> | |
| </div> | |
| {comparison} | |
| <div class="features-section"> | |
| <div class="features-title">Text features</div> | |
| <div class="chips">{chips}</div> | |
| </div> | |
| </div>""" | |
| # ββ CSS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CSS = """ | |
| :root { | |
| --bg: #070707; | |
| --surf: #0f0f0f; | |
| --surf2: #161616; | |
| --border: #222; | |
| --border2: #2e2e2e; | |
| --text: #efefef; | |
| --muted: #777; | |
| --muted2: #555; | |
| --green: #4ade80; | |
| --yellow: #facc15; | |
| --red: #f87171; | |
| --blue: #60a5fa; | |
| --r: 12px; | |
| --r-sm: 8px; | |
| } | |
| *, *::before, *::after { box-sizing: border-box; } | |
| body, .gradio-container { | |
| background: var(--bg) !important; | |
| color: var(--text) !important; | |
| font-family: ui-sans-serif, system-ui, -apple-system, sans-serif !important; | |
| } | |
| .gradio-container { | |
| max-width: 1060px !important; | |
| margin: 0 auto !important; | |
| padding: 24px 20px !important; | |
| } | |
| h1,h2,h3,h4,p,span,label,div,textarea,input,select,button { | |
| color: var(--text) !important; | |
| } | |
| .mono { font-family: ui-monospace, "SF Mono", Menlo, monospace !important; } | |
| /* ββ hero ββ */ | |
| .hero { | |
| padding: 22px 26px 20px; | |
| border: 1px solid var(--border2); | |
| border-radius: 18px; | |
| background: linear-gradient(145deg, rgba(255,255,255,0.038) 0%, rgba(255,255,255,0.008) 100%); | |
| margin-bottom: 22px; | |
| } | |
| .hero-eyebrow { | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 0.12em; | |
| text-transform: uppercase; | |
| color: var(--muted) !important; | |
| margin-bottom: 8px; | |
| } | |
| .hero-title { | |
| font-size: 28px; | |
| font-weight: 800; | |
| letter-spacing: -0.03em; | |
| line-height: 1; | |
| margin-bottom: 10px; | |
| } | |
| .hero-desc { | |
| font-size: 13.5px; | |
| color: var(--muted) !important; | |
| line-height: 1.6; | |
| max-width: 680px; | |
| } | |
| .hero-badges { | |
| display: flex; | |
| gap: 6px; | |
| margin-top: 14px; | |
| flex-wrap: wrap; | |
| } | |
| .badge { | |
| font-size: 11px; | |
| font-weight: 600; | |
| padding: 3px 10px; | |
| border-radius: 99px; | |
| border: 1px solid var(--border2); | |
| color: var(--muted) !important; | |
| background: var(--surf); | |
| letter-spacing: 0.04em; | |
| } | |
| /* ββ gradio internals ββ */ | |
| .block, .block-container, .group, .wrap, .panel, .form { | |
| background: transparent !important; | |
| border: none !important; | |
| box-shadow: none !important; | |
| } | |
| /* ββ inputs ββ */ | |
| textarea, input[type=text], input[type=number], select { | |
| background: var(--surf) !important; | |
| border: 1px solid var(--border2) !important; | |
| border-radius: var(--r) !important; | |
| color: var(--text) !important; | |
| box-shadow: none !important; | |
| transition: border-color 0.15s !important; | |
| } | |
| textarea:focus, input:focus { | |
| border-color: #3a3a3a !important; | |
| outline: none !important; | |
| } | |
| textarea::placeholder, input::placeholder { | |
| color: var(--muted2) !important; | |
| } | |
| .label-wrap label, .svelte-1gfkn6j { | |
| font-size: 12px !important; | |
| font-weight: 600 !important; | |
| letter-spacing: 0.04em !important; | |
| text-transform: uppercase !important; | |
| color: var(--muted) !important; | |
| margin-bottom: 6px !important; | |
| } | |
| /* ββ buttons ββ */ | |
| button { | |
| border-radius: var(--r) !important; | |
| border: 1px solid var(--border2) !important; | |
| font-weight: 600 !important; | |
| transition: opacity 0.15s, transform 0.1s !important; | |
| } | |
| button.primary { | |
| background: #fff !important; | |
| color: #000 !important; | |
| border-color: #fff !important; | |
| letter-spacing: 0.01em !important; | |
| } | |
| button.primary:hover { opacity: 0.88 !important; } | |
| button.primary:active { transform: scale(0.98) !important; } | |
| button.secondary { | |
| background: var(--surf) !important; | |
| color: var(--muted) !important; | |
| } | |
| button.secondary:hover { border-color: #3a3a3a !important; color: var(--text) !important; } | |
| /* ββ live counter ββ */ | |
| .live-counter { | |
| font-size: 12px; | |
| color: var(--muted) !important; | |
| padding: 6px 2px 0; | |
| display: flex; | |
| gap: 0; | |
| align-items: center; | |
| } | |
| .live-counter .sep { margin: 0 8px; opacity: 0.35; } | |
| /* ββ status pill ββ */ | |
| .status-pill { | |
| font-size: 12.5px; | |
| line-height: 1.5; | |
| padding: 8px 12px; | |
| border-radius: var(--r-sm); | |
| border: 1px solid var(--border); | |
| background: var(--surf); | |
| color: var(--muted) !important; | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 8px; | |
| } | |
| .status-pill.status-ok { border-color: rgba(74,222,128,0.25); background: rgba(74,222,128,0.06); } | |
| .status-pill.status-err { border-color: rgba(248,113,113,0.25); background: rgba(248,113,113,0.06); } | |
| .status-icon { opacity: 0.7; flex-shrink: 0; margin-top: 1px; } | |
| .status-pill b { font-weight: 600; color: inherit !important; } | |
| /* ββ accordion ββ */ | |
| details, .accordion { | |
| border: 1px solid var(--border) !important; | |
| border-radius: var(--r) !important; | |
| background: var(--surf) !important; | |
| } | |
| /* βββββββββββββββββββββββββββββββ | |
| RESULT PANEL | |
| βββββββββββββββββββββββββββββββ */ | |
| .result-wrap { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 14px; | |
| } | |
| /* ββ header meta ββ */ | |
| .result-header { display: flex; align-items: center; justify-content: space-between; } | |
| .result-meta { display: flex; gap: 6px; flex-wrap: wrap; } | |
| .meta-tag { | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 0.05em; | |
| padding: 3px 10px; | |
| border-radius: 99px; | |
| border: 1px solid var(--border2); | |
| background: var(--surf2); | |
| color: var(--muted) !important; | |
| } | |
| .meta-tag.muted { opacity: 0.5; } | |
| /* ββ prediction hero ββ */ | |
| .pred-hero { | |
| padding: 28px 26px 24px; | |
| border: 1px solid var(--border2); | |
| border-radius: 16px; | |
| background: linear-gradient(145deg, rgba(255,255,255,0.042) 0%, rgba(255,255,255,0.008) 100%); | |
| text-align: center; | |
| } | |
| .pred-label { | |
| font-size: 11px; | |
| font-weight: 700; | |
| letter-spacing: 0.12em; | |
| text-transform: uppercase; | |
| color: var(--muted) !important; | |
| margin-bottom: 12px; | |
| } | |
| .pred-number { | |
| font-size: 64px; | |
| font-weight: 900; | |
| line-height: 1; | |
| letter-spacing: -0.04em; | |
| font-variant-numeric: tabular-nums; | |
| background: linear-gradient(135deg, #ffffff 0%, rgba(255,255,255,0.6) 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin-bottom: 10px; | |
| } | |
| .pred-raw { | |
| font-size: 12px; | |
| color: var(--muted2) !important; | |
| font-family: ui-monospace, monospace; | |
| letter-spacing: 0.02em; | |
| } | |
| /* ββ comparison ββ */ | |
| .compare-block { | |
| border: 1px solid var(--border2); | |
| border-radius: 16px; | |
| background: var(--surf); | |
| padding: 18px 20px 16px; | |
| } | |
| .compare-cards { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| margin-bottom: 16px; | |
| } | |
| .ccard { | |
| flex: 1; | |
| padding: 14px 16px; | |
| border-radius: var(--r); | |
| border: 1px solid var(--border); | |
| background: var(--surf2); | |
| text-align: center; | |
| } | |
| .ccard.predicted { border-color: rgba(255,255,255,0.1); } | |
| .ccard.actual { border-color: rgba(255,255,255,0.06); } | |
| .ccard-label { | |
| font-size: 10px; | |
| font-weight: 700; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--muted) !important; | |
| margin-bottom: 6px; | |
| } | |
| .ccard-num { | |
| font-size: 28px; | |
| font-weight: 800; | |
| letter-spacing: -0.03em; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .ccard-divider { | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: var(--muted2) !important; | |
| letter-spacing: 0.08em; | |
| flex-shrink: 0; | |
| } | |
| .accuracy-row { display: flex; flex-direction: column; gap: 6px; } | |
| .accuracy-bar-bg { | |
| height: 5px; | |
| border-radius: 99px; | |
| background: var(--border); | |
| overflow: hidden; | |
| } | |
| .accuracy-bar-fill { | |
| height: 100%; | |
| border-radius: 99px; | |
| transition: width 0.4s ease; | |
| } | |
| .accuracy-meta { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: 12px; | |
| } | |
| .accuracy-pct { font-weight: 700; color: var(--text) !important; } | |
| .diff-exact { color: var(--green) !important; font-weight: 600; } | |
| .diff-over { color: var(--red) !important; } | |
| .diff-under { color: var(--blue) !important; } | |
| /* ββ tokenizer error ββ */ | |
| .tok-error { | |
| padding: 12px 14px; | |
| border-radius: var(--r); | |
| border: 1px solid rgba(248,113,113,0.25); | |
| background: rgba(248,113,113,0.05); | |
| font-size: 12.5px; | |
| color: var(--muted) !important; | |
| display: flex; | |
| gap: 10px; | |
| align-items: flex-start; | |
| } | |
| .tok-err-icon { color: #f87171 !important; flex-shrink: 0; font-size: 14px; } | |
| /* ββ features ββ */ | |
| .features-section { | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| background: var(--surf); | |
| padding: 16px 18px; | |
| } | |
| .features-title { | |
| font-size: 11px; | |
| font-weight: 700; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--muted) !important; | |
| margin-bottom: 12px; | |
| } | |
| .chips { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 7px; | |
| } | |
| @media (max-width: 560px) { .chips { grid-template-columns: repeat(2, 1fr); } } | |
| .chip { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 8px 11px; | |
| border: 1px solid var(--border); | |
| border-radius: var(--r-sm); | |
| background: var(--surf2); | |
| gap: 8px; | |
| min-width: 0; | |
| } | |
| .chip-label { | |
| font-size: 11px; | |
| color: var(--muted) !important; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .chip-val { | |
| font-size: 12px; | |
| font-weight: 700; | |
| font-family: ui-monospace, monospace; | |
| flex-shrink: 0; | |
| } | |
| /* ββ error banner ββ */ | |
| .error-banner { | |
| display: flex; | |
| gap: 14px; | |
| align-items: flex-start; | |
| padding: 18px 20px; | |
| border: 1px solid rgba(248,113,113,0.3); | |
| border-radius: 16px; | |
| background: rgba(248,113,113,0.06); | |
| } | |
| .err-icon { font-size: 18px; color: #f87171 !important; flex-shrink: 0; margin-top: 2px; } | |
| .err-title { font-size: 14px; font-weight: 700; margin-bottom: 4px; } | |
| .err-body { font-size: 13px; color: var(--muted) !important; line-height: 1.5; } | |
| /* ββ empty state ββ */ | |
| .empty-state { | |
| padding: 40px 24px; | |
| text-align: center; | |
| border: 1px dashed var(--border2); | |
| border-radius: 16px; | |
| } | |
| .empty-icon { font-size: 28px; margin-bottom: 12px; opacity: 0.3; } | |
| .empty-title { font-size: 15px; font-weight: 700; margin-bottom: 6px; } | |
| .empty-desc { font-size: 13px; color: var(--muted) !important; line-height: 1.6; } | |
| """ | |
| EMPTY_HTML = """ | |
| <div class="empty-state"> | |
| <div class="empty-icon">⬑</div> | |
| <div class="empty-title">Ready to predict</div> | |
| <div class="empty-desc"> | |
| Paste text above and press <b>Predict</b>.<br> | |
| Add a tokenizer repo ID to compare against ground-truth token count. | |
| </div> | |
| </div> | |
| """ | |
| # ββ UI layout ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Blocks(title="JetonCount") as demo: | |
| gr.HTML(f""" | |
| <div class="hero"> | |
| <div class="hero-eyebrow">Token Count Estimator</div> | |
| <div class="hero-title">JetonCount</div> | |
| <div class="hero-desc"> | |
| Predict how many tokens a text will produce β without running a full tokenizer. | |
| Optionally compare against any Hugging Face tokenizer for accuracy metrics. | |
| </div> | |
| <div class="hero-badges"> | |
| <span class="badge">MLP regressor</span> | |
| <span class="badge">fromziro/JetonCount</span> | |
| <span class="badge">{DEVICE.type.upper()}</span> | |
| </div> | |
| </div> | |
| """) | |
| locked_vocab = gr.State(None) | |
| with gr.Row(equal_height=False): | |
| # ββ left ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Column(scale=5, min_width=360): | |
| text_in = gr.Textbox( | |
| label="Text", | |
| lines=14, | |
| placeholder="Paste your text hereβ¦", | |
| container=True, | |
| ) | |
| counter_html = gr.HTML(value="<div class='live-counter'><span>0 chars</span><span class='sep'>Β·</span><span>0 words</span></div>") | |
| predict_btn = gr.Button("⬑ Predict tokens", variant="primary", size="lg") | |
| # ββ right βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Column(scale=3, min_width=260): | |
| tokenizer_in = gr.Textbox( | |
| label="Tokenizer repo (optional)", | |
| placeholder="e.g. openai-community/gpt2", | |
| ) | |
| vocab_in = gr.Number( | |
| label="Vocab size", | |
| value=DEFAULT_VOCAB_SIZE, | |
| precision=0, | |
| interactive=True, | |
| ) | |
| status_html = gr.HTML(value="") | |
| clear_btn = gr.Button("Clear tokenizer", variant="secondary", size="sm") | |
| results_html = gr.HTML(value=EMPTY_HTML) | |
| with gr.Accordion("Raw JSON", open=False): | |
| raw_json = gr.JSON(label="") | |
| # ββ wiring ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| text_in.change(fn=on_text_change, inputs=[text_in], outputs=[counter_html]) | |
| tokenizer_in.blur( | |
| fn=on_tokenizer_change, inputs=[tokenizer_in], | |
| outputs=[vocab_in, status_html, locked_vocab], | |
| ) | |
| tokenizer_in.submit( | |
| fn=on_tokenizer_change, inputs=[tokenizer_in], | |
| outputs=[vocab_in, status_html, locked_vocab], | |
| ) | |
| clear_btn.click( | |
| fn=on_clear, inputs=[vocab_in], | |
| outputs=[tokenizer_in, vocab_in, locked_vocab, status_html], | |
| ) | |
| predict_btn.click( | |
| fn=run, inputs=[text_in, vocab_in, tokenizer_in, locked_vocab], | |
| outputs=[results_html, raw_json], | |
| ) | |
| text_in.submit( | |
| fn=run, inputs=[text_in, vocab_in, tokenizer_in, locked_vocab], | |
| outputs=[results_html, raw_json], | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(theme=gr.themes.Monochrome(), css=CSS) |