| |
| |
| |
| |
| |
| |
|
|
| 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'), |
| }; |
|
|
| |
|
|
| 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}; } |
| } |
| } |
| } |
| } |
|
|
| |
|
|
| 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); |
| } |
|
|
| |
|
|
| 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); |
| }); |
| } |
|
|
| |
|
|
| 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'; |
| } |
| } |
|
|
| |
|
|
| 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'; |
| } |
| } |
|
|
| |
|
|
| 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'); |
|
|