Spaces:
Running
Running
| """ | |
| CROVIA OMISSION ORACLE v2.1.0 | |
| The Evidence Protocol for AI Training Compliance | |
| Building the global standard for AI trust — in public, from day one. | |
| """ | |
| import json | |
| import hashlib | |
| import gradio as gr | |
| import requests | |
| from datetime import datetime, timezone | |
| from typing import Dict, List, Any, Optional | |
| VERSION = "2.1.0" | |
| PROTOCOL_VERSION = "v1.0" | |
| TPR_API_URL = "https://registry.croviatrust.com" | |
| DATASET_ID = "Crovia/global-ai-training-omissions" | |
| # Fallback cards for display | |
| FALLBACK_CARDS = [ | |
| {"model_id": "meta-llama/Llama-3.1-8B", "score": 72, "badge": "BRONZE", "violations": ["NEC#1", "NEC#7"], "author": "meta-llama", "downloads": "N/A"}, | |
| {"model_id": "mistralai/Mistral-7B-v0.3", "score": 94, "badge": "GOLD", "violations": [], "author": "mistralai", "downloads": "N/A"}, | |
| ] | |
| try: | |
| from huggingface_hub import hf_hub_download | |
| HF_AVAILABLE = True | |
| except ImportError: | |
| HF_AVAILABLE = False | |
| def load_latest_cards_from_registry() -> List[Dict[str, Any]]: | |
| """Load latest observations from live registry API with Shadow Scores.""" | |
| try: | |
| # Fetch live data from registry | |
| response = requests.get(f"{TPR_API_URL}/api/registry/recent?limit=12", timeout=5) | |
| if response.status_code == 200: | |
| data = response.json() | |
| observations = data.get('observations', []) | |
| # Get Shadow Scores from dataset (updated by cron) | |
| shadow_scores = {} | |
| try: | |
| if HF_AVAILABLE: | |
| index_path = hf_hub_download( | |
| repo_id=DATASET_ID, | |
| filename="badges/index.json", | |
| repo_type="dataset" | |
| ) | |
| with open(index_path, 'r') as f: | |
| badges_data = json.load(f) | |
| for badge in badges_data.get("badges", []): | |
| shadow_scores[badge.get("model_id")] = badge.get("score", 0) | |
| except: | |
| pass | |
| # Convert observations to card format | |
| cards = [] | |
| for obs in observations: | |
| # Skip test/internal files | |
| target_id = obs.get('target_id', '') | |
| if any(pattern in target_id.lower() for pattern in ['test-', 'crovia-', 'debug-', 'demo-']): | |
| continue | |
| # Get Shadow Score if available | |
| score = shadow_scores.get(target_id, 0) | |
| # Determine badge based on score | |
| if score >= 90: | |
| badge = "GOLD" | |
| elif score >= 70: | |
| badge = "SILVER" | |
| elif score >= 50: | |
| badge = "BRONZE" | |
| else: | |
| badge = "UNVERIFIED" | |
| cards.append({ | |
| "model_id": target_id, | |
| "score": score, | |
| "badge": badge, | |
| "violations": [], | |
| "author": target_id.split("/")[0] if "/" in target_id else "Unknown", | |
| "downloads": "N/A", | |
| "observation_type": obs.get('observation_type', 'unknown'), | |
| "observed_at": obs.get('observed_at', '') | |
| }) | |
| if cards: | |
| return cards | |
| # Fallback to dataset if API fails | |
| return load_latest_cards_from_dataset() | |
| except Exception as e: | |
| print(f"Error loading from registry: {e}") | |
| return load_latest_cards_from_dataset() | |
| def load_latest_cards_from_dataset() -> List[Dict[str, Any]]: | |
| """Load pre-computed model cards from public dataset (fallback).""" | |
| try: | |
| if not HF_AVAILABLE: | |
| return FALLBACK_CARDS | |
| index_path = hf_hub_download( | |
| repo_id=DATASET_ID, | |
| filename="badges/index.json", | |
| repo_type="dataset" | |
| ) | |
| with open(index_path, 'r') as f: | |
| data = json.load(f) | |
| cards = [] | |
| for badge in data.get("badges", [])[:12]: | |
| model_id = badge.get("model_id", "Unknown") | |
| score = badge.get("score", 0) | |
| badge_type = badge.get("badge", "UNVERIFIED") | |
| violations = [] | |
| try: | |
| card_filename = f"cards/{model_id.replace('/', '__')}.json" | |
| card_path = hf_hub_download( | |
| repo_id=DATASET_ID, | |
| filename=card_filename, | |
| repo_type="dataset" | |
| ) | |
| with open(card_path, 'r') as cf: | |
| card_data = json.load(cf) | |
| violations = card_data.get("violations", []) | |
| except: | |
| pass | |
| author = model_id.split("/")[0] if "/" in model_id else "Unknown" | |
| cards.append({ | |
| "model_id": model_id, | |
| "score": score, | |
| "badge": badge_type, | |
| "violations": violations, | |
| "author": author, | |
| "downloads": "N/A" | |
| }) | |
| return cards if cards else FALLBACK_CARDS | |
| except Exception as e: | |
| print(f"Error loading cards: {e}") | |
| return FALLBACK_CARDS | |
| # Load cards from live registry (real-time data) | |
| PRELOADED_CARDS = load_latest_cards_from_registry() | |
| NECESSITY_CANON = { | |
| "NEC#1": {"name": "Missing data provenance", "severity": 75}, | |
| "NEC#2": {"name": "Missing license attribution", "severity": 80}, | |
| "NEC#7": {"name": "Missing usage scope", "severity": 45}, | |
| "NEC#10": {"name": "Missing temporal validity", "severity": 40}, | |
| "NEC#13": {"name": "Missing accountable entity", "severity": 70}, | |
| } | |
| def analyze_model_live(model_id: str) -> Optional[Dict[str, Any]]: | |
| """Live analysis via TPR API (if available).""" | |
| if not model_id or not model_id.strip(): | |
| return None | |
| try: | |
| response = requests.post( | |
| f"{TPR_API_URL}/api/analyze", | |
| json={"model_id": model_id.strip()}, | |
| timeout=30 | |
| ) | |
| if response.status_code == 200: | |
| result = response.json() | |
| result["analyzed_via"] = "tpr-api" | |
| result["timestamp"] = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") | |
| return result | |
| else: | |
| return None | |
| except: | |
| return None | |
| def analyze_model_fallback(model_id: str) -> Optional[Dict[str, Any]]: | |
| """Fallback analysis using HuggingFace API directly.""" | |
| if not model_id or not model_id.strip(): | |
| return None | |
| model_id = model_id.strip() | |
| if not HF_AVAILABLE: | |
| return {"error": "Analysis unavailable - HuggingFace Hub not accessible"} | |
| try: | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| info = api.model_info(model_id) | |
| card_data = info.card_data or {} | |
| license_val = card_data.get("license") or getattr(info, "license", None) | |
| datasets = card_data.get("datasets", []) or [] | |
| author = info.author or "" | |
| tags = info.tags or [] | |
| violations = [] | |
| if not datasets: | |
| violations.append("NEC#1") | |
| if not license_val: | |
| violations.append("NEC#2") | |
| has_use = any("task" in t.lower() or "use" in t.lower() for t in tags) | |
| if not has_use: | |
| violations.append("NEC#7") | |
| if not author: | |
| violations.append("NEC#13") | |
| total_severity = sum(NECESSITY_CANON.get(v, {}).get("severity", 30) for v in violations) | |
| score = max(0, min(100, 100 - int(total_severity * 0.15))) | |
| if score >= 90: | |
| badge = "GOLD" | |
| elif score >= 75: | |
| badge = "SILVER" | |
| elif score >= 60: | |
| badge = "BRONZE" | |
| else: | |
| badge = "UNVERIFIED" | |
| return { | |
| "model_id": model_id, | |
| "score": score, | |
| "badge": badge, | |
| "violations": violations, | |
| "author": author or "Unknown", | |
| "license": license_val or "Not declared", | |
| "evidence": hashlib.sha256(f"{model_id}:{score}".encode()).hexdigest()[:12], | |
| "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC"), | |
| "analyzed_via": "hf-fallback" | |
| } | |
| except Exception as e: | |
| return {"error": f"Model not found: {model_id}"} | |
| def analyze_model(model_id: str) -> Optional[Dict[str, Any]]: | |
| """Main analysis function with TPR API and fallback.""" | |
| result = analyze_model_live(model_id) | |
| if not result: | |
| result = analyze_model_fallback(model_id) | |
| return result | |
| def get_badge_color(badge: str) -> str: | |
| colors = { | |
| "GOLD": "#F59E0B", | |
| "SILVER": "#94A3B8", | |
| "BRONZE": "#D97706", | |
| "UNVERIFIED": "#EF4444" | |
| } | |
| return colors.get(badge, "#6B7280") | |
| def run_analysis(model_id: str): | |
| result = analyze_model(model_id) | |
| if not result: | |
| return "", "", "" | |
| if "error" in result: | |
| error_msg = result.get("message", result["error"]) | |
| return f'<div class="error-box">{error_msg}</div>', "", "" | |
| color = get_badge_color(result["badge"]) | |
| score_html = f''' | |
| <div class="result-card"> | |
| <div class="score-circle" style="border-color: {color}; box-shadow: 0 0 30px {color}40;"> | |
| <span class="score-value" style="color: {color};">{result["score"]}</span> | |
| <span class="score-label">SHADOW SCORE</span> | |
| </div> | |
| <div class="badge-pill" style="background: {color}20; color: {color};">{result["badge"]}</div> | |
| </div> | |
| ''' | |
| info_html = f''' | |
| <div class="info-card"> | |
| <div class="info-header"> | |
| <span class="info-label">MODEL</span> | |
| <span class="info-value">{result["model_id"]}</span> | |
| </div> | |
| <div class="info-row"> | |
| <span>Author: <strong>{result.get("author", "Unknown")}</strong></span> | |
| </div> | |
| <div class="info-row"> | |
| <span>License: <strong>{result.get("license", "Not declared")}</strong></span> | |
| </div> | |
| <div class="evidence"> | |
| Evidence: {result.get("evidence", "N/A")} · {result.get("timestamp", "")} | |
| </div> | |
| </div> | |
| ''' | |
| if result.get("violations"): | |
| viol_items = "".join([f'<span class="viol-tag">{v}</span>' for v in result["violations"]]) | |
| viol_html = f'<div class="violations-box"><span class="viol-label">VIOLATIONS:</span>{viol_items}</div>' | |
| else: | |
| viol_html = '<div class="clean-box">✓ ALL CHECKS PASSED</div>' | |
| if result.get("analyzed_via") == "hf-fallback": | |
| viol_html += '<div class="note-box">⚠️ Basic analysis mode (TPR API unavailable)</div>' | |
| return score_html, info_html, viol_html | |
| def generate_cards_html(cards_data=None): | |
| if cards_data is None: | |
| cards_data = load_latest_cards_from_registry() | |
| cards = "" | |
| for c in cards_data: | |
| color = get_badge_color(c["badge"]) | |
| viols = " ".join([f'<span class="card-viol">{v}</span>' for v in c["violations"]]) if c["violations"] else '<span class="card-clean">CLEAN</span>' | |
| cards += f''' | |
| <div class="model-card"> | |
| <div class="card-header"> | |
| <span class="card-score" style="color:{color};">{c["score"]}</span> | |
| <span class="card-badge" style="background:{color}20;color:{color};">{c["badge"]}</span> | |
| </div> | |
| <div class="card-model">{c["model_id"]}</div> | |
| <div class="card-meta">{c["author"]}</div> | |
| <div class="card-violations">{viols}</div> | |
| </div> | |
| ''' | |
| return cards | |
| CSS = """ | |
| .gradio-container { | |
| max-width: 100% !important; | |
| padding: 0 !important; | |
| margin: 0 !important; | |
| background: #030305 !important; | |
| } | |
| .main, .contain, .wrap { | |
| max-width: 100% !important; | |
| padding: 0 !important; | |
| } | |
| footer { display: none !important; } | |
| .gr-form { background: transparent !important; border: none !important; } | |
| .hero { | |
| background: linear-gradient(180deg, #0a0a12 0%, #030305 100%); | |
| padding: 60px 20px 40px; | |
| text-align: center; | |
| border-bottom: 1px solid #1a1a2e; | |
| } | |
| .hero-badge { | |
| display: inline-block; | |
| background: #8B5CF620; | |
| border: 1px solid #8B5CF640; | |
| color: #8B5CF6; | |
| padding: 6px 16px; | |
| border-radius: 20px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| margin-bottom: 20px; | |
| } | |
| .hero-logo { | |
| width: 70px; | |
| height: 70px; | |
| border-radius: 16px; | |
| margin: 0 auto 20px; | |
| background: linear-gradient(135deg, #8B5CF6 0%, #6D28D9 100%); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: 0 0 40px #8B5CF650; | |
| } | |
| .hero-logo img { width: 50px; height: 50px; } | |
| .hero-title { | |
| font-size: 42px; | |
| font-weight: 900; | |
| color: #fff; | |
| letter-spacing: 0.15em; | |
| margin: 0 0 12px 0; | |
| text-shadow: 0 0 30px #8B5CF640; | |
| } | |
| .hero-subtitle { | |
| color: #9CA3AF; | |
| font-size: 18px; | |
| font-weight: 500; | |
| margin: 0 0 20px 0; | |
| letter-spacing: 0.02em; | |
| } | |
| .hero-desc { | |
| color: #6B7280; | |
| font-size: 15px; | |
| max-width: 700px; | |
| margin: 0 auto 30px; | |
| line-height: 1.7; | |
| } | |
| .hero-emphasis { | |
| color: #8B5CF6; | |
| font-weight: 600; | |
| } | |
| .hero-stats { | |
| display: flex; | |
| justify-content: center; | |
| gap: 40px; | |
| margin: 30px auto 0; | |
| max-width: 900px; | |
| flex-wrap: wrap; | |
| } | |
| .stat-item { | |
| text-align: center; | |
| } | |
| .stat-label { | |
| color: #6B7280; | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.1em; | |
| font-weight: 600; | |
| } | |
| .stat-value { | |
| color: #fff; | |
| font-size: 16px; | |
| font-weight: 700; | |
| margin-top: 4px; | |
| } | |
| .stat-link { | |
| color: #8B5CF6; | |
| text-decoration: none; | |
| font-size: 16px; | |
| font-weight: 700; | |
| } | |
| .stat-link:hover { | |
| color: #A78BFA; | |
| } | |
| .nav { | |
| display: flex; | |
| justify-content: center; | |
| gap: 24px; | |
| padding: 16px; | |
| background: #050507; | |
| border-bottom: 1px solid #1a1a2e; | |
| flex-wrap: wrap; | |
| } | |
| .nav a { | |
| color: #9CA3AF; | |
| text-decoration: none; | |
| font-size: 13px; | |
| font-weight: 500; | |
| transition: color 0.2s; | |
| } | |
| .nav a:hover { color: #8B5CF6; } | |
| .search-section { | |
| max-width: 700px; | |
| margin: 40px auto; | |
| padding: 0 20px; | |
| } | |
| .search-box { | |
| display: flex; | |
| gap: 12px; | |
| } | |
| input[type="text"], textarea { | |
| flex: 1; | |
| background: #0a0a12 !important; | |
| border: 2px solid #1a1a2e !important; | |
| color: #fff !important; | |
| border-radius: 12px !important; | |
| padding: 16px 20px !important; | |
| font-size: 16px !important; | |
| } | |
| input:focus, textarea:focus { | |
| border-color: #8B5CF6 !important; | |
| box-shadow: 0 0 20px #8B5CF620 !important; | |
| outline: none !important; | |
| } | |
| button.primary { | |
| background: linear-gradient(135deg, #8B5CF6 0%, #6D28D9 100%) !important; | |
| border: none !important; | |
| padding: 16px 32px !important; | |
| border-radius: 12px !important; | |
| font-weight: 700 !important; | |
| font-size: 14px !important; | |
| letter-spacing: 0.05em !important; | |
| color: #fff !important; | |
| cursor: pointer !important; | |
| transition: all 0.2s; | |
| } | |
| button.primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 30px #8B5CF650 !important; | |
| } | |
| .results-section { | |
| max-width: 800px; | |
| margin: 0 auto 40px; | |
| padding: 0 20px; | |
| } | |
| .result-card { | |
| background: #0a0a12; | |
| border: 1px solid #1a1a2e; | |
| border-radius: 16px; | |
| padding: 30px; | |
| text-align: center; | |
| } | |
| .score-circle { | |
| width: 140px; | |
| height: 140px; | |
| border-radius: 50%; | |
| border: 4px solid; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| margin: 0 auto 20px; | |
| background: #050507; | |
| } | |
| .score-value { font-size: 48px; font-weight: 900; } | |
| .score-label { font-size: 10px; color: #6B7280; letter-spacing: 0.1em; } | |
| .badge-pill { | |
| display: inline-block; | |
| padding: 8px 24px; | |
| border-radius: 20px; | |
| font-weight: 700; | |
| font-size: 14px; | |
| letter-spacing: 0.1em; | |
| } | |
| .info-card { | |
| background: #0a0a12; | |
| border: 1px solid #1a1a2e; | |
| border-radius: 16px; | |
| padding: 20px; | |
| margin-top: 16px; | |
| } | |
| .info-header { margin-bottom: 12px; } | |
| .info-label { color: #8B5CF6; font-size: 10px; letter-spacing: 0.1em; display: block; } | |
| .info-value { color: #fff; font-size: 18px; font-weight: 700; } | |
| .info-row { color: #9CA3AF; font-size: 13px; margin: 8px 0; } | |
| .info-row strong { color: #fff; } | |
| .evidence { color: #4B5563; font-size: 11px; font-family: monospace; margin-top: 12px; padding-top: 12px; border-top: 1px solid #1a1a2e; } | |
| .violations-box { background: #1a0a0a; border: 1px solid #EF444440; border-radius: 12px; padding: 16px; margin-top: 16px; } | |
| .viol-label { color: #EF4444; font-size: 11px; letter-spacing: 0.1em; margin-right: 12px; } | |
| .viol-tag { background: #EF444420; color: #EF4444; padding: 4px 12px; border-radius: 6px; font-size: 12px; font-weight: 600; margin: 0 4px; } | |
| .clean-box { background: #0a1a0a; border: 1px solid #22C55E40; border-radius: 12px; padding: 16px; margin-top: 16px; color: #22C55E; text-align: center; font-weight: 600; } | |
| .note-box { background: #1a1a0a; border: 1px solid #F59E0B40; border-radius: 12px; padding: 12px; margin-top: 12px; color: #F59E0B; text-align: center; font-size: 12px; } | |
| .error-box { background: #1a0a0a; border: 1px solid #EF4444; border-radius: 12px; padding: 20px; color: #EF4444; text-align: center; } | |
| .cards-section { | |
| background: #050507; | |
| padding: 40px 20px; | |
| border-top: 1px solid #1a1a2e; | |
| } | |
| .cards-title { | |
| text-align: center; | |
| color: #fff; | |
| font-size: 24px; | |
| font-weight: 700; | |
| margin-bottom: 8px; | |
| } | |
| .cards-sub { | |
| text-align: center; | |
| color: #6B7280; | |
| font-size: 13px; | |
| margin-bottom: 30px; | |
| } | |
| .cards-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | |
| gap: 16px; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| .model-card { | |
| background: #0a0a12; | |
| border: 1px solid #1a1a2e; | |
| border-radius: 12px; | |
| padding: 20px; | |
| transition: transform 0.2s, box-shadow 0.2s; | |
| } | |
| .model-card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: 0 10px 30px rgba(139, 92, 246, 0.15); | |
| border-color: #8B5CF640; | |
| } | |
| .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } | |
| .card-score { font-size: 32px; font-weight: 900; } | |
| .card-badge { padding: 4px 12px; border-radius: 6px; font-size: 11px; font-weight: 700; } | |
| .card-model { color: #fff; font-size: 14px; font-weight: 600; margin-bottom: 4px; word-break: break-all; } | |
| .card-meta { color: #6B7280; font-size: 12px; margin-bottom: 12px; } | |
| .card-violations { display: flex; flex-wrap: wrap; gap: 6px; } | |
| .card-viol { background: #EF444420; color: #EF4444; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 600; } | |
| .card-clean { background: #22C55E20; color: #22C55E; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 600; } | |
| .site-footer { | |
| background: #030305; | |
| border-top: 1px solid #1a1a2e; | |
| padding: 40px 20px 30px; | |
| text-align: center; | |
| } | |
| .footer-tagline { | |
| color: #8B5CF6; | |
| font-size: 14px; | |
| font-weight: 600; | |
| margin-bottom: 20px; | |
| letter-spacing: 0.05em; | |
| } | |
| .footer-links { display: flex; justify-content: center; gap: 24px; margin-bottom: 20px; flex-wrap: wrap; } | |
| .footer-links a { color: #6B7280; text-decoration: none; font-size: 13px; } | |
| .footer-links a:hover { color: #8B5CF6; } | |
| .footer-copy { color: #4B5563; font-size: 11px; margin-bottom: 8px; } | |
| .footer-protocol { color: #374151; font-size: 10px; font-family: monospace; } | |
| @media (max-width: 640px) { | |
| .hero-title { font-size: 32px; } | |
| .hero-subtitle { font-size: 16px; } | |
| .hero-stats { gap: 20px; } | |
| .nav { gap: 16px; } | |
| .search-box { flex-direction: column; } | |
| button.primary { width: 100%; } | |
| .cards-grid { grid-template-columns: 1fr; } | |
| } | |
| """ | |
| HEADER_HTML = f''' | |
| <div class="hero"> | |
| <div class="hero-badge">🔴 FOUNDING STAGE · PROTOCOL {PROTOCOL_VERSION}</div> | |
| <div class="hero-logo"> | |
| <img src="https://huggingface.co/spaces/Crovia/omission-oracle/resolve/main/crovia-logo.png" alt="Crovia" onerror="this.style.display='none'"> | |
| </div> | |
| <h1 class="hero-title">CROVIA</h1> | |
| <p class="hero-subtitle">The Evidence Protocol for AI Training</p> | |
| <p class="hero-desc"> | |
| We're building the <span class="hero-emphasis">global standard</span> for AI compliance verification | |
| — in public, from day one. Cryptographic evidence. Multi-regulation. Open protocol. | |
| </p> | |
| <div class="hero-stats"> | |
| <div class="stat-item"> | |
| <div class="stat-label">Protocol</div> | |
| <div class="stat-value">{PROTOCOL_VERSION} · Jan 2026</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-label">Public Dataset</div> | |
| <a href="https://huggingface.co/datasets/{DATASET_ID}" target="_blank" class="stat-link"> | |
| Updated Every 6h → | |
| </a> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-label">Open Source</div> | |
| <a href="https://github.com/croviatrust" target="_blank" class="stat-link"> | |
| GitHub → | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="nav"> | |
| <a href="https://croviatrust.com" target="_blank">🏠 Home</a> | |
| <a href="https://huggingface.co/datasets/{DATASET_ID}" target="_blank">📊 Rankings</a> | |
| <a href="https://huggingface.co/datasets/Crovia/cep-capsules" target="_blank">📦 Evidence Capsules</a> | |
| <a href="https://huggingface.co/spaces/Crovia/cep-terminal" target="_blank">🔍 CEP Terminal</a> | |
| <a href="https://github.com/croviatrust" target="_blank">⚙️ GitHub</a> | |
| <a href="https://github.com/croviatrust/CROVIA_DEV/blob/master/CROVIA_PROTOCOL_SPEC.md" target="_blank">📄 Protocol Spec</a> | |
| </div> | |
| ''' | |
| CARDS_HTML = f''' | |
| <div class="cards-section"> | |
| <h2 class="cards-title">Latest Oracle Cards</h2> | |
| <p class="cards-sub">Auto-generated evidence · Public dataset · Updated every 6 hours</p> | |
| <div class="cards-grid"> | |
| {generate_cards_html()} | |
| </div> | |
| </div> | |
| ''' | |
| FOOTER_HTML = ''' | |
| <div class="site-footer"> | |
| <div class="footer-tagline">Evidence, not promises. Observation, not judgment.</div> | |
| <div class="footer-links"> | |
| <a href="https://croviatrust.com">Official Site</a> | |
| <a href="https://github.com/croviatrust">GitHub</a> | |
| <a href="https://huggingface.co/datasets/Crovia/global-ai-training-omissions">Dataset</a> | |
| <a href="https://github.com/croviatrust/CROVIA_DEV/blob/master/CROVIA_PROTOCOL_SPEC.md">Protocol Spec</a> | |
| <a href="https://github.com/croviatrust/CROVIA_DEV/blob/master/TPR_RESOLUTION_CONTRACT.md">TPR Contract</a> | |
| </div> | |
| <p class="footer-copy">© 2026 Crovia Protocol · Apache 2.0 License</p> | |
| <p class="footer-protocol">Built in public by contributors worldwide.</p> | |
| </div> | |
| ''' | |
| with gr.Blocks(css=CSS, title="CROVIA · Omission Oracle") as app: | |
| gr.HTML(HEADER_HTML) | |
| with gr.Column(elem_classes="search-section"): | |
| with gr.Row(elem_classes="search-box"): | |
| model_input = gr.Textbox( | |
| placeholder="Enter model ID (e.g., meta-llama/Llama-3.1-8B)", | |
| show_label=False, | |
| container=False, | |
| ) | |
| analyze_btn = gr.Button("🔍 ANALYZE", variant="primary") | |
| with gr.Column(elem_classes="results-section"): | |
| score_output = gr.HTML() | |
| info_output = gr.HTML() | |
| viol_output = gr.HTML() | |
| gr.HTML(CARDS_HTML) | |
| gr.HTML(FOOTER_HTML) | |
| analyze_btn.click(run_analysis, inputs=[model_input], outputs=[score_output, info_output, viol_output]) | |
| model_input.submit(run_analysis, inputs=[model_input], outputs=[score_output, info_output, viol_output]) | |
| if __name__ == "__main__": | |
| app.launch() | |