Spaces:
Runtime error
Runtime error
| /** | |
| * ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| * β WIZARD-VIBE STUDIO β Client Runtime β | |
| * β SSE Streaming Β· Sandbox iframe Β· Publish Gate β | |
| * ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| */ | |
| const S = { sessionId: null, sandboxStatus: 'idle', fullCode: '', errorsFound: 0, errorsFixed: 0, publishData: null }; | |
| const D = { | |
| prompt: document.getElementById('prompt-input'), vibeBtn: document.getElementById('vibe-btn'), | |
| codeContent: document.getElementById('code-content'), previewFrame: document.getElementById('preview-frame'), | |
| previewOverlay: document.getElementById('preview-overlay'), statusDot: document.getElementById('status-dot'), | |
| statusText: document.getElementById('status-text'), statusPhase: document.getElementById('status-phase'), | |
| sandboxBadge: document.getElementById('sandbox-badge'), publishBtn: document.getElementById('publish-btn'), | |
| publishGate: document.getElementById('publish-gate'), gateIcon: document.querySelector('.gate-icon'), | |
| gateText: document.querySelector('.gate-text'), publishResult: document.getElementById('publish-result'), | |
| publishUrl: document.getElementById('publish-url'), githubLink: document.getElementById('github-link'), | |
| spacesLink: document.getElementById('spaces-link'), agentLink: document.getElementById('agent-link'), | |
| modelBadges: document.getElementById('model-badges'), wizardHat: document.getElementById('wizard-hat'), | |
| statusRing: document.getElementById('status-ring'), copyBtn: document.getElementById('copy-btn'), | |
| }; | |
| // βββ SSE STREAMING β ASYNC ITERATOR CLIENT βββ | |
| async function* streamFromServer(prompt) { | |
| const resp = await fetch('/api/stream', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({prompt}) }); | |
| if (!resp.ok) throw new Error(`Stream error: ${resp.status}`); | |
| const reader = resp.body.getReader(), dec = new TextDecoder(); | |
| let buf = ''; | |
| while (true) { | |
| const {done, value} = await reader.read(); | |
| if (done) break; | |
| buf += dec.decode(value, {stream:true}); | |
| const lines = buf.split('\n'); buf = lines.pop() || ''; | |
| for (const line of lines) { | |
| if (line.startsWith('data: ')) { | |
| const d = line.slice(6); | |
| if (!d || d==='{}') continue; | |
| try { yield JSON.parse(d); } catch { yield {raw:d}; } | |
| } | |
| } | |
| } | |
| } | |
| // βββ STATE MACHINE β WIZARD HAT MORPHING βββ | |
| function setSandboxStatus(st) { | |
| S.sandboxStatus = st; | |
| D.wizardHat.classList.remove('stable','error','published','building'); | |
| D.statusRing.classList.remove('stable','error','published'); | |
| D.statusDot.classList.remove('building','streaming','stable','error','published'); | |
| D.publishGate.classList.remove('unlocked'); | |
| const map = { | |
| building: {hat:'building', dot:'building', badge:'π¨', btn:true, gate:[false,'π','Building...']}, | |
| streaming: {hat:'', dot:'streaming',badge:'π‘', btn:true, gate:[false,'π','Streaming...']}, | |
| stable: {hat:'stable', dot:'stable', badge:'β ', btn:false,gate:[true,'π','Sandbox stable β Ready']}, | |
| error: {hat:'error', dot:'error', badge:'β', btn:true, gate:[false,'π','Sandbox errors']}, | |
| published: {hat:'published',dot:'published',badge:'π',btn:false,gate:[true,'π','Published to A2A']}, | |
| }; | |
| const m = map[st] || map.building; | |
| if (m.hat) D.wizardHat.classList.add(m.hat); | |
| if (st==='stable'||st==='published') D.statusRing.classList.add(st); | |
| D.statusDot.classList.add(m.dot); | |
| D.sandboxBadge.textContent = m.badge; | |
| D.publishBtn.disabled = m.btn; | |
| if (m.gate) { | |
| if (m.gate[0]) D.publishGate.classList.add('unlocked'); | |
| D.gateIcon.textContent = m.gate[1]; | |
| D.gateText.textContent = m.gate[2]; | |
| } | |
| if (st==='published') D.publishBtn.classList.add('gold'); | |
| else D.publishBtn.classList.remove('gold'); | |
| D.statusText.textContent = st.charAt(0).toUpperCase()+st.slice(1); | |
| } | |
| // βββ IFRAME HOT-RELOAD βββ | |
| function updateIframe(code) { | |
| const blob = new Blob([code], {type:'text/html'}); | |
| const url = URL.createObjectURL(blob); | |
| if (D.previewFrame._blobUrl) URL.revokeObjectURL(D.previewFrame._blobUrl); | |
| D.previewFrame._blobUrl = url; | |
| D.previewFrame.src = url; | |
| D.previewOverlay.classList.add('hidden'); | |
| } | |
| function appendCode(chunk) { | |
| if (D.codeContent.querySelector('.placeholder')) D.codeContent.innerHTML = ''; | |
| const span = document.createElement('span'); span.className='code-chunk-new'; span.textContent=chunk; | |
| D.codeContent.appendChild(span); | |
| D.codeContent.parentElement.scrollTop = D.codeContent.parentElement.scrollHeight; | |
| } | |
| function updateModelBadges(plan) { | |
| D.modelBadges.innerHTML = ''; | |
| if (!plan?.models) return; | |
| plan.models.forEach(m => { | |
| const b = document.createElement('span'); | |
| b.className = `model-badge ${m.task_type}`; | |
| b.textContent = m.model_id.split('/').pop(); | |
| b.title = `${m.model_id} (${Math.round(m.confidence*100)}%)`; | |
| D.modelBadges.appendChild(b); | |
| }); | |
| } | |
| // βββ MAIN GENERATION βββ | |
| async function generate() { | |
| const prompt = D.prompt.value.trim(); if (!prompt) return; | |
| S.fullCode = ''; S.errorsFound = 0; S.errorsFixed = 0; S.publishData = null; | |
| D.codeContent.innerHTML = '<span class="placeholder">Streaming...</span>'; | |
| D.previewOverlay.classList.remove('hidden'); | |
| D.publishResult.style.display = 'none'; | |
| D.publishBtn.classList.remove('gold'); D.publishBtn.disabled = true; | |
| D.modelBadges.innerHTML = ''; | |
| D.vibeBtn.disabled = true; D.vibeBtn.querySelector('.btn-text').textContent = 'Generating...'; | |
| setSandboxStatus('building'); | |
| D.statusPhase.textContent = 'Connecting...'; | |
| try { | |
| for await (const ev of streamFromServer(prompt)) { | |
| if (ev.phase) D.statusPhase.textContent = ev.phase; | |
| if (ev.plan) updateModelBadges(ev.plan); | |
| if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; } | |
| if (ev.partial) updateIframe(ev.partial); | |
| if (ev.heal) { S.errorsFound = ev.errors_found; S.errorsFixed = ev.errors_fixed; } | |
| if (ev.sandbox) setSandboxStatus(ev.status); | |
| } | |
| } catch (err) { | |
| console.error('Stream error:', err); | |
| setSandboxStatus('error'); | |
| D.statusText.textContent = `Error: ${err.message}`; | |
| } finally { | |
| D.vibeBtn.disabled = false; D.vibeBtn.querySelector('.btn-text').textContent = 'Generate'; | |
| D.statusText.textContent = 'Done'; | |
| } | |
| } | |
| // βββ PUBLISH βββ | |
| async function publish() { | |
| const repoName = prompt('Repo name:', `wizard-vibe-${Date.now().toString(36)}`); | |
| if (!repoName) return; | |
| D.publishBtn.disabled = true; D.publishBtn.querySelector('.btn-text').textContent = 'Publishing...'; | |
| try { | |
| const resp = await fetch('/api/publish', { | |
| method:'POST', headers:{'Content-Type':'application/json'}, | |
| body:JSON.stringify({repo_name:repoName, description:D.prompt.value.trim().slice(0,200)}), | |
| }); | |
| const r = await resp.json(); | |
| if (r.success) { | |
| S.publishData = r; setSandboxStatus('published'); | |
| D.publishResult.style.display = 'block'; | |
| D.publishUrl.textContent = r.spaces?.url || r.github?.repo_url || 'Published'; | |
| if (r.github?.repo_url) { D.githubLink.href = r.github.repo_url; D.githubLink.style.display = 'inline'; } | |
| if (r.spaces?.url) { D.spacesLink.href = r.spaces.url; D.spacesLink.style.display = 'inline'; } | |
| if (r.agent_url) { D.agentLink.href = r.agent_url; D.agentLink.style.display = 'inline'; } | |
| D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'}); | |
| } else { D.statusText.textContent = `Publish error: ${r.error}`; } | |
| } catch (err) { | |
| D.statusText.textContent = `Publish error: ${err.message}`; | |
| } finally { | |
| D.publishBtn.disabled = false; D.publishBtn.querySelector('.btn-text').textContent = 'Publish to A2A Network'; | |
| } | |
| } | |
| // βββ EVENT BINDINGS βββ | |
| D.vibeBtn.addEventListener('click', generate); | |
| D.publishBtn.addEventListener('click', publish); | |
| D.prompt.addEventListener('keydown', e => { if ((e.ctrlKey||e.metaKey) && e.key==='Enter') { e.preventDefault(); generate(); } }); | |
| D.copyBtn.addEventListener('click', async () => { | |
| if (!S.fullCode) return; | |
| try { await navigator.clipboard.writeText(S.fullCode); } catch {} | |
| }); | |
| console.log('π§ββοΈ Wizard-Vibe Studio β Liquid Glass Client ready'); | |