dryymatt commited on
Commit
de6cc14
Β·
verified Β·
1 Parent(s): 902f43a

πŸ§™β€β™‚οΈ First Commit: static/wizard.js

Browse files
Files changed (1) hide show
  1. static/wizard.js +177 -0
static/wizard.js ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * ╔══════════════════════════════════════════════════════╗
3
+ * β•‘ WIZARD-VIBE STUDIO β€” Client Runtime β•‘
4
+ * β•‘ SSE Streaming Β· Sandbox iframe Β· Publish Gate β•‘
5
+ * β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
6
+ */
7
+
8
+ const S = { sessionId: null, sandboxStatus: 'idle', fullCode: '', errorsFound: 0, errorsFixed: 0, publishData: null };
9
+ const D = {
10
+ prompt: document.getElementById('prompt-input'), vibeBtn: document.getElementById('vibe-btn'),
11
+ codeContent: document.getElementById('code-content'), previewFrame: document.getElementById('preview-frame'),
12
+ previewOverlay: document.getElementById('preview-overlay'), statusDot: document.getElementById('status-dot'),
13
+ statusText: document.getElementById('status-text'), statusPhase: document.getElementById('status-phase'),
14
+ sandboxBadge: document.getElementById('sandbox-badge'), publishBtn: document.getElementById('publish-btn'),
15
+ publishGate: document.getElementById('publish-gate'), gateIcon: document.querySelector('.gate-icon'),
16
+ gateText: document.querySelector('.gate-text'), publishResult: document.getElementById('publish-result'),
17
+ publishUrl: document.getElementById('publish-url'), githubLink: document.getElementById('github-link'),
18
+ spacesLink: document.getElementById('spaces-link'), agentLink: document.getElementById('agent-link'),
19
+ modelBadges: document.getElementById('model-badges'), wizardHat: document.getElementById('wizard-hat'),
20
+ statusRing: document.getElementById('status-ring'), copyBtn: document.getElementById('copy-btn'),
21
+ };
22
+
23
+ // ═══ SSE STREAMING β€” ASYNC ITERATOR CLIENT ═══
24
+
25
+ async function* streamFromServer(prompt) {
26
+ const resp = await fetch('/api/stream', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({prompt}) });
27
+ if (!resp.ok) throw new Error(`Stream error: ${resp.status}`);
28
+ const reader = resp.body.getReader(), dec = new TextDecoder();
29
+ let buf = '';
30
+ while (true) {
31
+ const {done, value} = await reader.read();
32
+ if (done) break;
33
+ buf += dec.decode(value, {stream:true});
34
+ const lines = buf.split('\n'); buf = lines.pop() || '';
35
+ for (const line of lines) {
36
+ if (line.startsWith('data: ')) {
37
+ const d = line.slice(6);
38
+ if (!d || d==='{}') continue;
39
+ try { yield JSON.parse(d); } catch { yield {raw:d}; }
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ // ═══ STATE MACHINE β€” WIZARD HAT MORPHING ═══
46
+
47
+ function setSandboxStatus(st) {
48
+ S.sandboxStatus = st;
49
+ D.wizardHat.classList.remove('stable','error','published','building');
50
+ D.statusRing.classList.remove('stable','error','published');
51
+ D.statusDot.classList.remove('building','streaming','stable','error','published');
52
+ D.publishGate.classList.remove('unlocked');
53
+ const map = {
54
+ building: {hat:'building', dot:'building', badge:'πŸ”¨', btn:true, gate:[false,'πŸ”’','Building...']},
55
+ streaming: {hat:'', dot:'streaming',badge:'πŸ“‘', btn:true, gate:[false,'πŸ”’','Streaming...']},
56
+ stable: {hat:'stable', dot:'stable', badge:'βœ…', btn:false,gate:[true,'πŸ”“','Sandbox stable β€” Ready']},
57
+ error: {hat:'error', dot:'error', badge:'❌', btn:true, gate:[false,'πŸ”’','Sandbox errors']},
58
+ published: {hat:'published',dot:'published',badge:'🌟',btn:false,gate:[true,'🌟','Published to A2A']},
59
+ };
60
+ const m = map[st] || map.building;
61
+ if (m.hat) D.wizardHat.classList.add(m.hat);
62
+ if (st==='stable'||st==='published') D.statusRing.classList.add(st);
63
+ D.statusDot.classList.add(m.dot);
64
+ D.sandboxBadge.textContent = m.badge;
65
+ D.publishBtn.disabled = m.btn;
66
+ if (m.gate) {
67
+ if (m.gate[0]) D.publishGate.classList.add('unlocked');
68
+ D.gateIcon.textContent = m.gate[1];
69
+ D.gateText.textContent = m.gate[2];
70
+ }
71
+ if (st==='published') D.publishBtn.classList.add('gold');
72
+ else D.publishBtn.classList.remove('gold');
73
+ D.statusText.textContent = st.charAt(0).toUpperCase()+st.slice(1);
74
+ }
75
+
76
+ // ═══ IFRAME HOT-RELOAD ═══
77
+
78
+ function updateIframe(code) {
79
+ const blob = new Blob([code], {type:'text/html'});
80
+ const url = URL.createObjectURL(blob);
81
+ if (D.previewFrame._blobUrl) URL.revokeObjectURL(D.previewFrame._blobUrl);
82
+ D.previewFrame._blobUrl = url;
83
+ D.previewFrame.src = url;
84
+ D.previewOverlay.classList.add('hidden');
85
+ }
86
+
87
+ function appendCode(chunk) {
88
+ if (D.codeContent.querySelector('.placeholder')) D.codeContent.innerHTML = '';
89
+ const span = document.createElement('span'); span.className='code-chunk-new'; span.textContent=chunk;
90
+ D.codeContent.appendChild(span);
91
+ D.codeContent.parentElement.scrollTop = D.codeContent.parentElement.scrollHeight;
92
+ }
93
+
94
+ function updateModelBadges(plan) {
95
+ D.modelBadges.innerHTML = '';
96
+ if (!plan?.models) return;
97
+ plan.models.forEach(m => {
98
+ const b = document.createElement('span');
99
+ b.className = `model-badge ${m.task_type}`;
100
+ b.textContent = m.model_id.split('/').pop();
101
+ b.title = `${m.model_id} (${Math.round(m.confidence*100)}%)`;
102
+ D.modelBadges.appendChild(b);
103
+ });
104
+ }
105
+
106
+ // ═══ MAIN GENERATION ═══
107
+
108
+ async function generate() {
109
+ const prompt = D.prompt.value.trim(); if (!prompt) return;
110
+ S.fullCode = ''; S.errorsFound = 0; S.errorsFixed = 0; S.publishData = null;
111
+ D.codeContent.innerHTML = '<span class="placeholder">Streaming...</span>';
112
+ D.previewOverlay.classList.remove('hidden');
113
+ D.publishResult.style.display = 'none';
114
+ D.publishBtn.classList.remove('gold'); D.publishBtn.disabled = true;
115
+ D.modelBadges.innerHTML = '';
116
+ D.vibeBtn.disabled = true; D.vibeBtn.querySelector('.btn-text').textContent = 'Generating...';
117
+ setSandboxStatus('building');
118
+ D.statusPhase.textContent = 'Connecting...';
119
+
120
+ try {
121
+ for await (const ev of streamFromServer(prompt)) {
122
+ if (ev.phase) D.statusPhase.textContent = ev.phase;
123
+ if (ev.plan) updateModelBadges(ev.plan);
124
+ if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; }
125
+ if (ev.partial) updateIframe(ev.partial);
126
+ if (ev.heal) { S.errorsFound = ev.errors_found; S.errorsFixed = ev.errors_fixed; }
127
+ if (ev.sandbox) setSandboxStatus(ev.status);
128
+ }
129
+ } catch (err) {
130
+ console.error('Stream error:', err);
131
+ setSandboxStatus('error');
132
+ D.statusText.textContent = `Error: ${err.message}`;
133
+ } finally {
134
+ D.vibeBtn.disabled = false; D.vibeBtn.querySelector('.btn-text').textContent = 'Generate';
135
+ D.statusText.textContent = 'Done';
136
+ }
137
+ }
138
+
139
+ // ═══ PUBLISH ═══
140
+
141
+ async function publish() {
142
+ const repoName = prompt('Repo name:', `wizard-vibe-${Date.now().toString(36)}`);
143
+ if (!repoName) return;
144
+ D.publishBtn.disabled = true; D.publishBtn.querySelector('.btn-text').textContent = 'Publishing...';
145
+ try {
146
+ const resp = await fetch('/api/publish', {
147
+ method:'POST', headers:{'Content-Type':'application/json'},
148
+ body:JSON.stringify({repo_name:repoName, description:D.prompt.value.trim().slice(0,200)}),
149
+ });
150
+ const r = await resp.json();
151
+ if (r.success) {
152
+ S.publishData = r; setSandboxStatus('published');
153
+ D.publishResult.style.display = 'block';
154
+ D.publishUrl.textContent = r.spaces?.url || r.github?.repo_url || 'Published';
155
+ if (r.github?.repo_url) { D.githubLink.href = r.github.repo_url; D.githubLink.style.display = 'inline'; }
156
+ if (r.spaces?.url) { D.spacesLink.href = r.spaces.url; D.spacesLink.style.display = 'inline'; }
157
+ if (r.agent_url) { D.agentLink.href = r.agent_url; D.agentLink.style.display = 'inline'; }
158
+ D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'});
159
+ } else { D.statusText.textContent = `Publish error: ${r.error}`; }
160
+ } catch (err) {
161
+ D.statusText.textContent = `Publish error: ${err.message}`;
162
+ } finally {
163
+ D.publishBtn.disabled = false; D.publishBtn.querySelector('.btn-text').textContent = 'Publish to A2A Network';
164
+ }
165
+ }
166
+
167
+ // ═══ EVENT BINDINGS ═══
168
+
169
+ D.vibeBtn.addEventListener('click', generate);
170
+ D.publishBtn.addEventListener('click', publish);
171
+ D.prompt.addEventListener('keydown', e => { if ((e.ctrlKey||e.metaKey) && e.key==='Enter') { e.preventDefault(); generate(); } });
172
+ D.copyBtn.addEventListener('click', async () => {
173
+ if (!S.fullCode) return;
174
+ try { await navigator.clipboard.writeText(S.fullCode); } catch {}
175
+ });
176
+
177
+ console.log('πŸ§™β€β™‚οΈ Wizard-Vibe Studio β€” Liquid Glass Client ready');