CoolFace
Apppublic

nvidia/LocateAnything

sourceHugging Faceupdated 4mo agoView on Hugging Face
496likes
index.html1070 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="en">3<head>4    <meta charset="UTF-8">5    <meta name="viewport" content="width=device-width, initial-scale=1.0">6    <title>NVIDIA LocateAnything - Fast Vision-Language Grounding</title>7    8    <!-- Premium Google Fonts -->9    <link rel="preconnect" href="https://fonts.googleapis.com">10    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>11    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@500;600;700;800;900&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">12    13    <!-- Tailwind CSS CDN -->14    <script src="https://cdn.tailwindcss.com"></script>15    16    <script>17        tailwind.config = {18            theme: {19                extend: {20                    fontFamily: {21                        sans: ['Inter', 'sans-serif'],22                        outfit: ['Outfit', 'sans-serif'],23                        mono: ['Fira Code', 'monospace'],24                    },25                    colors: {26                        nvidia: {27                            light: '#76b900',28                            brand: '#76b900',29                            dark: '#5c9000',30                            hover: '#87d300',31                        },32                        dark: {33                            50: '#222222',34                            100: '#1a1a1a',35                            200: '#121212',36                            300: '#0a0a0a',37                            400: '#050505',38                        }39                    }40                }41            }42        }43    </script>44 45    <style>46        body {47            background-color: #050505;48            background-image: 49                radial-gradient(circle at 10% 20%, rgba(118, 185, 0, 0.08) 0%, transparent 45%),50                radial-gradient(circle at 90% 80%, rgba(99, 102, 241, 0.05) 0%, transparent 45%);51            background-attachment: fixed;52        }53 54        /* NVIDIA-style Carbon Triangle Grid Pattern */55        .carbon-grid {56            background-image: 57                linear-gradient(30deg, #0f0f0f 12%, transparent 12.5%, transparent 87%, #0f0f0f 87.5%, #0f0f0f),58                linear-gradient(150deg, #0f0f0f 12%, transparent 12.5%, transparent 87%, #0f0f0f 87.5%, #0f0f0f),59                linear-gradient(30deg, #0f0f0f 12%, transparent 12.5%, transparent 87%, #0f0f0f 87.5%, #0f0f0f),60                linear-gradient(150deg, #0f0f0f 12%, transparent 12.5%, transparent 87%, #0f0f0f 87.5%, #0f0f0f),61                linear-gradient(60deg, #171717 25%, transparent 25.5%, transparent 75%, #171717 75.5%, #171717),62                linear-gradient(60deg, #171717 25%, transparent 25.5%, transparent 75%, #171717 75.5%, #171717);63            background-size: 80px 140px;64            background-position: 0 0, 0 0, 40px 70px, 40px 70px, 0 0, 40px 70px;65        }66 67        /* Glassmorphism Styles */68        .glass-panel {69            background: rgba(18, 18, 18, 0.65);70            backdrop-filter: blur(20px);71            -webkit-backdrop-filter: blur(20px);72            border: 1px solid rgba(255, 255, 255, 0.04);73            box-shadow: 0 24px 64px 0 rgba(0, 0, 0, 0.7);74        }75 76        .glass-panel-interactive {77            transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);78        }79        .glass-panel-interactive:hover {80            border-color: rgba(118, 185, 0, 0.25);81            box-shadow: 0 30px 80px 0 rgba(118, 185, 0, 0.08);82            transform: translateY(-2px);83        }84 85        /* SAM 3 Style Glassmorphic Float Input */86        .sam-input-bar {87            background: rgba(255, 255, 255, 0.06);88            backdrop-filter: blur(25px);89            -webkit-backdrop-filter: blur(25px);90            border: 1px solid rgba(255, 255, 255, 0.08);91            box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);92            transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);93        }94        .sam-input-bar:focus-within {95            background: rgba(255, 255, 255, 0.09);96            border-color: rgba(118, 185, 0, 0.6);97            box-shadow: 0 20px 48px rgba(118, 185, 0, 0.15);98        }99 100        /* Hexagonal Glowing Border for Media Workspace (NVIDIA GTC Keynote Style) */101        .gtc-polygon-wrapper {102            position: relative;103            background: #0f1218;104            border: 1px solid rgba(118, 185, 0, 0.15);105            box-shadow: 0 0 50px rgba(0, 0, 0, 0.8);106            overflow: hidden;107            clip-path: polygon(8% 0%, 100% 0%, 100% 92%, 92% 100%, 0% 100%, 0% 8%);108        }109        .gtc-polygon-wrapper::before {110            content: '';111            position: absolute;112            top: 0;113            left: 0;114            width: 100%;115            height: 100%;116            border: 2px solid #76b900;117            pointer-events: none;118            clip-path: polygon(8% 0%, 100% 0%, 100% 92%, 92% 100%, 0% 100%, 0% 8%);119            opacity: 0.8;120            box-shadow: inset 0 0 20px rgba(118, 185, 0, 0.3);121        }122 123        .gtc-neon-border {124            position: absolute;125            top: -2px;126            left: -2px;127            right: -2px;128            bottom: -2px;129            background: linear-gradient(135deg, #76b900, #3f6200, #76b900);130            z-index: 0;131            pointer-events: none;132            opacity: 0.95;133            clip-path: polygon(8% 0%, 100% 0%, 100% 92%, 92% 100%, 0% 100%, 0% 8%);134        }135 136        .gtc-inner-box {137            position: relative;138            background: #080a0e;139            z-index: 10;140            height: 100%;141            clip-path: polygon(8.1% 0.1%, 99.9% 0.1%, 99.9% 91.9%, 91.9% 99.9%, 0.1% 99.9%, 0.1% 8.1%);142        }143 144        /* Pill Buttons styling */145        .pill-btn-green {146            background-color: #76b900;147            transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);148        }149        .pill-btn-green:hover {150            background-color: #87d300;151            box-shadow: 0 0 24px rgba(118, 185, 0, 0.45);152            transform: translateY(-1px);153        }154        .pill-btn-green:active {155            transform: translateY(1px);156        }157 158        /* Custom Scrollbar */159        ::-webkit-scrollbar {160            width: 6px;161            height: 6px;162        }163        ::-webkit-scrollbar-track {164            background: #0a0a0a;165        }166        ::-webkit-scrollbar-thumb {167            background: #222;168            border-radius: 3px;169        }170        ::-webkit-scrollbar-thumb:hover {171            background: #333;172        }173 174        /* Pulse loaders */175        .dot-pulse {176            animation: pulse 1.4s infinite ease-in-out;177        }178        @keyframes pulse {179            0%, 100% { opacity: 0.3; transform: scale(0.9); }180            50% { opacity: 1; transform: scale(1.1); }181        }182 183        .drop-zone-active {184            border-color: #76b900 !important;185            background: rgba(118, 185, 0, 0.04) !important;186        }187 188        /* Detection overlay tag pop-in (restored from previous demo) */189        @keyframes det-pop {190            0%   { opacity: 0; transform: translateY(10px) scale(0.88); }191            60%  { opacity: 1; transform: translateY(-2px) scale(1.03); }192            100% { opacity: 1; transform: translateY(0) scale(1); }193        }194        .det-tag-pop {195            opacity: 0;196            animation: det-pop 0.38s cubic-bezier(0.16, 1, 0.3, 1) forwards;197        }198        .det-count-pop {199            animation: det-pop 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;200        }201 202        /* Detected overlays: fixed height, internal scroll */203        .detection-scroll {204            min-height: 0;205            overflow-y: auto;206            overscroll-behavior: contain;207            scroll-behavior: smooth;208            scrollbar-width: thin;209            scrollbar-color: rgba(118, 185, 0, 0.45) rgba(0, 0, 0, 0.2);210            mask-image: linear-gradient(to bottom, black 88%, transparent 100%);211            -webkit-mask-image: linear-gradient(to bottom, black 88%, transparent 100%);212        }213        .detection-scroll::-webkit-scrollbar {214            width: 5px;215        }216        .detection-scroll::-webkit-scrollbar-thumb {217            background: rgba(118, 185, 0, 0.45);218            border-radius: 999px;219        }220    </style>221</head>222<body class="text-slate-100 font-sans min-h-screen pb-16 carbon-grid">223 224    <!-- NVIDIA Brand Navigation Header (Transparent dark blur) -->225    <nav class="bg-black/40 backdrop-blur-md sticky top-0 z-50 px-6 py-3.5 border-b border-white/5 shadow-lg">226        <div class="max-w-[1600px] mx-auto flex items-center justify-between">227            <!-- Official Styled NVIDIA Brand Text Logo -->228            <a href="#" class="flex items-center gap-1.5 select-none group">229                <svg class="h-6 w-6 text-nvidia-brand transition-transform duration-500 group-hover:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">230                    <path stroke-linecap="round" stroke-linejoin="round" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2z" />231                </svg>232                <span class="font-outfit text-[22px] font-black tracking-tighter text-white">233                    NVIDIA <span class="font-light tracking-wide text-slate-400">LocateAnything</span>234                </span>235            </a>236 237            <span class="px-3 py-1 text-xs font-semibold rounded bg-nvidia-brand/10 text-nvidia-brand border border-nvidia-brand/20 flex items-center gap-1.5 font-mono">238                <span class="h-1.5 w-1.5 rounded-full bg-nvidia-brand animate-pulse"></span>239                ZeroGPU Server240            </span>241        </div>242    </nav>243 244    <!-- MAIN MINIMAL LAYOUT CONTAINER -->245    <main class="max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8 pt-8 lg:pt-10 space-y-8">246        247        <!-- Giant Showcase Container (SAM 3 Full-Bleed Style) -->248        <div class="relative w-full rounded-[32px] overflow-hidden border border-white/5 bg-[#080a0e] shadow-2xl h-[580px] lg:h-[640px] flex select-none">249            250            <!-- 1. Dedicated Media Canvas (offset right of the control panel on desktop) -->251            <div class="absolute inset-y-0 right-0 left-0 lg:left-[440px] z-0 flex items-center justify-center bg-black/40 lg:border-l lg:border-white/10">252                <!-- Drop Zone (Initially shown) -->253                <div id="drop-zone" class="absolute inset-0 border-none rounded-none bg-transparent flex flex-col items-center justify-center p-4 text-center cursor-pointer transition-all z-10">254                    <div id="upload-prompt" class="space-y-3 opacity-60 hover:opacity-100 transition-opacity">255                        <div class="inline-flex h-12 w-12 rounded-full bg-white/5 items-center justify-center text-slate-300">256                            <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">257                                <path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />258                            </svg>259                        </div>260                        <div>261                            <p class="text-xs font-bold text-slate-200">Drag & drop your file here</p>262                            <p class="text-[10px] text-slate-500 mt-1">or click to browse local folders</p>263                        </div>264                    </div>265 266                    <!-- Dynamic Preview Media -->267                    <img id="preview-image" src="" alt="Input Preview" class="hidden max-h-full max-w-full rounded-2xl object-contain shadow-2xl z-20 border border-white/5">268                    <video id="preview-video" src="" controls class="hidden max-h-full max-w-full rounded-2xl object-contain shadow-2xl z-20 border border-white/5"></video>269 270                    <!-- File Input -->271                    <input type="file" id="media-file-input" accept="image/*,video/*" class="absolute inset-0 opacity-0 cursor-pointer z-30">272                </div>273 274                <!-- Inference Output Zone -->275                <div class="absolute inset-0 pointer-events-none flex items-center justify-center z-20">276                    <img id="output-image" src="" alt="Inference Output" class="hidden max-h-full max-w-full rounded-2xl object-contain shadow-2xl pointer-events-auto border border-white/5">277                    <video id="output-video" src="" controls class="hidden max-h-full max-w-full rounded-2xl object-contain shadow-2xl pointer-events-auto border border-white/5"></video>278                </div>279 280                <!-- Processing Overlays -->281                <div id="processing-overlay" class="absolute inset-0 bg-black/85 backdrop-blur-sm hidden flex-col items-center justify-center gap-4 z-40">282                    <div class="flex gap-1.5">283                        <span class="dot-pulse inline-block h-3 w-3 rounded-full bg-nvidia-brand" style="animation-delay: 0s;"></span>284                        <span class="dot-pulse inline-block h-3 w-3 rounded-full bg-emerald-400" style="animation-delay: 0.2s;"></span>285                        <span class="dot-pulse inline-block h-3 w-3 rounded-full bg-emerald-300" style="animation-delay: 0.4s;"></span>286                    </div>287                    <div class="text-center space-y-1">288                        <p id="processing-status" class="text-[11px] font-bold tracking-widest text-slate-200 uppercase">Executing Model...</p>289                        <p class="text-[9px] text-slate-500 uppercase tracking-wider font-mono">ZeroGPU Queue Active</p>290                    </div>291                </div>292            </div>293 294            <!-- 2. Left Control Panel (Title, simple selectors, accordion, and action buttons) -->295            <div class="absolute left-6 top-8 bottom-8 z-30 flex flex-col justify-between w-[380px] max-w-[calc(100%-3rem)] pointer-events-none">296                297                <!-- Main Header Overlay text -->298                <div class="space-y-3 pt-4 pointer-events-auto bg-gradient-to-b from-[#080a0e]/90 via-[#080a0e]/60 to-transparent p-4 rounded-2xl">299                    <span class="text-[9px] font-bold text-nvidia-brand uppercase tracking-widest block font-mono">AI Research from NVIDIA</span>300                    <h1 class="font-outfit text-3xl sm:text-5xl font-black tracking-tight text-white leading-none">301                        Locate<span class="text-nvidia-brand font-light">Anything</span>302                    </h1>303                    <p class="text-xs text-slate-400 max-w-sm font-medium leading-relaxed">304                        NVIDIA's advanced 3B vision-language model. Locate any object, UI target, or text in images and videos with natural language.305                    </p>306                    <p class="text-[9px] text-slate-500 max-w-sm leading-relaxed border-l-2 border-nvidia-brand/30 pl-2.5">307                        Note: inputs larger than 1K are auto-resized in this Space demo. For full-resolution inference, download the weights and run locally.308                    </p>309                </div>310 311                <!-- Setup Glass Card Controls -->312                <div class="glass-panel rounded-2xl p-4 space-y-4 pointer-events-auto max-w-xs shadow-2xl">313                    <div class="grid grid-cols-2 gap-3">314                        315                        <!-- Media Type toggle selection -->316                        <div class="space-y-1">317                            <label class="text-[8px] font-bold text-slate-400 uppercase tracking-widest">Media Type</label>318                            <div class="grid grid-cols-2 gap-0.5 bg-black/40 p-0.5 rounded-lg border border-white/5 text-center">319                                <button id="media-type-image" class="py-1 rounded-md font-semibold text-[9px] transition-all bg-nvidia-brand text-black font-outfit font-black shadow shadow-nvidia-brand/10">320                                    Image321                                </button>322                                <button id="media-type-video" class="py-1 rounded-md font-semibold text-[9px] text-slate-400 hover:text-slate-200 transition-all">323                                    Video324                                </button>325                            </div>326                        </div>327 328                        <!-- Task Selector -->329                        <div class="space-y-1">330                            <label for="task-type" class="text-[8px] font-bold text-slate-400 uppercase tracking-widest">Task Type</label>331                            <select id="task-type" class="w-full bg-black/40 border border-white/5 rounded-lg px-2 py-1 text-[9px] focus:border-nvidia-brand focus:outline-none transition-all text-slate-200 font-semibold">332                                <option value="Detection">Detection</option>333                                <option value="Grounding">Grounding</option>334                                <option value="OCR">OCR</option>335                                <option value="GUI">GUI</option>336                                <option value="Pointing">Pointing</option>337                            </select>338                        </div>339 340                    </div>341 342                    <!-- Advanced parameters sliders (Collapsible details inside the left overlay) -->343                    <details class="group border-t border-white/5 pt-3">344                        <summary class="list-none flex justify-between items-center cursor-pointer select-none text-[8px] font-bold text-slate-400 tracking-wider uppercase hover:text-slate-200 transition-colors">345                            <span>โš™๏ธ Advanced parameters</span>346                            <svg class="h-3 w-3 transform group-open:rotate-180 transition-transform text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">347                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />348                            </svg>349                        </summary>350                        <div class="space-y-3 pt-3">351                            352                            <!-- Inference Mode Selection -->353                            <div class="space-y-1">354                                <label for="inference-mode" class="text-[8px] font-bold text-slate-400 uppercase tracking-widest">Inference Mode</label>355                                <select id="inference-mode" class="w-full bg-black/40 border border-white/5 rounded-lg px-2 py-1 text-[9px] focus:border-nvidia-brand focus:outline-none transition-all text-slate-200">356                                    <option value="hybrid">Hybrid</option>357                                    <option value="fast">Fast</option>358                                    <option value="slow">Slow</option>359                                </select>360                            </div>361 362                            <!-- Short side resize cap -->363                            <div class="space-y-1">364                                <label for="short-size" class="text-[8px] font-bold text-slate-400 uppercase tracking-widest">Resize Cap (px)</label>365                                <input type="number" id="short-size" placeholder="Auto-Cap (1024)" class="w-full bg-black/40 border border-white/5 rounded-lg px-2 py-1 text-[9px] focus:border-nvidia-brand focus:outline-none transition-all text-slate-200 font-mono">366                            </div>367 368                            <!-- Temp -->369                            <div class="space-y-1">370                                <div class="flex justify-between text-[8px] uppercase font-bold text-slate-400 tracking-wider">371                                    <span>Temperature</span>372                                    <span id="temp-val" class="font-mono text-nvidia-brand">0.7</span>373                                </div>374                                <input type="range" id="temp" min="0.1" max="2.0" step="0.1" value="0.7" class="w-full h-0.5 bg-black rounded appearance-none cursor-pointer accent-nvidia-brand">375                            </div>376 377                            <!-- Top P -->378                            <div class="space-y-1">379                                <div class="flex justify-between text-[8px] uppercase font-bold text-slate-400 tracking-wider">380                                    <span>Top P</span>381                                    <span id="topp-val" class="font-mono text-nvidia-brand">0.9</span>382                                </div>383                                <input type="range" id="topp" min="0.05" max="1.0" step="0.05" value="0.9" class="w-full h-0.5 bg-black rounded appearance-none cursor-pointer accent-nvidia-brand">384                            </div>385 386                            <!-- Top K -->387                            <div class="space-y-1">388                                <div class="flex justify-between text-[8px] uppercase font-bold text-slate-400 tracking-wider">389                                    <span>Top K</span>390                                    <span id="topk-val" class="font-mono text-nvidia-brand">20</span>391                                </div>392                                <input type="range" id="topk" min="1" max="100" step="1" value="20" class="w-full h-0.5 bg-black rounded appearance-none cursor-pointer accent-nvidia-brand">393                            </div>394 395                            <!-- Video Frames (Only displayed for Video mode) -->396                            <div id="video-frames-wrapper" class="space-y-1 opacity-50 pointer-events-none transition-opacity duration-300">397                                <div class="flex justify-between text-[8px] uppercase font-bold text-slate-400 tracking-wider">398                                    <span>Max Video Frames</span>399                                    <span id="frames-val" class="font-mono text-nvidia-brand">4</span>400                                </div>401                                <input type="range" id="max-frames" min="1" max="10" step="1" value="4" class="w-full h-0.5 bg-black rounded appearance-none cursor-pointer accent-nvidia-brand" disabled>402                            </div>403 404                        </div>405                    </details>406 407                    <!-- Quick Start Guide -->408                    <details class="group border-t border-white/5 pt-3" open>409                        <summary class="list-none flex justify-between items-center cursor-pointer select-none text-[8px] font-bold text-nvidia-brand tracking-wider uppercase hover:text-nvidia-hover transition-colors">410                            <span>๐Ÿ“– How to Use</span>411                            <svg class="h-3 w-3 transform group-open:rotate-180 transition-transform text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">412                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />413                            </svg>414                        </summary>415                        <ol class="space-y-1.5 pt-2.5 text-[9px] text-slate-400 leading-relaxed list-decimal list-inside marker:text-nvidia-brand/70">416                            <li>Upload an <strong class="text-slate-300">Image</strong> or <strong class="text-slate-300">Video</strong>, or pick a Quick Sandbox example below.</li>417                            <li>Choose a <strong class="text-slate-300">Task Type</strong>: Detection ยท Grounding ยท OCR ยท GUI ยท Pointing.</li>418                            <li>Enter <strong class="text-slate-300">Categories</strong> in the search bar (comma-separated, e.g. <code class="text-nvidia-brand/80">car, person</code>).</li>419                            <li>Optionally tune <strong class="text-slate-300">Advanced parameters</strong> above (mode, resize, temperature, etc.).</li>420                            <li>Click <strong class="text-nvidia-brand">Run Inference</strong> or press <kbd class="px-1 py-0.5 rounded bg-white/5 border border-white/10 text-[8px]">Enter</kbd> in the search bar.</li>421                        </ol>422                    </details>423                </div>424 425                <!-- CTA Action Button (Floats at bottom-left corner of visual container) -->426                <div class="pointer-events-auto pt-2 max-w-xs">427                    <button id="run-btn" class="pill-btn-green w-full py-3 px-6 rounded-full text-black font-extrabold text-sm flex items-center justify-center gap-2 select-none shadow-2xl">428                        <span id="btn-icon">๐Ÿง </span>429                        <span id="btn-text">Run Inference</span>430                    </button>431                </div>432 433            </div>434 435            <!-- 3. Floating Categories Search Bar (bottom-center of the dedicated image zone) -->436            <div class="absolute bottom-6 left-0 right-0 lg:left-[440px] z-30 flex flex-col items-center gap-2 px-6 pointer-events-none">437                <div class="sam-input-bar rounded-2xl px-3.5 py-2.5 flex items-center gap-2 w-full max-w-md pointer-events-auto">438                    <svg class="h-4 w-4 text-nvidia-brand shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">439                        <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />440                    </svg>441                    <input type="text" id="categories" value="car, bus, person, potted plant" placeholder="Describe objects to locate..." class="bg-transparent border-none outline-none focus:outline-none w-full text-slate-100 placeholder-slate-600 font-semibold text-xs">442                    <button id="clear-search-btn" class="text-slate-500 hover:text-white transition-colors p-0.5 rounded-full hover:bg-white/5 shrink-0">443                        <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">444                            <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />445                        </svg>446                    </button>447                </div>448                <p class="text-[9px] text-slate-500 text-center leading-relaxed pointer-events-none px-1 max-w-md">449                    Comma-separated targets ยท supports English &amp; Chinese ยท press <span class="text-slate-400">Enter</span> to run450                </p>451            </div>452 453            <!-- Floating Workspace Status (top-right of the image zone) -->454            <div class="absolute top-4 right-4 z-30 bg-black/60 backdrop-blur px-2.5 py-1 rounded-lg border border-white/10 text-[9px] text-slate-400 font-mono select-none pointer-events-none">455                status: <span id="workspace-status" class="text-slate-200 font-semibold">No Media Loaded</span>456            </div>457 458        </div>459 460        <!-- Shelf Section (Examples and Log metrics placed directly below the giant showcase) -->461        <div class="grid grid-cols-1 lg:grid-cols-12 gap-6 items-start">462            463            <!-- Left: Examples Library Shelf (Col Span: 5) -->464            <div class="lg:col-span-5 space-y-4">465                <div class="glass-panel rounded-2xl p-5 space-y-4">466                    <span class="text-[9px] font-bold text-slate-400 uppercase tracking-widest block font-mono">๐Ÿ–ผ๏ธ Interactive Quick Sandbox</span>467                    <div class="grid grid-cols-4 gap-3">468                        469                        <!-- Card 1 -->470                        <div class="example-card border border-white/5 rounded-xl p-1 cursor-pointer group space-y-1 bg-black/35 hover:border-nvidia-brand/20 transition-all text-center" data-type="Image" data-name="Book" data-category="book" data-task="Detection" data-mode="hybrid" data-asset="assets/book.jpg">471                            <div class="h-12 w-full rounded-lg bg-cover bg-center overflow-hidden bg-slate-900" style="background-image: url('/assets/book.jpg');"></div>472                            <span class="text-[9px] font-semibold text-slate-300 block truncate">Book</span>473                        </div>474 475                        <!-- Card 2 -->476                        <div class="example-card border border-white/5 rounded-xl p-1 cursor-pointer group space-y-1 bg-black/35 hover:border-nvidia-brand/20 transition-all text-center" data-type="Image" data-name="Sushi" data-category="sushi" data-task="Detection" data-mode="hybrid" data-asset="assets/sweet.jpg">477                            <div class="h-12 w-full rounded-lg bg-cover bg-center overflow-hidden bg-slate-900" style="background-image: url('/assets/sweet.jpg');"></div>478                            <span class="text-[9px] font-semibold text-slate-300 block truncate">Sushi</span>479                        </div>480 481                        <!-- Card 3 -->482                        <div class="example-card border border-white/5 rounded-xl p-1 cursor-pointer group space-y-1 bg-black/35 hover:border-nvidia-brand/20 transition-all text-center" data-type="Image" data-name="Person" data-category="person" data-task="Detection" data-mode="hybrid" data-asset="assets/person.jpg">483                            <div class="h-12 w-full rounded-lg bg-cover bg-center overflow-hidden bg-slate-900" style="background-image: url('/assets/person.jpg');"></div>484                            <span class="text-[9px] font-semibold text-slate-300 block truncate">People</span>485                        </div>486 487                        <!-- Card 4 -->488                        <div class="example-card border border-white/5 rounded-xl p-1 cursor-pointer group space-y-1 bg-black/35 hover:border-nvidia-brand/20 transition-all text-center" data-type="Image" data-name="OCR" data-category="text" data-task="OCR" data-mode="slow" data-asset="assets/ocr.jpg">489                            <div class="h-12 w-full rounded-lg bg-cover bg-center overflow-hidden bg-slate-900" style="background-image: url('/assets/ocr.jpg');"></div>490                            <span class="text-[9px] font-semibold text-slate-300 block truncate">OCR</span>491                        </div>492 493                    </div>494                </div>495 496                <!-- Text Prompt logs -->497                <div class="glass-panel rounded-2xl p-4 text-[10px] text-slate-500 font-mono flex justify-between items-center select-none bg-black/40">498                    <span class="truncate block">compiled: <span id="raw-prompt-preview" class="text-slate-400"></span></span>499                </div>500            </div>501 502            <!-- Right: Performance Metrics & Tag draw overlays (Col Span: 7) -->503            <div class="lg:col-span-7 space-y-4">504                <div class="glass-panel rounded-2xl p-5 space-y-4">505                    <div class="grid grid-cols-1 sm:grid-cols-12 gap-4 items-start">506                        507                        <!-- Performance Statistics Metrics Console (Grid: 5) -->508                        <div class="sm:col-span-5 bg-black/60 rounded-xl p-4 border border-white/5 font-mono text-[10px] text-slate-300 space-y-2 leading-normal h-[168px]">509                            <div class="text-nvidia-brand font-bold border-b border-white/5 pb-1 mb-1.5 uppercase tracking-widest text-[9px] font-mono">๐Ÿ“Š Metrics Log</div>510                            <div class="flex justify-between"><span class="text-slate-500">Status:</span> <span id="meta-status" class="text-emerald-500 font-semibold">Idle</span></div>511                            <div class="flex justify-between"><span class="text-slate-500">Tokens/Frames:</span> <span id="meta-tokens">-</span></div>512                            <div class="flex justify-between"><span class="text-slate-500">Detections:</span> <span id="meta-boxes">-</span></div>513                            <div class="flex justify-between"><span class="text-slate-500">TPS / BPS:</span> <span><span id="meta-tps">-</span> / <span id="meta-bps">-</span></span></div>514                            <div class="flex justify-between"><span class="text-slate-500">Time:</span> <span id="meta-time">-</span></div>515                        </div>516 517                        <!-- Tag drawer box list (Grid: 7) -->518                        <div class="sm:col-span-7 bg-black/60 rounded-xl p-4 border border-white/5 flex flex-col h-[168px] overflow-hidden">519                            <div class="text-nvidia-brand font-mono font-bold border-b border-white/5 pb-1 mb-2 uppercase tracking-widest text-[9px] flex justify-between shrink-0">520                                <span>๐ŸŽฏ Detected Target Overlays</span>521                                <span id="detection-count-badge" class="text-[8px] bg-nvidia-brand/10 text-nvidia-brand border border-nvidia-brand/20 px-1.5 py-0.5 rounded-full font-bold">0</span>522                            </div>523                            <div id="detection-tags-wrapper" class="detection-scroll flex-1 flex flex-col gap-1.5 pt-1 text-[10px] text-slate-500">524                                <div id="detection-empty-hint" class="space-y-1.5 leading-relaxed">525                                    <p>Run inference to populate detected targets here โ€” each result will pop in one by one.</p>526                                    <p class="text-[9px] text-slate-600">Adjustable: Task Type ยท Categories ยท Inference Mode ยท Resize Cap ยท Temperature ยท Top P/K ยท Max Video Frames</p>527                                </div>528                            </div>529                        </div>530 531                    </div>532                </div>533            </div>534 535        </div>536 537        <!-- Full-width Decoding Trace (always visible, no nested scroll) -->538        <div id="rich-trace-section" class="glass-panel rounded-2xl p-5">539            <div id="rich-trace-log" class="text-[10px]">540                <div class="rounded-xl border border-dashed border-white/10 bg-black/30 p-6 text-center text-[10px] text-slate-500 leading-relaxed">541                    <p class="text-slate-400 font-semibold mb-1">Decoding Trace</p>542                    <p>Run inference to watch model tokens pop in here โ€” ref labels, box coords, and stats shown in full without scrolling sideways.</p>543                </div>544            </div>545        </div>546 547    </main>548 549    <!-- Gradio client connection & app runtime logic -->550    <script type="module">551        import { client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";552 553        // State variables554        let selectedMediaType = "Image";555        let activeFile = null;556        let clientInstance = null;557 558        // Cache elements559        const mediaTypeImageBtn = document.getElementById("media-type-image");560        const mediaTypeVideoBtn = document.getElementById("media-type-video");561        const videoFramesWrapper = document.getElementById("video-frames-wrapper");562        const taskTypeSelect = document.getElementById("task-type");563        const categoriesInput = document.getElementById("categories");564        const clearSearchBtn = document.getElementById("clear-search-btn");565        const inferenceModeSelect = document.getElementById("inference-mode");566        const rawPromptPreview = document.getElementById("raw-prompt-preview");567        568        // Advanced Controls Elements569        const tempSlider = document.getElementById("temp");570        const tempVal = document.getElementById("temp-val");571        const toppSlider = document.getElementById("topp");572        const toppVal = document.getElementById("topp-val");573        const topkSlider = document.getElementById("topk");574        const topkVal = document.getElementById("topk-val");575        const shortSizeInput = document.getElementById("short-size");576        const maxFramesSlider = document.getElementById("max-frames");577        const maxFramesVal = document.getElementById("frames-val");578        579        // Workspace Preview elements580        const dropZone = document.getElementById("drop-zone");581        const uploadPrompt = document.getElementById("upload-prompt");582        const previewImage = document.getElementById("preview-image");583        const previewVideo = document.getElementById("preview-video");584        const fileInput = document.getElementById("media-file-input");585        const workspaceStatus = document.getElementById("workspace-status");586        587        // Output result elements588        const outputEmpty = document.getElementById("output-empty");589        const outputImage = document.getElementById("output-image");590        const outputVideo = document.getElementById("output-video");591        592        // Overlay and run button593        const runBtn = document.getElementById("run-btn");594        const btnText = document.getElementById("btn-text");595        const btnIcon = document.getElementById("btn-icon");596        const processingOverlay = document.getElementById("processing-overlay");597        const processingStatus = document.getElementById("processing-status");598 599        // Logging & Trace elements600        const metaStatus = document.getElementById("meta-status");601        const metaTokens = document.getElementById("meta-tokens");602        const metaBoxes = document.getElementById("meta-boxes");603        const metaTps = document.getElementById("meta-tps");604        const metaBps = document.getElementById("meta-bps");605        const metaTime = document.getElementById("meta-time");606        const detectionTagsWrapper = document.getElementById("detection-tags-wrapper");607        const detectionCountBadge = document.getElementById("detection-count-badge");608        const richTraceLog = document.getElementById("rich-trace-log");609 610        const TRACE_PLACEHOLDER_HTML = `611            <div class="rounded-xl border border-dashed border-white/10 bg-black/30 p-6 text-center text-[10px] text-slate-500 leading-relaxed">612                <p class="text-slate-400 font-semibold mb-1">Decoding Trace</p>613                <p>Run inference to watch model tokens pop in here โ€” ref labels, box coords, and stats shown in full without scrolling sideways.</p>614            </div>`;615 616        function setTracePlaceholder() {617            richTraceLog.innerHTML = TRACE_PLACEHOLDER_HTML;618        }619 620        function setTraceProcessing() {621            richTraceLog.innerHTML = '<p class="text-slate-400 animate-pulse p-4 text-center">Building decoding trace...</p>';622        }623 624        function setTraceHtml(html) {625            richTraceLog.innerHTML = html || TRACE_PLACEHOLDER_HTML;626        }627 628        // Connect client629        async function getClient() {630            if (!clientInstance) {631                try {632                    clientInstance = await client(window.location.origin);633                } catch (e) {634                    console.error("Gradio Server connection failed:", e);635                    alert("Could not connect to Gradio backend. Ensure the server is active.");636                }637            }638            return clientInstance;639        }640 641        // Live values updater642        function setupLiveUpdaters() {643            tempSlider.addEventListener("input", (e) => tempVal.textContent = e.target.value);644            toppSlider.addEventListener("input", (e) => toppVal.textContent = e.target.value);645            topkSlider.addEventListener("input", (e) => topkVal.textContent = e.target.value);646            maxFramesSlider.addEventListener("input", (e) => maxFramesVal.textContent = e.target.value);647 648            // Clear search categories button649            clearSearchBtn.addEventListener("click", () => {650                categoriesInput.value = "";651                categoriesInput.focus();652                triggerPromptUpdate();653            });654 655            // Trigger prompt generation updates656            const triggerPromptUpdate = () => {657                const task = taskTypeSelect.value;658                const cat = categoriesInput.value;659                rawPromptPreview.textContent = generateRawPromptText(task, cat);660            };661            taskTypeSelect.addEventListener("change", () => {662                // OCR defaults to slow (standard AR decoding) for best text accuracy663                if (taskTypeSelect.value === "OCR") {664                    inferenceModeSelect.value = "slow";665                }666                triggerPromptUpdate();667            });668            categoriesInput.addEventListener("input", triggerPromptUpdate);669            670            // Run prompt builder initially671            triggerPromptUpdate();672        }673 674        // Prompt builder mirroring python logic675        function generateRawPromptText(taskType, category) {676            if (!category) category = "objects";677            const cats = category.split(",")678                                 .map(c => c.trim())679                                 .filter(c => c.length > 0)680                                 .join("</c>");681            682            switch (taskType) {683                case "Detection": return `Locate all the instances that matches the following description: ${cats}.`;684                case "Grounding": return `Locate all the instances that match the following description: ${cats}.`;685                case "OCR": return "Detect all the text in box format.";686                case "GUI": return `Locate the region that matches the following description: ${cats}.`;687                case "Pointing": return `Point to: ${cats}.`;688                default: return `Locate all the instances that matches the following description: ${cats}.`;689            }690        }691 692        function formatDetectionCoords(det) {693            const coords = det.coords || [];694            if (!coords.length) return "";695            const rounded = coords.map(c => Number.isFinite(c) ? Math.round(c) : c);696            return rounded.join(", ");697        }698 699        function renderDetectionTags(detections) {700            detectionTagsWrapper.innerHTML = "";701            detectionCountBadge.textContent = "0";702            detectionCountBadge.classList.remove("det-count-pop");703 704            if (!detections.length) {705                detectionTagsWrapper.innerHTML = '<p class="text-slate-500">No objects matched the given categories.</p>';706                return;707            }708 709            // Animate count badge after tags finish popping in710            const countDelay = detections.length * 80 + 120;711            setTimeout(() => {712                detectionCountBadge.textContent = detections.length;713                detectionCountBadge.classList.add("det-count-pop");714            }, countDelay);715 716            detections.forEach((det, idx) => {717                setTimeout(() => {718                    const card = document.createElement("div");719                    card.className = "det-tag-pop flex items-center justify-between gap-2 px-2 py-1.5 rounded-lg bg-nvidia-brand/8 border border-nvidia-brand/20 hover:border-nvidia-brand/40 transition-colors";720                    card.style.animationDelay = "0s";721 722                    const labelWrap = document.createElement("div");723                    labelWrap.className = "flex items-center gap-1.5 min-w-0";724 725                    const typeBadge = document.createElement("span");726                    typeBadge.className = "shrink-0 px-1 py-0.5 rounded text-[7px] font-bold uppercase tracking-wider bg-black/40 text-nvidia-brand border border-nvidia-brand/25";727                    typeBadge.textContent = det.type || "box";728 729                    const label = document.createElement("span");730                    label.className = "font-bold uppercase tracking-wider text-[9px] text-nvidia-brand truncate";731                    label.textContent = det.frame ? `[F${det.frame}] ${det.label}` : (det.label || "object");732 733                    labelWrap.appendChild(typeBadge);734                    labelWrap.appendChild(label);735 736                    const coords = document.createElement("span");737                    coords.className = "shrink-0 font-mono text-[8px] text-slate-500";738                    const coordStr = formatDetectionCoords(det);739                    coords.textContent = coordStr ? `[${coordStr}]` : "";740 741                    card.appendChild(labelWrap);742                    card.appendChild(coords);743                    detectionTagsWrapper.appendChild(card);744                    detectionTagsWrapper.scrollTop = detectionTagsWrapper.scrollHeight;745                }, idx * 80);746            });747        }748 749        function resetDetectionTagsPlaceholder() {750            detectionTagsWrapper.innerHTML = `751                <div id="detection-empty-hint" class="space-y-1.5 leading-relaxed">752                    <p>Run inference to populate detected targets here โ€” each result will pop in one by one.</p>753                    <p class="text-[9px] text-slate-600">Adjustable: Task Type ยท Categories ยท Inference Mode ยท Resize Cap ยท Temperature ยท Top P/K ยท Max Video Frames</p>754                </div>`;755            detectionCountBadge.textContent = "0";756            detectionCountBadge.classList.remove("det-count-pop");757        }758 759        // Switch workspace input styles without clearing760        function setMediaType(type) {761            selectedMediaType = type;762            if (type === "Image") {763                mediaTypeImageBtn.className = "py-1.5 rounded-lg font-semibold text-[10px] transition-all bg-nvidia-brand text-black font-outfit font-black shadow shadow-nvidia-brand/10";764                mediaTypeVideoBtn.className = "py-1.5 rounded-lg font-semibold text-[10px] text-slate-400 hover:text-slate-200 transition-all";765                videoFramesWrapper.classList.add("hidden");766                videoFramesWrapper.classList.add("opacity-50");767                videoFramesWrapper.classList.add("pointer-events-none");768                maxFramesSlider.disabled = true;769                fileInput.accept = "image/*";770                workspaceStatus.textContent = activeFile ? "Image Loaded" : "No Media Loaded";771            } else {772                mediaTypeVideoBtn.className = "py-1.5 rounded-lg font-semibold text-[10px] transition-all bg-nvidia-brand text-black font-outfit font-black shadow shadow-nvidia-brand/10";773                mediaTypeImageBtn.className = "py-1.5 rounded-lg font-semibold text-[10px] text-slate-400 hover:text-slate-200 transition-all";774                videoFramesWrapper.classList.remove("hidden");775                videoFramesWrapper.classList.remove("opacity-50");776                videoFramesWrapper.classList.remove("pointer-events-none");777                maxFramesSlider.disabled = false;778                fileInput.accept = "video/*";779                workspaceStatus.textContent = activeFile ? "Video Loaded" : "No Media Loaded";780            }781        }782 783        // Reset elements784        function clearWorkspace() {785            activeFile = null;786            previewImage.src = "";787            previewImage.classList.add("hidden");788            previewVideo.src = "";789            previewVideo.classList.add("hidden");790            uploadPrompt.classList.remove("hidden");791            if (outputEmpty) outputEmpty.classList.remove("hidden");792            outputImage.src = "";793            outputImage.classList.add("hidden");794            outputVideo.src = "";795            outputVideo.classList.add("hidden");796            workspaceStatus.textContent = "Workspace Cleared";797            resetDetectionTagsPlaceholder();798        }799 800        // Drag and drop utilities801        function setupDragDrop() {802            ['dragenter', 'dragover'].forEach(eventName => {803                dropZone.addEventListener(eventName, (e) => {804                    e.preventDefault();805                    dropZone.classList.add('drop-zone-active');806                }, false);807            });808 809            ['dragleave', 'drop'].forEach(eventName => {810                dropZone.addEventListener(eventName, (e) => {811                    e.preventDefault();812                    dropZone.classList.remove('drop-zone-active');813                }, false);814            });815 816            dropZone.addEventListener('drop', (e) => {817                const dt = e.dataTransfer;818                const file = dt.files[0];819                if (file) handleFileImport(file);820            });821 822            fileInput.addEventListener('change', (e) => {823                const file = e.target.files[0];824                if (file) handleFileImport(file);825            });826        }827 828        // Display imported media829        function handleFileImport(file) {830            uploadPrompt.classList.add("hidden");831 832            if (file.type.startsWith("image/")) {833                setMediaType("Image");834                activeFile = file;835                836                const reader = new FileReader();837                reader.onload = (e) => {838                    previewImage.src = e.target.result;839                    previewImage.classList.remove("hidden");840                    previewVideo.classList.add("hidden");841                };842                reader.readAsDataURL(file);843                workspaceStatus.textContent = `Image Loaded: ${file.name}`;844            } else if (file.type.startsWith("video/")) {845                setMediaType("Video");846                activeFile = file;847 848                previewVideo.src = URL.createObjectURL(file);849                previewVideo.classList.remove("hidden");850                previewImage.classList.add("hidden");851                workspaceStatus.textContent = `Video Loaded: ${file.name}`;852            }853        }854 855        // Initialize preloaded examples click actions856        // Utility to fetch preloaded example assets and convert to File857        async function loadExampleFromAsset(url, filename) {858            try {859                const response = await fetch(url);860                const blob = await response.blob();861                return new File([blob], filename, { type: blob.type });862            } catch (err) {863                console.error("Failed to load example asset:", err);864                return null;865            }866        }867 868        // Initialize preloaded examples click actions869        function setupExamples() {870            document.querySelectorAll(".example-card").forEach(card => {871                card.addEventListener("click", async () => {872                    const type = card.getAttribute("data-type");873                    const name = card.getAttribute("data-name");874                    const category = card.getAttribute("data-category");875                    const task = card.getAttribute("data-task");876                    const mode = card.getAttribute("data-mode");877                    const assetPath = card.getAttribute("data-asset"); // e.g. "assets/book.jpg"878 879                    clearWorkspace();880                    workspaceStatus.textContent = `Loading ${name} example...`;881 882                    // Set parameters883                    taskTypeSelect.value = task;884                    categoriesInput.value = category;885                    inferenceModeSelect.value = mode;886                    887                    // Trigger live prompt update888                    taskTypeSelect.dispatchEvent(new Event("change"));889 890                    // Setup Media type891                    setMediaType(type);892 893                    // Fetch asset file with robust absolute URL resolution (works in iframe)894                    const ext = type === "Image" ? "jpg" : "mp4";895                    const resolvedAssetUrl = new URL(assetPath, window.location.href).href;896                    console.log("Fetching example from:", resolvedAssetUrl);897                    const file = await loadExampleFromAsset(resolvedAssetUrl, `${name.toLowerCase()}.${ext}`);898                    if (file) {899                        activeFile = file;900                        uploadPrompt.classList.add("hidden");901                        if (type === "Image") {902                            previewImage.src = URL.createObjectURL(file);903                            previewImage.classList.remove("hidden");904                            previewVideo.classList.add("hidden");905                            workspaceStatus.textContent = `Example Image Loaded: ${name}`;906                        } else {907                            previewVideo.src = URL.createObjectURL(file);908                            previewVideo.classList.remove("hidden");909                            previewImage.classList.add("hidden");910                            workspaceStatus.textContent = `Example Video Loaded: ${name}`;911                        }912                    } else {913                        workspaceStatus.textContent = `Failed to load ${name} example`;914                    }915                });916            });917        }918 919        // Execution logic920        async function executeInference() {921            if (!activeFile) {922                alert("Please upload a media file (Image or Video) or select an example first.");923                return;924            }925 926            // Set loading state927            runBtn.disabled = true;928            btnText.textContent = "โณ Queueing Request...";929            btnIcon.textContent = "๐Ÿ”’";930            processingOverlay.classList.remove("hidden");931            processingStatus.textContent = "Waiting for Gradio queue...";932            933            // Clean outputs934            if (outputEmpty) outputEmpty.classList.add("hidden");935            outputImage.classList.add("hidden");936            outputVideo.classList.add("hidden");937            setTraceProcessing();938            metaStatus.textContent = "Processing...";939            metaStatus.className = "text-yellow-500 font-semibold";940            detectionTagsWrapper.innerHTML = '<p class="text-slate-400 animate-pulse">Processing objects in backend...</p>';941            detectionCountBadge.textContent = "0";942            detectionCountBadge.classList.remove("det-count-pop");943 944            try {945                const clientInstance = await getClient();946                if (!clientInstance) {947                    throw new Error("Unable to create Gradio Client instance.");948                }949 950                // Handle file parameter wrapping using Gradio client handle_file951                const wrappedFile = activeFile ? handle_file(activeFile) : null;952                const imageFile = (selectedMediaType === "Image") ? wrappedFile : null;953                const videoFile = (selectedMediaType === "Video") ? wrappedFile : null;954                955                // Collect configuration values956                const taskType = taskTypeSelect.value;957                const category = categoriesInput.value;958                const modelMode = inferenceModeSelect.value;959                const temp = parseFloat(tempSlider.value);960                const topp = parseFloat(toppSlider.value);961                const topk = parseInt(topkSlider.value);962                const shortSize = shortSizeInput.value ? parseInt(shortSizeInput.value) : null;963                const maxVideoFrames = parseInt(maxFramesSlider.value);964 965                processingStatus.textContent = "Running Vision Model (duration-locked)...";966 967                // Execute predictions using named parameters object matching app.py signature968                const result = await clientInstance.predict("/run_inference", {969                    input_type: selectedMediaType,970                    image_file: imageFile,971                    video_file: videoFile,972                    task_type: taskType,973                    category: category,974                    model_mode: modelMode,975                    temp: temp,976                    top_p: topp,977                    top_k: topk,978                    short_size: shortSize,979                    question_override: null,980                    max_video_frames: maxVideoFrames981                });982 983                console.log("Inference complete. API outputs:", result);984 985                // Unpack result values986                const [outImageObj, outVideoObj, meta] = result.data;987 988                if (!meta.success) {989                    throw new Error(meta.error || "Backend returned processing failure.");990                }991 992                // Process image result993                if (selectedMediaType === "Image" && outImageObj) {994                    outputImage.src = outImageObj.url;995                    outputImage.classList.remove("hidden");996                    outputVideo.classList.add("hidden");997                } 998                // Process video result999                else if (selectedMediaType === "Video" && outVideoObj) {1000                    outputVideo.src = outVideoObj.url;1001                    outputVideo.classList.remove("hidden");1002                    outputImage.classList.add("hidden");1003                }1004 1005                // Render metrics logs1006                metaStatus.textContent = "Success";1007                metaStatus.className = "text-emerald-500 font-semibold";1008                1009                const stats = meta.stats || {};1010                metaTokens.textContent = stats.num_tokens || stats.total_frames || "-";1011                metaBoxes.textContent = stats.num_boxes || stats.processed_frames || "-";1012                metaTps.textContent = stats.tps || "-";1013                metaBps.textContent = stats.bps || "-";1014                metaTime.textContent = stats.total_time_seconds ? `${stats.total_time_seconds}s` : "Optimal";1015 1016                // Render detection tags with staggered pop-in animation1017                renderDetectionTags(meta.detections || []);1018 1019                // Render decoding trace (token-by-token pop animation from previous version)1020                setTraceHtml(meta.html);1021 1022            } catch (err) {1023                console.error("Execution failed:", err);1024                metaStatus.textContent = "Error";1025                metaStatus.className = "text-red-500 font-semibold";1026                detectionTagsWrapper.innerHTML = `<span class="text-red-400">Failed: ${err.message}</span>`;1027                setTracePlaceholder();1028                alert(`Inference failed: ${err.message}`);1029                if (outputEmpty) outputEmpty.classList.remove("hidden");1030            } finally {1031                // Restore UI state1032                runBtn.disabled = false;1033                btnText.textContent = "Run Inference";1034                btnIcon.textContent = "๐Ÿง ";1035                processingOverlay.classList.add("hidden");1036            }1037        }1038 1039        // Add event listeners on load1040        document.addEventListener("DOMContentLoaded", () => {1041            mediaTypeImageBtn.addEventListener("click", () => {1042                if (selectedMediaType !== "Image") {1043                    setMediaType("Image");1044                    clearWorkspace();1045                }1046            });1047            mediaTypeVideoBtn.addEventListener("click", () => {1048                if (selectedMediaType !== "Video") {1049                    setMediaType("Video");1050                    clearWorkspace();1051                }1052            });1053            runBtn.addEventListener("click", executeInference);1054 1055            // Bind enter key press in Categories float bar input1056            categoriesInput.addEventListener("keydown", (e) => {1057                if (e.key === "Enter") {1058                    e.preventDefault();1059                    executeInference();1060                }1061            });1062 1063            setupLiveUpdaters();1064            setupDragDrop();1065            setupExamples();1066        });1067    </script>1068</body>1069</html>1070