| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| <title>Synapse LiveEdit · Realtime</title> |
| <style> |
| :root { --bg:#0e0f13; --panel:#171922; --line:#2a2e3a; --fg:#e8e8ee; --accent:#c084fc; --good:#86efac; } |
| * { box-sizing: border-box; } |
| body { margin:0; background:var(--bg); color:var(--fg); font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif; } |
| .wrap { max-width:1100px; margin:0 auto; padding:24px 18px 60px; } |
| h1 { font-size:1.5rem; margin:0 0 4px; } |
| .sub { color:#9aa0ad; font-size:.95rem; margin:0 0 18px; line-height:1.5; } |
| .sub a { color:var(--accent); } |
| .controls { display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin-bottom:16px; } |
| input[type=text] { flex:1; min-width:260px; background:var(--panel); border:1px solid var(--line); color:var(--fg); padding:12px 14px; border-radius:10px; font-size:1rem; } |
| button { background:var(--accent); color:#1a1024; border:0; padding:12px 18px; border-radius:10px; font-size:1rem; font-weight:600; cursor:pointer; } |
| button:disabled { opacity:.5; cursor:not-allowed; } |
| .timer { font-family:ui-monospace,Menlo,monospace; color:var(--good); background:#11210f; border:1px solid #234; padding:8px 12px; border-radius:8px; display:none; } |
| .grid { display:grid; grid-template-columns:1fr 1fr; gap:14px; } |
| .card { background:var(--panel); border:1px solid var(--line); border-radius:14px; overflow:hidden; } |
| .card h2 { font-size:.85rem; text-transform:uppercase; letter-spacing:.05em; color:#9aa0ad; margin:0; padding:10px 14px; border-bottom:1px solid var(--line); } |
| .media { aspect-ratio:832/480; background:#000; display:flex; align-items:center; justify-content:center; } |
| .media video, .media img { width:100%; height:100%; object-fit:cover; display:block; } |
| .placeholder { color:#5a6070; font-size:.9rem; } |
| .status { margin-top:14px; color:#9aa0ad; font-size:.9rem; min-height:1.2em; } |
| @media (max-width:780px){ .grid{ grid-template-columns:1fr; } } |
| </style> |
| </head> |
| <body> |
| <div class="wrap"> |
| <h1>🌀 Synapse Vision V2 · Realtime Webcam Diffusion</h1> |
| <p class="sub"> |
| Live demo of <a href="https://streamdiffusionv2.github.io/" target="_blank">SynapseDiffusionV2</a> |
| (Synapse AI Technology) on Synapse Vision 1.3B. It streams your webcam through a causal video-diffusion |
| model with a <b>sink-token rolling KV cache</b> — built for <i>continuous</i> streaming, so it |
| keeps flowing without the window-shift burst. Type a style prompt, click <b>Start</b> to grab SynapseGPU |
| for ~60s. <b>Change the prompt anytime</b> — it updates the live stream. |
| </p> |
|
|
| <div class="controls"> |
|
|
| <select id="promptSelect"> |
| <option value="">🎨 Choose a style preset...</option> |
| <option value="A cinematic cyberpunk city at night, neon lights reflecting on wet streets, futuristic atmosphere, ultra detailed, realistic"> |
| 🌃 Cyberpunk City |
| </option> |
| <option value="Transform the webcam into a dark futuristic AI world, glowing purple and blue energy, cybernetic effects, high detail, cinematic lighting"> |
| 🔮 Dark AI World |
| </option> |
| <option value="Turn the webcam view into a Studio Ghibli inspired animated world, soft colors, magical atmosphere, detailed environment"> |
| 🌿 Animated Fantasy |
| </option> |
| <option value="Make the webcam look like a futuristic hologram transmission, digital particles, sci-fi interface overlays, glowing blue energy"> |
| 🌀 Hologram Transmission |
| </option> |
| <option value="Transform the webcam into a realistic fantasy world, ancient ruins, magical lighting, cinematic fantasy style"> |
| 🏰 Fantasy World |
| </option> |
| <option value="Create a glitch art version of the webcam feed, RGB distortion, digital artifacts, cyberpunk aesthetic, futuristic AI effect"> |
| ⚡ Glitch Art |
| </option> |
| <option value="Make the webcam look like a professional movie scene, dramatic lighting, shallow depth of field, cinematic color grading"> |
| 🎬 Cinematic Movie |
| </option> |
| <option value="Cyberpunk futuristic city, neon lighting, cinematic AI transformation, realistic details, glowing blue and purple atmosphere"> |
| 💜 Synapse Cyberpunk |
| </option> |
| </select> |
|
|
| <input |
| id="instruction" |
| type="text" |
| placeholder="style prompt · e.g. psychedelic neon dream · van gogh · cyberpunk city" |
| /> |
|
|
| <button id="startBtn">▶ Start session</button> |
|
|
| <span id="timer" class="timer"> |
| ⏱ <span id="count">58</span>s |
| </span> |
|
|
| </div> |
|
|
| <div class="grid"> |
| <div class="card"> |
| <h2>Your webcam</h2> |
| <div class="media"><video id="cam" autoplay muted playsinline></video></div> |
| </div> |
| <div class="card"> |
| <h2>Edited (live)</h2> |
| <div class="media"><img id="out" alt="" /><span id="outPh" class="placeholder">edited stream appears here</span></div> |
| </div> |
| </div> |
|
|
| <div id="status" class="status"></div> |
| </div> |
|
|
| <canvas id="grab" width="640" height="360" style="display:none"></canvas> |
| <script> |
| const promptSelect = document.getElementById("promptSelect"); |
| const instructionInput = document.getElementById("instruction"); |
| |
| promptSelect.addEventListener("change", function () { |
| instructionInput.value = this.value; |
| }); |
| </script> |
| <script type="module"> |
| import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; |
| |
| const camEl = document.getElementById("cam"); |
| const outEl = document.getElementById("out"); |
| const outPh = document.getElementById("outPh"); |
| const startBtn = document.getElementById("startBtn"); |
| const instr = document.getElementById("instruction"); |
| const timerEl = document.getElementById("timer"); |
| const countEl = document.getElementById("count"); |
| const statusEl = document.getElementById("status"); |
| const grab = document.getElementById("grab"); |
| const gctx = grab.getContext("2d"); |
| |
| let client = null; |
| let stream = null; |
| let captureTimer = null; |
| let countdownTimer = null; |
| let running = false; |
| |
| const FPS = 30; |
| const SESSION_SECONDS = 58; |
| |
| |
| |
| |
| let playQueue = []; |
| const MAX_QUEUE = 3; |
| let lastShown = 0; |
| function playLoop(ts){ |
| if (playQueue.length > MAX_QUEUE) playQueue = playQueue.slice(-MAX_QUEUE); |
| if (playQueue.length && ts - lastShown >= 1000 / 30){ |
| outEl.src = playQueue.shift(); |
| outPh.style.display = "none"; |
| lastShown = ts; |
| } |
| requestAnimationFrame(playLoop); |
| } |
| requestAnimationFrame(playLoop); |
| |
| function setStatus(t){ statusEl.textContent = t; } |
| |
| async function ensureClient(){ |
| if (!client) client = await Client.connect(window.location.origin); |
| return client; |
| } |
| |
| async function ensureCam(){ |
| if (stream) return; |
| stream = await navigator.mediaDevices.getUserMedia({ video: { width: 832, height: 480 }, audio: false }); |
| camEl.srcObject = stream; |
| await camEl.play().catch(()=>{}); |
| } |
| |
| async function sendInstruction(){ |
| try { |
| await fetch("/instruction", { |
| method:"POST", headers:{ "Content-Type":"application/json" }, |
| body: JSON.stringify({ instruction: instr.value || "" }) |
| }); |
| } catch(e){} |
| } |
| |
| function startCapture(){ |
| captureTimer = setInterval(() => { |
| if (!camEl.videoWidth) return; |
| gctx.drawImage(camEl, 0, 0, grab.width, grab.height); |
| grab.toBlob(async (blob) => { |
| if (!blob) return; |
| try { await fetch("/frame", { method:"POST", body: blob }); } catch(e){} |
| }, "image/jpeg", 0.6); |
| }, 1000 / FPS); |
| } |
| |
| function stopAll(){ |
| running = false; |
| if (captureTimer) { clearInterval(captureTimer); captureTimer = null; } |
| if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; } |
| timerEl.style.display = "none"; |
| startBtn.disabled = false; |
| startBtn.textContent = "▶ Start session"; |
| } |
| |
| function startCountdown(){ |
| let r = SESSION_SECONDS; |
| countEl.textContent = r; |
| timerEl.style.display = "inline-block"; |
| countdownTimer = setInterval(() => { |
| r -= 1; countEl.textContent = Math.max(0, r); |
| if (r <= 0) clearInterval(countdownTimer); |
| }, 1000); |
| } |
| |
| instr.addEventListener("change", () => { if (running) sendInstruction(); }); |
| instr.addEventListener("input", () => { if (running) sendInstruction(); }); |
| |
| startBtn.addEventListener("click", async () => { |
| if (running) return; |
| startBtn.disabled = true; |
| try { |
| setStatus("Requesting webcam…"); |
| await ensureCam(); |
| setStatus("Connecting…"); |
| await ensureClient(); |
| running = true; |
| playQueue = []; |
| startBtn.textContent = "◌ Acquiring ZeroGPU…"; |
| await sendInstruction(); |
| setStatus("Queued for ZeroGPU — webcam streaming starts once the GPU is acquired…"); |
| |
| const job = client.submit("/run_session", {}); |
| let frames = 0; |
| for await (const msg of job) { |
| if (msg.type !== "data" || !msg.data || msg.data[0] == null) continue; |
| const payload = msg.data[0]; |
| if (payload === "__READY__") { |
| |
| startBtn.textContent = "● Live"; |
| await sendInstruction(); |
| startCapture(); |
| startCountdown(); |
| setStatus("ZeroGPU acquired — streaming your webcam through LiveEdit…"); |
| continue; |
| } |
| playQueue.push(payload); |
| frames += 1; |
| if (frames % 12 === 0) setStatus(`Streaming… ${frames} edited frames`); |
| } |
| setStatus(frames ? "Session ended. Click Start to run another ~60s session." |
| : "Session ended before any frames were produced — try again."); |
| } catch (e) { |
| setStatus("Error: " + (e && e.message ? e.message : e)); |
| } finally { |
| stopAll(); |
| } |
| }); |
| </script> |
| </body> |
| </html> |
|
|