import json import os import shutil import subprocess import uuid from pathlib import Path import gradio as gr from huggingface_hub import snapshot_download LEGUARD_BIN = os.environ.get("LEGUARD_BIN", "/usr/local/bin/leguard") WORKDIR = Path("/tmp/leguard-space") DATASETS_DIR = WORKDIR / "datasets" RUNS_DIR = WORKDIR / "runs" CHECK_ORDER = [ "structure", "schema", "consistency", "episodes", "temporal", "numerical", "video", "annotation", "training", "portability", ] BANNER_URL = "https://raw.githubusercontent.com/0xPraedico/LeGuard/main/assets/leguard-banner.png" REPO_URL = "https://github.com/0xPraedico/LeGuard" ISSUES_URL = f"{REPO_URL}/issues" BOOTSTRAP_JS = """ () => { const root = document.documentElement; const applyTheme = (mode) => { const normalized = String(mode || "light").toLowerCase(); const finalMode = normalized === "light" ? "light" : "dark"; root.setAttribute("data-leguard-theme", finalMode); }; window.__leguardApplyTheme = applyTheme; applyTheme("light"); const localizeFooter = () => { const localizedBuiltWith = [67, 114, 233, 233, 32, 97, 118, 101, 99] .map((cp) => String.fromCodePoint(cp)) .join(""); const localizedSettings = [80, 97, 114, 97, 109, 232, 116, 114, 101, 115] .map((cp) => String.fromCodePoint(cp)) .join(""); const localizedError = [69, 114, 114, 101, 117, 114] .map((cp) => String.fromCodePoint(cp)) .join(""); const localizedWarning = [65, 118, 101, 114, 116, 105, 115, 115, 101, 109, 101, 110, 116] .map((cp) => String.fromCodePoint(cp)) .join(""); const replacer = { [localizedBuiltWith]: "Built with", [localizedSettings]: "Settings", [localizedError]: "Error", [localizedWarning]: "Warning", }; const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); let current = walker.nextNode(); while (current) { if (current.nodeValue) { Object.entries(replacer).forEach(([from, to]) => { if (current.nodeValue.includes(from)) { current.nodeValue = current.nodeValue.replace(from, to); } }); } current = walker.nextNode(); } }; localizeFooter(); const observer = new MutationObserver(() => localizeFooter()); observer.observe(document.body, { childList: true, subtree: true }); } """ THEME_CHANGE_JS = """ (mode) => { if (window.__leguardApplyTheme) { window.__leguardApplyTheme(mode); } return []; } """ CHECK_LABELS = { "structure": "Structure", "schema": "Schema", "consistency": "Consistency", "episodes": "Episodes", "temporal": "Temporal", "numerical": "Numerical", "video": "Video", "annotation": "Annotation", "training": "Training Readiness", "portability": "Portability", } CSS = """ :root, html[data-leguard-theme="light"] { --lg-page-bg: #f3f6fb; --lg-panel-bg: #ffffff; --lg-surface-bg: #ffffff; --lg-border: #d8e1ef; --lg-text: #111827; --lg-muted: #5b6473; --lg-accent: #4f46e5; --lg-label-color: #5b6473; --lg-info-color: #4f46e5; --lg-accent-soft: #e0e7ff; --lg-input-bg: #ffffff; --lg-input-border: #c7d2e5; --lg-input-text: #111827; } html[data-leguard-theme="dark"] { --lg-page-bg: #060c1b; --lg-panel-bg: #0a1226; --lg-surface-bg: #0d1a34; --lg-border: #19345c; --lg-text: #e7eefc; --lg-muted: #93a9c9; --lg-accent: #22d3ee; --lg-label-color: #5b6473; --lg-info-color: #4f46e5; --lg-accent-soft: #14324a; --lg-input-bg: #09162d; --lg-input-border: #1b3d68; --lg-input-text: #dbeafe; } .gradio-container { background: var(--lg-page-bg) !important; color: var(--lg-text) !important; } .gradio-container, .gradio-container * { border-color: var(--lg-border); } .gradio-container input[type="text"], .gradio-container input[type="number"], .gradio-container input[type="search"], .gradio-container textarea, .gradio-container .wrap.svelte-1ykxmf5, .gradio-container .wrap.svelte-1rjryqp { background: var(--lg-input-bg) !important; color: var(--lg-input-text) !important; border-color: var(--lg-input-border) !important; } .gradio-container .tabs, .gradio-container .gradio-accordion, .gradio-container [role="tablist"], .gradio-container .form, .gradio-container .panel { background: var(--lg-panel-bg) !important; border-color: var(--lg-border) !important; } .gradio-container [role="tab"] { color: var(--lg-muted) !important; } .gradio-container [role="tab"][aria-selected="true"] { color: var(--lg-text) !important; border-bottom-color: var(--lg-accent) !important; } .gradio-container button { border-radius: 10px !important; } .gradio-container label, .gradio-container legend, .gradio-container .gr-form label, .gradio-container .gr-block label, .gradio-container .caption, .gradio-container [data-testid="block-label"], .gradio-container .svelte-1ipelgc, .gradio-container [data-testid="block-label"] *, .gradio-container .svelte-1ipelgc * { color: var(--lg-label-color) !important; opacity: 1 !important; } .gradio-container .prose, .gradio-container .prose *, .gradio-container .markdown, .gradio-container .markdown * { color: var(--lg-text) !important; opacity: 1 !important; } .gradio-container .prose p, .gradio-container .prose li, .gradio-container .prose ul, .gradio-container .prose ol, .gradio-container .prose span { color: var(--lg-text) !important; } .gradio-container .prose h1, .gradio-container .prose h2, .gradio-container .prose h3, .gradio-container .prose strong { color: var(--lg-text) !important; } .gradio-container .prose a, .gradio-container .markdown a { color: var(--lg-accent) !important; } .gradio-container [data-testid="block-info"], .gradio-container .block-info, .gradio-container .gradio-info, .gradio-container [data-testid="block-info"] *, .gradio-container .block-info *, .gradio-container .gradio-info * { color: var(--lg-info-color) !important; opacity: 1 !important; } .field-help { margin-top: 4px; font-size: 0.78rem; line-height: 1.25; color: var(--lg-info-color) !important; opacity: 1 !important; } .app-container { max-width: 1080px; margin: 0 auto; } .hero-card { border: 1px solid var(--lg-border); border-radius: 14px; padding: 14px 16px 10px 16px; background: var(--lg-panel-bg); margin-bottom: 14px; } .hero-card img { width: 100%; border-radius: 10px; } .hero-card h1 { margin: 10px 0 4px 0; font-size: 1.35rem; } .hero-card p { margin: 0; color: var(--lg-muted); } .hero-links { margin-top: 10px; } .hero-links a { color: var(--lg-accent); text-decoration: none; font-weight: 600; } .hero-links a:hover { text-decoration: underline; } .run-btn button { min-height: 48px !important; font-size: 1rem !important; font-weight: 700 !important; background: linear-gradient(90deg, #4f46e5, #4338ca) !important; color: #ffffff !important; border: none !important; } html[data-leguard-theme="dark"] .run-btn button { background: linear-gradient(90deg, #0ea5e9, #06b6d4) !important; } .refresh-btn button { min-height: 40px !important; font-size: 0.88rem !important; font-weight: 600 !important; background: var(--lg-surface-bg) !important; color: var(--lg-text) !important; border: 1px solid var(--lg-border) !important; } .status-card { border-radius: 10px; padding: 12px 14px; margin-bottom: 12px; font-weight: 600; } .gradio-container .status-card, .gradio-container .status-card * { color: inherit !important; opacity: 1 !important; } .status-neutral { background: var(--lg-surface-bg); color: var(--lg-muted); border: 1px solid var(--lg-border); } .status-pass { background: #ecfdf3 !important; color: #166534 !important; border: 1px solid #bbf7d0 !important; } .status-warn { background: #fff7ed !important; color: #9a3412 !important; border: 1px solid #fed7aa !important; } .status-fail { background: #fef2f2 !important; color: #991b1b !important; border: 1px solid #fecaca !important; } html[data-leguard-theme="dark"] .status-pass { background: #052e1f !important; color: #86efac !important; border: 1px solid #166534 !important; } html[data-leguard-theme="dark"] .status-warn { background: #3b2206 !important; color: #fdba74 !important; border: 1px solid #9a5800 !important; } html[data-leguard-theme="dark"] .status-fail { background: #3a0b10 !important; color: #fca5a5 !important; border: 1px solid #b91c1c !important; } .metrics-grid { display: grid; grid-template-columns: repeat(3, minmax(120px, 1fr)); gap: 10px; margin-bottom: 14px; } .metric { border: 1px solid var(--lg-border); border-radius: 10px; padding: 10px 12px; background: var(--lg-surface-bg); } .metric-label { font-size: 0.74rem; color: var(--lg-muted); text-transform: uppercase; letter-spacing: 0.04em; } .metric-value { margin-top: 4px; font-size: 1.1rem; font-weight: 700; color: var(--lg-text) !important; } .section-note { color: var(--lg-muted); font-size: 0.92rem; } .issues-card { border: 1px solid var(--lg-border); border-radius: 10px; background: var(--lg-surface-bg); padding: 12px; margin-top: 4px; color: var(--lg-muted); font-size: 0.92rem; } .issues-card a { color: var(--lg-accent); text-decoration: none; font-weight: 600; } .issues-card a:hover { text-decoration: underline; } """ for directory in (DATASETS_DIR, RUNS_DIR): directory.mkdir(parents=True, exist_ok=True) def run_command(command): completed = subprocess.run(command, capture_output=True, text=True) logs = [f"$ {' '.join(command)}"] if completed.stdout.strip(): logs.append(completed.stdout.strip()) if completed.stderr.strip(): logs.append(completed.stderr.strip()) return completed.returncode, "\n\n".join(logs) def resolve_dataset_path(dataset_repo_id): dataset_repo_id = dataset_repo_id.strip() if not dataset_repo_id: raise ValueError("Enter a Hugging Face dataset id, for example: praedico/SO101_pillbox_vita") target_dir = DATASETS_DIR / dataset_repo_id.replace("/", "__") if target_dir.exists(): shutil.rmtree(target_dir) token = os.environ.get("HF_TOKEN") snapshot_download( repo_id=dataset_repo_id, repo_type="dataset", local_dir=str(target_dir), token=token, ) return target_dir def summarize_report(report, check_exit): counts = report["issue_counts"] errors = counts["errors"] warnings = counts["warnings"] if errors > 0 or check_exit != 0: status = "FAIL" style = "status-fail" status_text = "Blocking issues detected. Review and fix before using this dataset in CI/training." elif warnings > 0: status = "WARN" style = "status-warn" status_text = "Warnings detected. Dataset is likely usable, but review issues before large runs." else: status = "PASS" style = "status-pass" status_text = "No blocking issues detected." return ( f"
Run LeGuard diagnostics on Hugging Face LeRobot datasets and export actionable reports.