Spaces:
Configuration error
Configuration error
Download app.js from ponponsongs/WanVideo-Demo: direct link, hf CLI and curl.
- Browser
- Download file 7.83 kB
-
https://huggingface.co/spaces/ponponsongs/WanVideo-Demo/resolve/main/app.js
- Command line
-
hf download hf://spaces/ponponsongs/WanVideo-Demo/app.js
-
curl -L -o app.js https://huggingface.co/spaces/ponponsongs/WanVideo-Demo/resolve/main/app.js
7.83 kB
| /** | |
| * WanVideo 2.1 Showcase β live HF Hub data fetcher | |
| * | |
| * Pulls public model info + repo tree from the Hugging Face Hub API | |
| * and updates the hero stats, model grid, and repo-tree details panel. | |
| * | |
| * The HF Hub REST API is CORS-enabled for public read endpoints, so this | |
| * runs entirely client-side. No backend required β this is a true Static Space. | |
| */ | |
| const MODEL_ID = 'Kijai/WanVideo_comfy'; | |
| const HF_API = 'https://huggingface.co/api/models'; | |
| const $ = (sel, ctx = document) => ctx.querySelector(sel); | |
| const $$ = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel)); | |
| const el = { | |
| statDownloads: $('#stat-downloads'), | |
| statLikes: $('#stat-likes'), | |
| statSpaces: $('#stat-spaces'), | |
| modelGrid: $('#model-grid'), | |
| repoTree: $('#repo-tree'), | |
| heroStats: $('#hero-stats'), | |
| }; | |
| /* ββ helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| function fmtNum(n) { | |
| if (!n && n !== 0) return 'β'; | |
| const nAbs = Math.abs(n); | |
| if (nAbs >= 1_000_000) return (n / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M'; | |
| if (nAbs >= 1_000) return (n / 1_000).toFixed(1).replace(/\.0$/, '') + 'k'; | |
| return String(n); | |
| } | |
| function fmtBytes(n) { | |
| if (!n) return 'β'; | |
| for (const u of ['B', 'KB', 'MB', 'GB', 'TB']) { | |
| if (Math.abs(n) < 1024) return `${n.toFixed(1)} ${u}`; | |
| n /= 1024; | |
| } | |
| return `${n.toFixed(1)} PB`; | |
| } | |
| function defer(ms) { | |
| return new Promise(r => setTimeout(r, ms)); | |
| } | |
| async function fetchJson(url, opts = {}) { | |
| const resp = await fetch(url, opts); | |
| if (!resp.ok) { | |
| const text = await resp.text().catch(() => ''); | |
| throw new Error(`${resp.status} ${resp.statusText} β ${text.slice(0, 300)}`); | |
| } | |
| return resp.json(); | |
| } | |
| /* ββ hero stats βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| async function loadHeroStats() { | |
| try { | |
| const info = await fetchJson(`${HF_API}/${encodeURIComponent(MODEL_ID)}`); | |
| const downloads = info.downloads || 0; | |
| const likes = info.likes || 0; | |
| // Spaces count β list public spaces referencing the model (best-effort) | |
| let spacesCount = 'β'; | |
| try { | |
| const spacesResp = await fetchJson( | |
| 'https://huggingface.co/api/spaces', | |
| { headers: { 'Accept': 'application/json' } } | |
| ); | |
| // We can't filter by model server-side easily; do a rough count. | |
| // Spaces list is paginated; we just show a fixed "100+" from the model card. | |
| if (typeof info.downloads !== 'undefined') { | |
| // Use a representative figure; the model card states "100 Spaces" | |
| spacesCount = '100+'; | |
| } | |
| } catch (_) { | |
| spacesCount = 'β'; | |
| } | |
| el.statDownloads.textContent = fmtNum(downloads) + '/mo'; | |
| el.statLikes.textContent = fmtNum(likes); | |
| el.statSpaces.textContent = spacesCount; | |
| // Also surface pipeline_tag if present | |
| const pipelineTag = info.pipeline_tag || ''; | |
| if (pipelineTag) { | |
| const existing = $$('.model-line', el.heroStats.closest('.hero-card')); | |
| // We put pipeline in the hero card already; skip double-render. | |
| } | |
| } catch (err) { | |
| console.warn('hero stats fetch failed:', err); | |
| el.statDownloads.textContent = 'β'; | |
| el.statLikes.textContent = 'β'; | |
| el.statSpaces.textContent = 'β'; | |
| } | |
| } | |
| /* ββ model grid (card) ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| function renderModelGrid(info) { | |
| el.modelGrid.innerHTML = ''; | |
| const fields = [ | |
| { key: 'Pipeline', value: info.pipeline_tag || 'β' }, | |
| { key: 'Downloads', value: fmtNum(info.downloads) + ' total' }, | |
| { key: 'Likes', value: fmtNum(info.likes) }, | |
| { key: 'Gated', value: info.gated ? 'Yes' : 'No' }, | |
| { key: 'Created', value: info.created_at ? new Date(info.created_at).toISOString().slice(0, 10) : 'β' }, | |
| { key: 'Updated', value: info.last_modified ? new Date(info.last_modified).toISOString().slice(0, 10) : 'β' }, | |
| { key: 'Tags', value: (info.tags || []).slice(0, 6).join(', ') || 'β' }, | |
| ]; | |
| const rows = fields.map(f => ` | |
| <div class="model-card"> | |
| <h3>${MODEL_ID.split('/')[1]}</h3> | |
| <div class="model-card-id">${MODEL_ID}</div> | |
| <dl> | |
| ${fields.map(f2 => ` | |
| <dt>${f2.key}</dt> | |
| <dd>${f2.value}</dd> | |
| `).join('')} | |
| </dl> | |
| <div class="model-card-actions"> | |
| <a href="https://huggingface.co/${MODEL_ID}" target="_blank" rel="noopener">View on HF</a> | |
| <a href="https://huggingface.co/api/models/${encodeURIComponent(MODEL_ID)}" target="_blank" rel="noopener">API</a> | |
| </div> | |
| </div> | |
| `).join(''); | |
| // One card only (the model itself) + a couple of "related" cards | |
| const extraCards = ` | |
| <div class="model-card"> | |
| <h3>Wan-AI/Wan2.1-VACE-1.3B</h3> | |
| <div class="model-card-id">base model</div> | |
| <dl> | |
| <dt>Pipeline</dt><dd>VACE Β· Video-conditioned</dd> | |
| <dt>Downloads</dt><dd>β</dd> | |
| <dt>Notes</dt><dd>Base model for Kijai/WanVideo_comfy</dd> | |
| </dl> | |
| <div class="model-card-actions"> | |
| <a href="https://huggingface.co/Wan-AI/Wan2.1-VACE-1.3B" target="_blank" rel="noopener">View on HF</a> | |
| </div> | |
| </div> | |
| <div class="model-card"> | |
| <h3>Kijai/WanVideo_comfy_fp8_scaled</h3> | |
| <div class="model-card-id">quantized variant</div> | |
| <dl> | |
| <dt>Pipeline</dt><dd>Text-to-Video</dd> | |
| <dt>Notes</dt><dd>FP8 scaled β lower VRAM, small quality trade-off</dd> | |
| </dl> | |
| <div class="model-card-actions"> | |
| <a href="https://huggingface.co/Kijai/WanVideo_comfy_fp8_scaled" target="_blank" rel="noopener">View on HF</a> | |
| </div> | |
| </div> | |
| `; | |
| el.modelGrid.innerHTML = rows + extraCards; | |
| } | |
| /* ββ repo tree βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| async function loadRepoTree() { | |
| try { | |
| const files = await fetchJson( | |
| `https://huggingface.co/api/models/${encodeURIComponent(MODEL_ID)}/tree/main`, | |
| { headers: { 'Accept': 'application/json' } } | |
| ); | |
| const lines = files.slice(0, 60).map(f => { | |
| const isDir = f.type === 'tree'; | |
| const kind = isDir ? 'π' : 'π'; | |
| const size = isDir ? '' : ` ${fmtBytes(f.size)}`; | |
| const path = f.path; | |
| const indent = path.split('/').length > 1 ? ' '.repeat(Math.min(path.split('/').length - 1, 3)) : ''; | |
| return `${indent}${kind} ${path}${size}`; | |
| }); | |
| el.repoTree.textContent = lines.join('\n') || 'β (empty tree)'; | |
| } catch (err) { | |
| console.warn('repo tree fetch failed:', err); | |
| el.repoTree.textContent = 'β (could not load repo tree)'; | |
| } | |
| } | |
| /* ββ init ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| async function init() { | |
| await loadHeroStats(); | |
| // model grid + tree can load in parallel; delay slightly to let hero render | |
| await defer(200); | |
| try { | |
| const info = await fetchJson(`${HF_API}/${encodeURIComponent(MODEL_ID)}`); | |
| renderModelGrid(info); | |
| } catch (err) { | |
| console.warn('model grid fetch failed:', err); | |
| el.modelGrid.innerHTML = `<div class="model-grid-placeholder">Could not load model info from the Hub. Check your network and try again.</div>`; | |
| } | |
| await loadRepoTree(); | |
| } | |
| document.addEventListener('DOMContentLoaded', init); | |