Upload static/wizard.js
Browse files- static/wizard.js +173 -107
static/wizard.js
CHANGED
|
@@ -1,50 +1,46 @@
|
|
| 1 |
/**
|
| 2 |
* ╔══════════════════════════════════════════════════════╗
|
| 3 |
* ║ OMNI-VIBE STUDIO — Client Runtime ║
|
| 4 |
-
* ║
|
| 5 |
* ╚══════════════════════════════════════════════════════╝
|
| 6 |
*/
|
| 7 |
|
| 8 |
-
const S = {
|
|
|
|
|
|
|
|
|
|
| 9 |
const D = {
|
| 10 |
prompt: document.getElementById('prompt-input'),
|
| 11 |
vibeBtn: document.getElementById('vibe-btn'),
|
| 12 |
codeContent: document.getElementById('code-content'),
|
|
|
|
| 13 |
previewFrame: document.getElementById('preview-frame'),
|
| 14 |
previewOverlay: document.getElementById('preview-overlay'),
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
publishBtn: document.getElementById('publish-btn'),
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
publishResult: document.getElementById('publish-result'),
|
| 24 |
-
|
| 25 |
-
githubLink: document.getElementById('github-link'),
|
| 26 |
-
spacesLink: document.getElementById('spaces-link'),
|
| 27 |
-
agentLink: document.getElementById('agent-link'),
|
| 28 |
-
modelBadges: document.getElementById('model-badges'),
|
| 29 |
wizardHat: document.getElementById('wizard-hat'),
|
| 30 |
-
|
|
|
|
|
|
|
| 31 |
copyBtn: document.getElementById('copy-btn'),
|
| 32 |
-
previewTabBtn: document.getElementById('preview-tab-btn'),
|
| 33 |
-
panelForge: document.getElementById('panel-forge'),
|
| 34 |
-
panelPreview: document.getElementById('panel-preview'),
|
| 35 |
};
|
| 36 |
|
| 37 |
/* ═══ TAB SWITCHING ═══ */
|
| 38 |
document.querySelectorAll('.tab-btn').forEach(btn => {
|
| 39 |
btn.addEventListener('click', () => {
|
| 40 |
if (btn.disabled) return;
|
| 41 |
-
// Deactivate all
|
| 42 |
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
| 43 |
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
| 44 |
-
// Activate clicked
|
| 45 |
btn.classList.add('active');
|
| 46 |
-
|
| 47 |
-
document.getElementById(panelId).classList.add('active');
|
| 48 |
});
|
| 49 |
});
|
| 50 |
|
|
@@ -57,12 +53,31 @@ function unlockPreview() {
|
|
| 57 |
D.previewTabBtn.querySelector('.tab-icon').textContent = '👁️';
|
| 58 |
}
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
D.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
|
| 68 |
/* ═══ SSE STREAMING ═══ */
|
|
@@ -90,37 +105,7 @@ async function* streamFromServer(prompt) {
|
|
| 90 |
}
|
| 91 |
}
|
| 92 |
|
| 93 |
-
/* ═══
|
| 94 |
-
function setSandboxStatus(st) {
|
| 95 |
-
S.sandboxStatus = st;
|
| 96 |
-
D.wizardHat.classList.remove('stable','error','published','building');
|
| 97 |
-
D.statusRing.classList.remove('stable','error','published');
|
| 98 |
-
D.statusDot.classList.remove('building','streaming','stable','error','published');
|
| 99 |
-
D.publishGate.classList.remove('unlocked');
|
| 100 |
-
const map = {
|
| 101 |
-
building: {hat:'building', dot:'building', badge:'🔨', btn:true, gate:[false,'🔒','Building...']},
|
| 102 |
-
streaming: {hat:'', dot:'streaming',badge:'📡', btn:true, gate:[false,'🔒','Streaming...']},
|
| 103 |
-
stable: {hat:'stable', dot:'stable', badge:'✅', btn:false,gate:[true,'🔓','Sandbox stable — Ready']},
|
| 104 |
-
error: {hat:'error', dot:'error', badge:'❌', btn:true, gate:[false,'🔒','Sandbox errors']},
|
| 105 |
-
published: {hat:'published',dot:'published',badge:'🌟',btn:false,gate:[true,'🌟','Published to A2A']},
|
| 106 |
-
};
|
| 107 |
-
const m = map[st] || map.building;
|
| 108 |
-
if (m.hat) D.wizardHat.classList.add(m.hat);
|
| 109 |
-
if (st==='stable'||st==='published') D.statusRing.classList.add(st);
|
| 110 |
-
D.statusDot.classList.add(m.dot);
|
| 111 |
-
D.sandboxBadge.textContent = m.badge;
|
| 112 |
-
D.publishBtn.disabled = m.btn;
|
| 113 |
-
if (m.gate) {
|
| 114 |
-
if (m.gate[0]) D.publishGate.classList.add('unlocked');
|
| 115 |
-
D.gateIcon.textContent = m.gate[1];
|
| 116 |
-
D.gateText.textContent = m.gate[2];
|
| 117 |
-
}
|
| 118 |
-
if (st==='published') D.publishBtn.classList.add('gold');
|
| 119 |
-
else D.publishBtn.classList.remove('gold');
|
| 120 |
-
D.statusText.textContent = st.charAt(0).toUpperCase()+st.slice(1);
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
/* ═══ IFRAME HOT-RELOAD ═══ */
|
| 124 |
function updateIframe(code) {
|
| 125 |
const blob = new Blob([code], {type:'text/html'});
|
| 126 |
const url = URL.createObjectURL(blob);
|
|
@@ -136,84 +121,150 @@ function appendCode(chunk) {
|
|
| 136 |
span.className='code-chunk-new'; span.textContent=chunk;
|
| 137 |
D.codeContent.appendChild(span);
|
| 138 |
D.codeContent.parentElement.scrollTop = D.codeContent.parentElement.scrollHeight;
|
|
|
|
| 139 |
}
|
| 140 |
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
}
|
| 152 |
|
| 153 |
/* ═══ MAIN GENERATION ═══ */
|
| 154 |
async function generate() {
|
| 155 |
const prompt = D.prompt.value.trim(); if (!prompt) return;
|
| 156 |
-
S.fullCode = ''; S.
|
| 157 |
-
S.publishData = null; S.previewUnlocked = false;
|
| 158 |
|
| 159 |
-
// Reset
|
| 160 |
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
| 161 |
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
| 162 |
document.querySelector('.tab-btn[data-tab="forge"]').classList.add('active');
|
| 163 |
D.panelForge.classList.add('active');
|
| 164 |
-
|
| 165 |
-
// Lock Preview tab
|
| 166 |
D.previewTabBtn.disabled = true;
|
| 167 |
D.previewTabBtn.classList.add('locked');
|
| 168 |
D.previewTabBtn.classList.remove('unlocked');
|
| 169 |
D.previewTabBtn.querySelector('.tab-icon').textContent = '🔒';
|
| 170 |
|
| 171 |
-
D.codeContent.innerHTML = '<span class="placeholder">Streaming
|
|
|
|
| 172 |
D.previewOverlay.classList.remove('hidden');
|
| 173 |
D.previewFrame.src = 'about:blank';
|
| 174 |
D.publishResult.style.display = 'none';
|
| 175 |
-
|
| 176 |
-
D.modelBadges.innerHTML = '';
|
| 177 |
D.vibeBtn.disabled = true;
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
| 181 |
|
| 182 |
try {
|
| 183 |
for await (const ev of streamFromServer(prompt)) {
|
| 184 |
-
|
| 185 |
-
if (ev.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; }
|
| 187 |
if (ev.partial) updateIframe(ev.partial);
|
| 188 |
-
|
|
|
|
| 189 |
if (ev.sandbox) {
|
| 190 |
-
|
| 191 |
-
// Unlock Preview tab when stable or published
|
| 192 |
if (ev.status === 'stable' || ev.status === 'published') {
|
| 193 |
unlockPreview();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
}
|
| 195 |
}
|
| 196 |
}
|
| 197 |
} catch (err) {
|
| 198 |
console.error('Stream error:', err);
|
| 199 |
-
|
| 200 |
-
|
| 201 |
} finally {
|
| 202 |
D.vibeBtn.disabled = false;
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
// Always unlock preview after generation, even on errors
|
| 206 |
-
// (so user can see partial/error output)
|
| 207 |
-
if (S.fullCode) unlockPreview();
|
| 208 |
}
|
| 209 |
}
|
| 210 |
|
| 211 |
-
/* ═══ PUBLISH ═══ */
|
| 212 |
async function publish() {
|
| 213 |
-
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
D.publishBtn.disabled = true;
|
| 216 |
-
D.publishBtn.
|
|
|
|
|
|
|
| 217 |
try {
|
| 218 |
const resp = await fetch('/api/publish', {
|
| 219 |
method:'POST',
|
|
@@ -221,20 +272,29 @@ async function publish() {
|
|
| 221 |
body:JSON.stringify({repo_name:repoName, description:D.prompt.value.trim().slice(0,200)}),
|
| 222 |
});
|
| 223 |
const r = await resp.json();
|
|
|
|
| 224 |
if (r.success) {
|
| 225 |
-
S.publishData = r;
|
|
|
|
|
|
|
|
|
|
| 226 |
D.publishResult.style.display = 'block';
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'});
|
| 232 |
-
} else {
|
|
|
|
|
|
|
|
|
|
| 233 |
} catch (err) {
|
| 234 |
-
|
|
|
|
| 235 |
} finally {
|
| 236 |
-
D.publishBtn.disabled =
|
| 237 |
-
D.publishBtn.
|
|
|
|
| 238 |
}
|
| 239 |
}
|
| 240 |
|
|
@@ -242,11 +302,17 @@ async function publish() {
|
|
| 242 |
D.vibeBtn.addEventListener('click', generate);
|
| 243 |
D.publishBtn.addEventListener('click', publish);
|
| 244 |
D.prompt.addEventListener('keydown', e => {
|
| 245 |
-
if (
|
| 246 |
});
|
| 247 |
D.copyBtn.addEventListener('click', async () => {
|
| 248 |
if (!S.fullCode) return;
|
| 249 |
try { await navigator.clipboard.writeText(S.fullCode); } catch {}
|
| 250 |
});
|
| 251 |
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
* ╔══════════════════════════════════════════════════════╗
|
| 3 |
* ║ OMNI-VIBE STUDIO — Client Runtime ║
|
| 4 |
+
* ║ Fluid Thinking · Wizard Pulse · E2E Handshake ║
|
| 5 |
* ╚══════════════════════════════════════════════════════╝
|
| 6 |
*/
|
| 7 |
|
| 8 |
+
const S = {
|
| 9 |
+
sessionId: null, fullCode: '', previewUnlocked: false,
|
| 10 |
+
publishReady: false, publishData: null, handshakeVerified: false,
|
| 11 |
+
};
|
| 12 |
const D = {
|
| 13 |
prompt: document.getElementById('prompt-input'),
|
| 14 |
vibeBtn: document.getElementById('vibe-btn'),
|
| 15 |
codeContent: document.getElementById('code-content'),
|
| 16 |
+
charCount: document.getElementById('char-count'),
|
| 17 |
previewFrame: document.getElementById('preview-frame'),
|
| 18 |
previewOverlay: document.getElementById('preview-overlay'),
|
| 19 |
+
previewBadge: document.getElementById('preview-badge'),
|
| 20 |
+
previewTabBtn: document.getElementById('preview-tab-btn'),
|
| 21 |
+
panelForge: document.getElementById('panel-forge'),
|
| 22 |
+
panelPreview: document.getElementById('panel-preview'),
|
| 23 |
publishBtn: document.getElementById('publish-btn'),
|
| 24 |
+
publishBar: document.getElementById('publish-bar'),
|
| 25 |
+
pubIcon: document.getElementById('pub-icon'),
|
| 26 |
+
pubText: document.getElementById('pub-text'),
|
| 27 |
publishResult: document.getElementById('publish-result'),
|
| 28 |
+
liveLink: document.getElementById('live-link'),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
wizardHat: document.getElementById('wizard-hat'),
|
| 30 |
+
thoughtBar: document.getElementById('thought-bar'),
|
| 31 |
+
thoughtText: document.getElementById('thought-text'),
|
| 32 |
+
thoughtModels: document.getElementById('thought-models'),
|
| 33 |
copyBtn: document.getElementById('copy-btn'),
|
|
|
|
|
|
|
|
|
|
| 34 |
};
|
| 35 |
|
| 36 |
/* ═══ TAB SWITCHING ═══ */
|
| 37 |
document.querySelectorAll('.tab-btn').forEach(btn => {
|
| 38 |
btn.addEventListener('click', () => {
|
| 39 |
if (btn.disabled) return;
|
|
|
|
| 40 |
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
| 41 |
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
|
|
|
| 42 |
btn.classList.add('active');
|
| 43 |
+
document.getElementById('panel-' + btn.dataset.tab).classList.add('active');
|
|
|
|
| 44 |
});
|
| 45 |
});
|
| 46 |
|
|
|
|
| 53 |
D.previewTabBtn.querySelector('.tab-icon').textContent = '👁️';
|
| 54 |
}
|
| 55 |
|
| 56 |
+
/* ═══ WIZARD PULSE — high-velocity, 600ms ═══ */
|
| 57 |
+
function pulseWizard() {
|
| 58 |
+
D.wizardHat.classList.add('pulse');
|
| 59 |
+
}
|
| 60 |
+
function stopWizard(state) {
|
| 61 |
+
D.wizardHat.classList.remove('pulse');
|
| 62 |
+
D.wizardHat.classList.remove('stable','error','published');
|
| 63 |
+
if (state) D.wizardHat.classList.add(state);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
/* ═══ SWARM THOUGHT PROCESS — single line ═══ */
|
| 67 |
+
function showThought() {
|
| 68 |
+
D.thoughtBar.style.display = 'flex';
|
| 69 |
+
requestAnimationFrame(() => D.thoughtBar.classList.add('visible'));
|
| 70 |
+
}
|
| 71 |
+
function updateThought(text, models) {
|
| 72 |
+
D.thoughtText.textContent = text;
|
| 73 |
+
if (models?.length) {
|
| 74 |
+
D.thoughtModels.innerHTML = models.map(m =>
|
| 75 |
+
`<span>${m.model_id.split('/').pop()}</span>`
|
| 76 |
+
).join('');
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
function hideThought() {
|
| 80 |
+
D.thoughtBar.classList.remove('visible');
|
| 81 |
}
|
| 82 |
|
| 83 |
/* ═══ SSE STREAMING ═══ */
|
|
|
|
| 105 |
}
|
| 106 |
}
|
| 107 |
|
| 108 |
+
/* ═══ IFRAME ═══ */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
function updateIframe(code) {
|
| 110 |
const blob = new Blob([code], {type:'text/html'});
|
| 111 |
const url = URL.createObjectURL(blob);
|
|
|
|
| 121 |
span.className='code-chunk-new'; span.textContent=chunk;
|
| 122 |
D.codeContent.appendChild(span);
|
| 123 |
D.codeContent.parentElement.scrollTop = D.codeContent.parentElement.scrollHeight;
|
| 124 |
+
D.charCount.textContent = `${S.fullCode.length.toLocaleString()} chars`;
|
| 125 |
}
|
| 126 |
|
| 127 |
+
/* ═══ END-TO-END HANDSHAKE VERIFICATION ═══ */
|
| 128 |
+
async function verifyHandshake(url) {
|
| 129 |
+
try {
|
| 130 |
+
const resp = await fetch('/api/verify-handshake', {
|
| 131 |
+
method:'POST',
|
| 132 |
+
headers:{'Content-Type':'application/json'},
|
| 133 |
+
body:JSON.stringify({url}),
|
| 134 |
+
});
|
| 135 |
+
const data = await resp.json();
|
| 136 |
+
S.handshakeVerified = data.verified;
|
| 137 |
+
return data;
|
| 138 |
+
} catch {
|
| 139 |
+
S.handshakeVerified = false;
|
| 140 |
+
return {verified:false, error:'Handshake unreachable'};
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
/* ═══ PUBLISH GATE ═══ */
|
| 145 |
+
function setPublishState(ready, text, icon) {
|
| 146 |
+
S.publishReady = ready;
|
| 147 |
+
D.publishBtn.disabled = !ready;
|
| 148 |
+
if (ready) {
|
| 149 |
+
D.publishBar.classList.add('unlocked');
|
| 150 |
+
D.pubIcon.textContent = icon || '🔓';
|
| 151 |
+
D.pubText.textContent = text || 'Ready to publish';
|
| 152 |
+
} else {
|
| 153 |
+
D.publishBar.classList.remove('unlocked');
|
| 154 |
+
D.pubIcon.textContent = icon || '🔒';
|
| 155 |
+
D.pubText.textContent = text || 'Validate to unlock publishing';
|
| 156 |
+
}
|
| 157 |
}
|
| 158 |
|
| 159 |
/* ═══ MAIN GENERATION ═══ */
|
| 160 |
async function generate() {
|
| 161 |
const prompt = D.prompt.value.trim(); if (!prompt) return;
|
| 162 |
+
S.fullCode = ''; S.previewUnlocked = false; S.publishReady = false; S.handshakeVerified = false;
|
|
|
|
| 163 |
|
| 164 |
+
// Reset
|
| 165 |
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
| 166 |
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
| 167 |
document.querySelector('.tab-btn[data-tab="forge"]').classList.add('active');
|
| 168 |
D.panelForge.classList.add('active');
|
|
|
|
|
|
|
| 169 |
D.previewTabBtn.disabled = true;
|
| 170 |
D.previewTabBtn.classList.add('locked');
|
| 171 |
D.previewTabBtn.classList.remove('unlocked');
|
| 172 |
D.previewTabBtn.querySelector('.tab-icon').textContent = '🔒';
|
| 173 |
|
| 174 |
+
D.codeContent.innerHTML = '<span class="placeholder">Streaming…</span>';
|
| 175 |
+
D.charCount.textContent = '';
|
| 176 |
D.previewOverlay.classList.remove('hidden');
|
| 177 |
D.previewFrame.src = 'about:blank';
|
| 178 |
D.publishResult.style.display = 'none';
|
| 179 |
+
setPublishState(false, 'Building…', '🔨');
|
|
|
|
| 180 |
D.vibeBtn.disabled = true;
|
| 181 |
+
|
| 182 |
+
// Wizard pulse start
|
| 183 |
+
pulseWizard();
|
| 184 |
+
showThought();
|
| 185 |
+
updateThought('Analyzing prompt…', []);
|
| 186 |
|
| 187 |
try {
|
| 188 |
for await (const ev of streamFromServer(prompt)) {
|
| 189 |
+
// Phase updates → single thought line
|
| 190 |
+
if (ev.phase) {
|
| 191 |
+
const phaseMap = {
|
| 192 |
+
'pose': 'Selecting models…',
|
| 193 |
+
'generate': 'Generating code…',
|
| 194 |
+
'audit': 'Verifying output…',
|
| 195 |
+
'heal': 'Self-healing errors…',
|
| 196 |
+
'sandbox': 'Validating sandbox…',
|
| 197 |
+
};
|
| 198 |
+
updateThought(phaseMap[ev.phase] || ev.phase, []);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
// Pose plan → show model badges
|
| 202 |
+
if (ev.pose) {
|
| 203 |
+
const models = [];
|
| 204 |
+
if (ev.pose.architect?.stack?.backend) models.push({model_id:'postgres+oauth'});
|
| 205 |
+
models.push({model_id:'liquid-glass'});
|
| 206 |
+
updateThought('Swarm dispatched', models);
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
// Code streaming
|
| 210 |
if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; }
|
| 211 |
if (ev.partial) updateIframe(ev.partial);
|
| 212 |
+
|
| 213 |
+
// Sandbox result
|
| 214 |
if (ev.sandbox) {
|
| 215 |
+
stopWizard(ev.status === 'stable' ? 'stable' : ev.status === 'error' ? 'error' : null);
|
|
|
|
| 216 |
if (ev.status === 'stable' || ev.status === 'published') {
|
| 217 |
unlockPreview();
|
| 218 |
+
setPublishState(true, 'Sandbox stable', '🔓');
|
| 219 |
+
updateThought('Build complete', [{model_id:'ready'}]);
|
| 220 |
+
} else if (ev.status === 'error') {
|
| 221 |
+
setPublishState(false, 'Sandbox errors found', '❌');
|
| 222 |
+
updateThought('Errors detected', []);
|
| 223 |
}
|
| 224 |
}
|
| 225 |
}
|
| 226 |
} catch (err) {
|
| 227 |
console.error('Stream error:', err);
|
| 228 |
+
stopWizard('error');
|
| 229 |
+
updateThought(`Error: ${err.message}`, []);
|
| 230 |
} finally {
|
| 231 |
D.vibeBtn.disabled = false;
|
| 232 |
+
if (!S.publishReady) stopWizard(null);
|
| 233 |
+
setTimeout(hideThought, 2000);
|
|
|
|
|
|
|
|
|
|
| 234 |
}
|
| 235 |
}
|
| 236 |
|
| 237 |
+
/* ═══ PUBLISH WITH HANDSHAKE ═══ */
|
| 238 |
async function publish() {
|
| 239 |
+
if (!S.publishReady) return;
|
| 240 |
+
|
| 241 |
+
// Phase 1: Verify handshake
|
| 242 |
+
D.publishBtn.classList.add('verifying');
|
| 243 |
+
D.publishBtn.querySelector('.spinner').style.display = 'inline-block';
|
| 244 |
+
updateThought('Verifying server handshake…', [{model_id:'litheat.app'}]);
|
| 245 |
+
showThought();
|
| 246 |
+
|
| 247 |
+
const hk = await verifyHandshake('litheat.app');
|
| 248 |
+
if (!hk.verified) {
|
| 249 |
+
updateThought('Handshake failed — retrying…', []);
|
| 250 |
+
// Retry once
|
| 251 |
+
await new Promise(r => setTimeout(r, 1000));
|
| 252 |
+
const hk2 = await verifyHandshake('litheat.app');
|
| 253 |
+
if (!hk2.verified) {
|
| 254 |
+
updateThought('⚠ Server unreachable — publishing anyway', []);
|
| 255 |
+
D.publishBtn.classList.remove('verifying');
|
| 256 |
+
D.vibeBtn.querySelector('.spinner')?.style && (D.publishBtn.querySelector('.spinner').style.display = 'none');
|
| 257 |
+
}
|
| 258 |
+
} else {
|
| 259 |
+
updateThought('✓ Handshake verified', [{model_id:'litheat.app'}]);
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
// Phase 2: Publish
|
| 263 |
+
const repoName = `omni-vibe-${Date.now().toString(36)}`;
|
| 264 |
D.publishBtn.disabled = true;
|
| 265 |
+
D.publishBtn.classList.add('verifying');
|
| 266 |
+
updateThought('Deploying to production…', [{model_id:'ghost-deploy'}]);
|
| 267 |
+
|
| 268 |
try {
|
| 269 |
const resp = await fetch('/api/publish', {
|
| 270 |
method:'POST',
|
|
|
|
| 272 |
body:JSON.stringify({repo_name:repoName, description:D.prompt.value.trim().slice(0,200)}),
|
| 273 |
});
|
| 274 |
const r = await resp.json();
|
| 275 |
+
|
| 276 |
if (r.success) {
|
| 277 |
+
S.publishData = r;
|
| 278 |
+
stopWizard('published');
|
| 279 |
+
setPublishState(true, 'Published!', '🌟');
|
| 280 |
+
D.publishBtn.classList.add('gold');
|
| 281 |
D.publishResult.style.display = 'block';
|
| 282 |
+
const liveUrl = r.spaces?.url || r.tunnel_url || '#';
|
| 283 |
+
D.liveLink.href = liveUrl;
|
| 284 |
+
D.liveLink.textContent = liveUrl;
|
| 285 |
+
updateThought('✓ Live — production ready', [{model_id:'deployed'}]);
|
| 286 |
D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'});
|
| 287 |
+
} else {
|
| 288 |
+
updateThought(`Publish error: ${r.error || r.space_error || 'Unknown'}`, []);
|
| 289 |
+
setPublishState(true, 'Publish failed — retry', '⚠');
|
| 290 |
+
}
|
| 291 |
} catch (err) {
|
| 292 |
+
updateThought(`Publish error: ${err.message}`, []);
|
| 293 |
+
setPublishState(true, 'Publish failed — retry', '⚠');
|
| 294 |
} finally {
|
| 295 |
+
D.publishBtn.disabled = !S.publishReady;
|
| 296 |
+
D.publishBtn.classList.remove('verifying');
|
| 297 |
+
setTimeout(hideThought, 3000);
|
| 298 |
}
|
| 299 |
}
|
| 300 |
|
|
|
|
| 302 |
D.vibeBtn.addEventListener('click', generate);
|
| 303 |
D.publishBtn.addEventListener('click', publish);
|
| 304 |
D.prompt.addEventListener('keydown', e => {
|
| 305 |
+
if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); generate(); }
|
| 306 |
});
|
| 307 |
D.copyBtn.addEventListener('click', async () => {
|
| 308 |
if (!S.fullCode) return;
|
| 309 |
try { await navigator.clipboard.writeText(S.fullCode); } catch {}
|
| 310 |
});
|
| 311 |
|
| 312 |
+
/* ═══ AUTO-RESIZE TEXTAREA ═══ */
|
| 313 |
+
D.prompt.addEventListener('input', () => {
|
| 314 |
+
D.prompt.style.height = 'auto';
|
| 315 |
+
D.prompt.style.height = Math.min(D.prompt.scrollHeight, 120) + 'px';
|
| 316 |
+
});
|
| 317 |
+
|
| 318 |
+
console.log('🧙♂️ Omni-Vibe Studio — Fluid Thinking Client ready');
|