| <script> |
| import { onMount, onDestroy } from 'svelte'; |
|
|
| const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; |
|
|
| const scenarios = [ |
| { |
| command: 'hf download openai/gpt-oss-20b', |
| phase1: { |
| spinning: 'Preparing download...', |
| children: [{ text: 'Resolved main @ a1b2c3d', isLast: true }], |
| }, |
| phase2: { |
| ready: 'Downloaded 14 files (13.5 GB)', |
| children: [ |
| { text: 'config.json', isLast: false }, |
| { text: 'tokenizer.json', isLast: false }, |
| { text: 'model.safetensors', isLast: false }, |
| { |
| text: 'Cached to ~/.cache/huggingface/hub', |
| isLast: true, |
| }, |
| ], |
| }, |
| }, |
| { |
| command: 'hf upload username/mini-gpt .', |
| phase1: { |
| spinning: 'Preparing commit...', |
| children: [ |
| { text: '8 files hashed', isLast: false }, |
| { text: 'Repo created on the Hub', isLast: true }, |
| ], |
| }, |
| phase2: { |
| ready: 'Uploaded to username/mini-gpt', |
| children: [ |
| { text: 'README.md', isLast: false }, |
| { text: 'model.safetensors (142 MB)', isLast: false }, |
| { text: 'Commit c3f4d1e pushed to main', isLast: true }, |
| ], |
| }, |
| }, |
| { |
| command: |
| 'hf jobs run --flavor a10g-small python:3.12 python infer.py', |
| phase1: { |
| spinning: 'Provisioning a10g-small...', |
| children: [ |
| { text: 'GPU allocated', isLast: false }, |
| { text: 'Image pulled', isLast: true }, |
| ], |
| }, |
| phase2: { |
| ready: 'Job complete', |
| children: [ |
| { text: 'Loaded weights in 4.2s', isLast: false }, |
| { text: 'Generated 128 tokens', isLast: false }, |
| { text: 'Logs at ~/.hf/jobs/job_9f2k3.log', isLast: true }, |
| ], |
| }, |
| }, |
| { |
| command: |
| 'hf repos create username/diffuser-demo --type space --space-sdk gradio', |
| phase1: { |
| spinning: 'Creating Space...', |
| children: [ |
| { text: 'Initialized Gradio template', isLast: false }, |
| { text: 'CI build triggered', isLast: true }, |
| ], |
| }, |
| phase2: { |
| ready: 'Space live', |
| children: [ |
| { |
| text: 'https://huggingface.co/spaces/username/diffuser-demo', |
| isLast: true, |
| }, |
| ], |
| }, |
| }, |
| ]; |
|
|
| let spinnerFrame = $state(0); |
| let lines = $state([]); |
| let running = true; |
|
|
| const wait = (ms) => new Promise((r) => setTimeout(r, ms)); |
|
|
| async function typeInto(idx, text, speed = 28) { |
| for (let i = 1; i <= text.length; i++) { |
| if (!running) return; |
| lines[idx].typed = text.slice(0, i); |
| await wait(speed); |
| } |
| } |
|
|
| function pushCommand(text) { |
| lines.push({ kind: 'command', text, typed: '', showCaret: true }); |
| return lines.length - 1; |
| } |
|
|
| function pushPhase(variant, text) { |
| lines.push({ kind: 'phase', variant, text }); |
| return lines.length - 1; |
| } |
|
|
| function pushChild(text, isLast) { |
| lines.push({ kind: 'child', text, isLast }); |
| } |
|
|
| async function playScenario(scenario) { |
| lines = []; |
| await wait(500); |
| if (!running) return; |
|
|
| const c = pushCommand(scenario.command); |
| await typeInto(c, scenario.command); |
| lines[c].showCaret = false; |
| await wait(400); |
|
|
| const p1 = pushPhase('spinning', scenario.phase1.spinning); |
| await wait(750); |
| for (const child of scenario.phase1.children) { |
| pushChild(child.text, child.isLast); |
| await wait(500); |
| } |
| lines[p1].variant = 'done-hollow'; |
| await wait(300); |
|
|
| pushPhase('ready', scenario.phase2.ready); |
| await wait(460); |
| for (const child of scenario.phase2.children) { |
| pushChild(child.text, child.isLast); |
| await wait(460); |
| } |
|
|
| await wait(3200); |
| } |
|
|
| async function run() { |
| while (running) { |
| for (const scenario of scenarios) { |
| if (!running) return; |
| await playScenario(scenario); |
| } |
| } |
| } |
|
|
| let spinnerInterval; |
|
|
| onMount(() => { |
| spinnerInterval = setInterval(() => { |
| spinnerFrame = (spinnerFrame + 1) % spinnerFrames.length; |
| }, 90); |
| run(); |
| }); |
|
|
| onDestroy(() => { |
| running = false; |
| clearInterval(spinnerInterval); |
| }); |
|
|
| function splitChildText(text) { |
| const pattern = /(https?:\/\/\S+|~\/[^\s]+)/g; |
| const parts = []; |
| let last = 0; |
| let m; |
| while ((m = pattern.exec(text)) !== null) { |
| if (m.index > last) |
| parts.push({ text: text.slice(last, m.index), accent: false }); |
| parts.push({ text: m[0], accent: true }); |
| last = m.index + m[0].length; |
| } |
| if (last < text.length) |
| parts.push({ text: text.slice(last), accent: false }); |
| return parts; |
| } |
| </script> |
|
|
| <div |
| class="frame-bg frame-shadow w-[760px] max-w-[calc(100vw-48px)] rounded-[20px] p-[3px]" |
| > |
| <div |
| class="w-full min-h-[540px] bg-[#fbfbf9] rounded-[17px] overflow-hidden font-mono text-[18px] leading-[1.85] text-[#232323]" |
| > |
| <div class="flex gap-2 pt-4 px-[18px] pb-[6px]"> |
| <span class="w-3 h-3 rounded-full bg-[#d7d7d3]"></span> |
| <span class="w-3 h-3 rounded-full bg-[#d7d7d3]"></span> |
| <span class="w-3 h-3 rounded-full bg-[#d7d7d3]"></span> |
| </div> |
| |
| <div class="pt-[18px] px-7 pb-7 min-h-[460px]" aria-live="polite"> |
| { |
| <div |
| class="flex items-baseline gap-2.5 whitespace-pre-wrap break-words animate-fade-in {line.kind === |
| 'child' |
| ? 'pl-[2.2ch]' |
| : ''}" |
| > |
| {#if line.kind === 'command'} |
| <span class="inline-block w-[1ch] text-[ |
| <span class="text-[ |
| >{line.typed}{ |
| class="inline-block w-[0.55ch] ml-px text-[ |
| aria-hidden="true">▎</span |
| >{/if}</span |
| > |
| {:else if line.kind === 'phase'} |
| {#if line.variant === 'spinning'} |
| <span class="inline-block w-[1ch] text-center text-[#5f5f5c]" |
| >{spinnerFrames[spinnerFrame]}</span |
| > |
| <span class="text-[#333331]">{line.text}</span> |
| {:else if line.variant === 'done-hollow'} |
| <span class="inline-block w-[1ch] text-center text-[#6b6b68]" |
| >○</span |
| > |
| <span class="text-[#333331]">{line.text}</span> |
| {:else if line.variant === 'ready'} |
| <span |
| class="inline-block w-[1ch] text-center text-[#0f7a3a] animate-ready-pulse" |
| >●</span |
| > |
| <span class="text-[#0f5a2a] font-semibold">{line.text}</span> |
| {/if} |
| {:else if line.kind === 'child'} |
| <span class="inline-block w-[1ch] text-[#b3b3ad]" |
| >{line.isLast ? '└' : '├'}</span |
| > |
| <span class="inline-block w-[1ch] text-[#17a34a]">✓</span> |
| <span class="text-[#474744]" |
| >{#each splitChildText(line.text) as part}{#if part.accent}<span |
| class="text-[#8b5cf6]">{part.text}</span |
| >{:else}{part.text}{/if}{/each}</span |
| > |
| {/if} |
| </div> |
| {/each} |
| </div> |
| </div> |
| </div> |
|
|