Upload static/wizard.js
Browse files- static/wizard.js +312 -142
static/wizard.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
| 1 |
/**
|
| 2 |
* ╔══════════════════════════════════════════════════════╗
|
| 3 |
-
* ║
|
| 4 |
-
* ║
|
|
|
|
| 5 |
* ╚══════════════════════════════════════════════════════╝
|
| 6 |
*/
|
| 7 |
|
| 8 |
const S = {
|
| 9 |
-
sessionId: null, fullCode: '',
|
| 10 |
publishReady: false, publishData: null, handshakeVerified: false,
|
|
|
|
|
|
|
| 11 |
};
|
|
|
|
| 12 |
const D = {
|
| 13 |
prompt: document.getElementById('prompt-input'),
|
| 14 |
vibeBtn: document.getElementById('vibe-btn'),
|
|
@@ -16,23 +20,113 @@ const D = {
|
|
| 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 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
| 27 |
publishResult: document.getElementById('publish-result'),
|
| 28 |
liveLink: document.getElementById('live-link'),
|
| 29 |
wizardHat: document.getElementById('wizard-hat'),
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
| 33 |
copyBtn: document.getElementById('copy-btn'),
|
| 34 |
};
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
/* ═══ TAB SWITCHING ═══ */
|
| 37 |
document.querySelectorAll('.tab-btn').forEach(btn => {
|
| 38 |
btn.addEventListener('click', () => {
|
|
@@ -45,41 +139,44 @@ document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
| 45 |
});
|
| 46 |
|
| 47 |
function unlockPreview() {
|
| 48 |
-
if (S.
|
| 49 |
-
S.
|
| 50 |
D.previewTabBtn.disabled = false;
|
| 51 |
D.previewTabBtn.classList.remove('locked');
|
| 52 |
-
D.previewTabBtn.
|
| 53 |
-
D.previewTabBtn.querySelector('.tab-icon').textContent = '👁️';
|
| 54 |
}
|
| 55 |
|
| 56 |
-
/* ═══
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
function pulseWizard() {
|
| 58 |
D.wizardHat.classList.add('pulse');
|
|
|
|
| 59 |
}
|
| 60 |
function stopWizard(state) {
|
| 61 |
-
D.wizardHat.classList.remove('pulse');
|
| 62 |
-
D.
|
| 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 ═══ */
|
| 84 |
async function* streamFromServer(prompt) {
|
| 85 |
const resp = await fetch('/api/stream', {
|
|
@@ -105,7 +202,7 @@ async function* streamFromServer(prompt) {
|
|
| 105 |
}
|
| 106 |
}
|
| 107 |
|
| 108 |
-
/* ═══ IFRAME ═══ */
|
| 109 |
function updateIframe(code) {
|
| 110 |
const blob = new Blob([code], {type:'text/html'});
|
| 111 |
const url = URL.createObjectURL(blob);
|
|
@@ -113,6 +210,8 @@ function updateIframe(code) {
|
|
| 113 |
D.previewFrame._blobUrl = url;
|
| 114 |
D.previewFrame.src = url;
|
| 115 |
D.previewOverlay.classList.add('hidden');
|
|
|
|
|
|
|
| 116 |
}
|
| 117 |
|
| 118 |
function appendCode(chunk) {
|
|
@@ -124,42 +223,95 @@ function appendCode(chunk) {
|
|
| 124 |
D.charCount.textContent = `${S.fullCode.length.toLocaleString()} chars`;
|
| 125 |
}
|
| 126 |
|
| 127 |
-
/* ═══
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
}
|
| 142 |
}
|
| 143 |
|
| 144 |
-
/* ═══
|
| 145 |
-
function
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
}
|
|
|
|
| 157 |
}
|
| 158 |
|
| 159 |
-
/* ═══ MAIN
|
| 160 |
-
async function
|
| 161 |
const prompt = D.prompt.value.trim(); if (!prompt) return;
|
| 162 |
-
S.fullCode = ''; S.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
// Reset
|
| 165 |
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
@@ -168,102 +320,87 @@ async function generate() {
|
|
| 168 |
D.panelForge.classList.add('active');
|
| 169 |
D.previewTabBtn.disabled = true;
|
| 170 |
D.previewTabBtn.classList.add('locked');
|
| 171 |
-
D.previewTabBtn.
|
| 172 |
-
D.previewTabBtn.querySelector('.tab-icon').textContent = '🔒';
|
| 173 |
|
| 174 |
-
D.codeContent.innerHTML = '<span class="placeholder">
|
| 175 |
D.charCount.textContent = '';
|
| 176 |
D.previewOverlay.classList.remove('hidden');
|
| 177 |
D.previewFrame.src = 'about:blank';
|
| 178 |
D.publishResult.style.display = 'none';
|
| 179 |
-
|
| 180 |
D.vibeBtn.disabled = true;
|
| 181 |
|
| 182 |
-
// Wizard pulse
|
|
|
|
| 183 |
pulseWizard();
|
| 184 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 219 |
-
|
|
|
|
|
|
|
| 220 |
} else if (ev.status === 'error') {
|
| 221 |
-
|
| 222 |
-
updateThought('Errors detected', []);
|
| 223 |
}
|
| 224 |
}
|
| 225 |
}
|
| 226 |
} catch (err) {
|
| 227 |
-
console.error('
|
| 228 |
stopWizard('error');
|
| 229 |
-
|
|
|
|
| 230 |
} finally {
|
| 231 |
D.vibeBtn.disabled = false;
|
| 232 |
if (!S.publishReady) stopWizard(null);
|
| 233 |
-
|
|
|
|
| 234 |
}
|
| 235 |
}
|
| 236 |
|
| 237 |
-
/* ═══
|
| 238 |
-
async function
|
| 239 |
-
if (!S.publishReady) return;
|
| 240 |
|
| 241 |
-
// Phase 1:
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
showThought();
|
| 246 |
|
| 247 |
-
const hk = await
|
| 248 |
-
if (
|
| 249 |
-
|
|
|
|
| 250 |
// Retry once
|
| 251 |
-
await new Promise(r => setTimeout(r,
|
| 252 |
-
const hk2 = await
|
| 253 |
-
if (
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
D.
|
|
|
|
|
|
|
| 257 |
}
|
| 258 |
-
} else {
|
| 259 |
-
updateThought('✓ Handshake verified', [{model_id:'litheat.app'}]);
|
| 260 |
}
|
| 261 |
|
| 262 |
// Phase 2: Publish
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
updateThought('Deploying to production…', [{model_id:'ghost-deploy'}]);
|
| 267 |
|
| 268 |
try {
|
| 269 |
const resp = await fetch('/api/publish', {
|
|
@@ -275,44 +412,77 @@ async function publish() {
|
|
| 275 |
|
| 276 |
if (r.success) {
|
| 277 |
S.publishData = r;
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
D.
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
} else {
|
| 288 |
-
|
| 289 |
-
|
| 290 |
}
|
| 291 |
} catch (err) {
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
} finally {
|
| 295 |
-
D.publishBtn.disabled = !S.publishReady;
|
| 296 |
-
D.publishBtn.classList.remove('verifying');
|
| 297 |
-
setTimeout(hideThought, 3000);
|
| 298 |
}
|
| 299 |
}
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
/* ═══ EVENT BINDINGS ═══ */
|
| 302 |
-
D.vibeBtn.addEventListener('click',
|
| 303 |
-
D.publishBtn.addEventListener('click',
|
| 304 |
D.prompt.addEventListener('keydown', e => {
|
| 305 |
-
if (e.key==='Enter' && !e.shiftKey) { e.preventDefault();
|
| 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
|
| 313 |
D.prompt.addEventListener('input', () => {
|
| 314 |
D.prompt.style.height = 'auto';
|
| 315 |
-
D.prompt.style.height = Math.min(D.prompt.scrollHeight,
|
| 316 |
});
|
| 317 |
|
| 318 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
* ╔══════════════════════════════════════════════════════╗
|
| 3 |
+
* ║ OBSIDIAN FORGE — Client Runtime ║
|
| 4 |
+
* ║ RLM Thought-Stream · Context Folding ║
|
| 5 |
+
* ║ Quantum Sandbox · Silent Handshake · Deploy ║
|
| 6 |
* ╚══════════════════════════════════════════════════════╝
|
| 7 |
*/
|
| 8 |
|
| 9 |
const S = {
|
| 10 |
+
sessionId: null, fullCode: '', previewReady: false,
|
| 11 |
publishReady: false, publishData: null, handshakeVerified: false,
|
| 12 |
+
handshakeUrl: null, deployedUrl: null,
|
| 13 |
+
quantumWindow: null, // Reference to dedicated preview tab
|
| 14 |
};
|
| 15 |
+
|
| 16 |
const D = {
|
| 17 |
prompt: document.getElementById('prompt-input'),
|
| 18 |
vibeBtn: document.getElementById('vibe-btn'),
|
|
|
|
| 20 |
charCount: document.getElementById('char-count'),
|
| 21 |
previewFrame: document.getElementById('preview-frame'),
|
| 22 |
previewOverlay: document.getElementById('preview-overlay'),
|
|
|
|
| 23 |
previewTabBtn: document.getElementById('preview-tab-btn'),
|
| 24 |
panelForge: document.getElementById('panel-forge'),
|
| 25 |
panelPreview: document.getElementById('panel-preview'),
|
| 26 |
publishBtn: document.getElementById('publish-btn'),
|
| 27 |
+
deployBar: document.getElementById('deploy-bar'),
|
| 28 |
+
deployDot: document.getElementById('deploy-dot'),
|
| 29 |
+
deployText: document.getElementById('deploy-text'),
|
| 30 |
+
deployLabel: document.getElementById('deploy-label'),
|
| 31 |
publishResult: document.getElementById('publish-result'),
|
| 32 |
liveLink: document.getElementById('live-link'),
|
| 33 |
wizardHat: document.getElementById('wizard-hat'),
|
| 34 |
+
orbitRing: document.getElementById('orbit-ring'),
|
| 35 |
+
thoughtStream: document.getElementById('thought-stream'),
|
| 36 |
+
streamText: document.getElementById('stream-text'),
|
| 37 |
+
popoutBtn: document.getElementById('popout-btn'),
|
| 38 |
copyBtn: document.getElementById('copy-btn'),
|
| 39 |
};
|
| 40 |
|
| 41 |
+
/* ═══ RLM THOUGHT-STREAM NARRATION ═══ */
|
| 42 |
+
const RLM_NARRATIVES = {
|
| 43 |
+
pose: [
|
| 44 |
+
'Recursively partitioning the Liquid Glass layers...',
|
| 45 |
+
'Calibrating the RLM feedback loop for structural perfection...',
|
| 46 |
+
'Analyzing architectural constraints through the Obsidian lens...',
|
| 47 |
+
'Dispatching sub-agents to the Quantum Sandbox...',
|
| 48 |
+
'Mapping design topology across the neural manifold...',
|
| 49 |
+
],
|
| 50 |
+
generate: [
|
| 51 |
+
'Synthing frontend lattice from compressed semantic schemas...',
|
| 52 |
+
'Folding context vectors into the DOM manifold...',
|
| 53 |
+
'Applying Aetheric CSS transforms to the structural lattice...',
|
| 54 |
+
'Generating Liquid Glass components from RLM state...',
|
| 55 |
+
'Weaving the Obsidian gradient across the output surface...',
|
| 56 |
+
],
|
| 57 |
+
audit: [
|
| 58 |
+
'Auditing code integrity through recursive type propagation...',
|
| 59 |
+
'Verifying HTML topology against the Liquid Glass schema...',
|
| 60 |
+
'Scanning for structural anomalies in the generated lattice...',
|
| 61 |
+
'Cross-referencing output against the RLM design constraints...',
|
| 62 |
+
],
|
| 63 |
+
heal: [
|
| 64 |
+
'Applying Reflect-Select healing to identified anomalies...',
|
| 65 |
+
'Self-correcting structural deviations in real-time...',
|
| 66 |
+
'Merging healed fragments into the coherent output stream...',
|
| 67 |
+
],
|
| 68 |
+
sandbox: [
|
| 69 |
+
'Validating sandbox integrity through the Quantum Sandbox...',
|
| 70 |
+
'Performing final consistency check on the generated artifact...',
|
| 71 |
+
'Sealing the output in the Obsidian forge...',
|
| 72 |
+
],
|
| 73 |
+
done: [
|
| 74 |
+
'RLM recursion complete. Output stable.',
|
| 75 |
+
'Forge cycle terminated — artifact ready.',
|
| 76 |
+
'All sub-agents returned. Sandbox synchronized.',
|
| 77 |
+
],
|
| 78 |
+
deploy: [
|
| 79 |
+
'Initiating Ghost Deploy protocol...',
|
| 80 |
+
'Resolving DNS handshake with litheat.app...',
|
| 81 |
+
'Verifying deployment endpoint reachability...',
|
| 82 |
+
'Propagating artifact to production surface...',
|
| 83 |
+
],
|
| 84 |
+
};
|
| 85 |
+
|
| 86 |
+
let _narrativeIdx = {};
|
| 87 |
+
function pickNarrative(phase) {
|
| 88 |
+
const pool = RLM_NARRATIVES[phase] || RLM_NARRATIVES['pose'];
|
| 89 |
+
if (!_narrativeIdx[phase]) _narrativeIdx[phase] = 0;
|
| 90 |
+
const idx = _narrativeIdx[phase]++ % pool.length;
|
| 91 |
+
return pool[idx];
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
function showThoughtStream() {
|
| 95 |
+
D.thoughtStream.style.display = 'flex';
|
| 96 |
+
requestAnimationFrame(() => D.thoughtStream.classList.add('visible'));
|
| 97 |
+
}
|
| 98 |
+
function addThoughtSentence(phase) {
|
| 99 |
+
showThoughtStream();
|
| 100 |
+
const sentence = pickNarrative(phase);
|
| 101 |
+
const el = document.createElement('span');
|
| 102 |
+
el.className = 'sentence';
|
| 103 |
+
el.textContent = sentence;
|
| 104 |
+
D.streamText.appendChild(el);
|
| 105 |
+
// Auto-scroll
|
| 106 |
+
D.streamText.scrollTop = D.streamText.scrollHeight;
|
| 107 |
+
}
|
| 108 |
+
function clearThoughtStream() {
|
| 109 |
+
D.streamText.innerHTML = '';
|
| 110 |
+
D.thoughtStream.classList.remove('visible');
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
/* ═══ CONTEXT FOLDING — localStorage persistence ═══ */
|
| 114 |
+
const CTX_KEY = 'obsidian-forge-context';
|
| 115 |
+
function foldContext(data) {
|
| 116 |
+
try {
|
| 117 |
+
const ctx = JSON.parse(localStorage.getItem(CTX_KEY) || '{}');
|
| 118 |
+
Object.assign(ctx, data, {_folded: Date.now()});
|
| 119 |
+
localStorage.setItem(CTX_KEY, JSON.stringify(ctx));
|
| 120 |
+
} catch {}
|
| 121 |
+
}
|
| 122 |
+
function unfoldContext() {
|
| 123 |
+
try {
|
| 124 |
+
const ctx = JSON.parse(localStorage.getItem(CTX_KEY) || '{}');
|
| 125 |
+
if (ctx.lastPrompt) D.prompt.value = ctx.lastPrompt;
|
| 126 |
+
return ctx;
|
| 127 |
+
} catch { return {}; }
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
/* ═══ TAB SWITCHING ═══ */
|
| 131 |
document.querySelectorAll('.tab-btn').forEach(btn => {
|
| 132 |
btn.addEventListener('click', () => {
|
|
|
|
| 139 |
});
|
| 140 |
|
| 141 |
function unlockPreview() {
|
| 142 |
+
if (S.previewReady) return;
|
| 143 |
+
S.previewReady = true;
|
| 144 |
D.previewTabBtn.disabled = false;
|
| 145 |
D.previewTabBtn.classList.remove('locked');
|
| 146 |
+
D.previewTabBtn.querySelector('.tab-label').textContent = '🔮 View Preview';
|
|
|
|
| 147 |
}
|
| 148 |
|
| 149 |
+
/* ═══ QUANTUM SANDBOX — dedicated persistent tab ═══ */
|
| 150 |
+
function launchQuantumSandbox(code) {
|
| 151 |
+
const blob = new Blob([code], {type:'text/html'});
|
| 152 |
+
const url = URL.createObjectURL(blob);
|
| 153 |
+
if (S.quantumWindow && !S.quantumWindow.closed) {
|
| 154 |
+
S.quantumWindow.location.href = url;
|
| 155 |
+
S.quantumWindow.focus();
|
| 156 |
+
} else {
|
| 157 |
+
S.quantumWindow = window.open(url, 'obsidian-quantum-sandbox', 'width=1400,height=900');
|
| 158 |
+
}
|
| 159 |
+
return S.quantumWindow;
|
| 160 |
+
}
|
| 161 |
+
function syncQuantumSandbox(code) {
|
| 162 |
+
if (S.quantumWindow && !S.quantumWindow.closed) {
|
| 163 |
+
const blob = new Blob([code], {type:'text/html'});
|
| 164 |
+
const url = URL.createObjectURL(blob);
|
| 165 |
+
S.quantumWindow.location.href = url;
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
/* ═══ WIZARD PULSE ═══ */
|
| 170 |
function pulseWizard() {
|
| 171 |
D.wizardHat.classList.add('pulse');
|
| 172 |
+
D.orbitRing.classList.add('forging');
|
| 173 |
}
|
| 174 |
function stopWizard(state) {
|
| 175 |
+
D.wizardHat.classList.remove('pulse','stable','error','deployed');
|
| 176 |
+
D.orbitRing.classList.remove('forging');
|
| 177 |
if (state) D.wizardHat.classList.add(state);
|
| 178 |
}
|
| 179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
/* ═══ SSE STREAMING ═══ */
|
| 181 |
async function* streamFromServer(prompt) {
|
| 182 |
const resp = await fetch('/api/stream', {
|
|
|
|
| 202 |
}
|
| 203 |
}
|
| 204 |
|
| 205 |
+
/* ═══ IFRAME + QUANTUM ═══ */
|
| 206 |
function updateIframe(code) {
|
| 207 |
const blob = new Blob([code], {type:'text/html'});
|
| 208 |
const url = URL.createObjectURL(blob);
|
|
|
|
| 210 |
D.previewFrame._blobUrl = url;
|
| 211 |
D.previewFrame.src = url;
|
| 212 |
D.previewOverlay.classList.add('hidden');
|
| 213 |
+
// Sync quantum tab
|
| 214 |
+
syncQuantumSandbox(code);
|
| 215 |
}
|
| 216 |
|
| 217 |
function appendCode(chunk) {
|
|
|
|
| 223 |
D.charCount.textContent = `${S.fullCode.length.toLocaleString()} chars`;
|
| 224 |
}
|
| 225 |
|
| 226 |
+
/* ═══ DEPLOY GATE ═══ */
|
| 227 |
+
function setDeployState(state) {
|
| 228 |
+
D.deployBar.classList.remove('ready','verified');
|
| 229 |
+
D.deployDot.classList.remove('ready','verifying','deployed');
|
| 230 |
+
D.publishBtn.classList.remove('ready','deploying','deployed');
|
| 231 |
+
D.deployLabel.textContent = 'Deploy';
|
| 232 |
+
|
| 233 |
+
switch(state) {
|
| 234 |
+
case 'ready':
|
| 235 |
+
D.deployBar.classList.add('ready');
|
| 236 |
+
D.deployDot.classList.add('ready');
|
| 237 |
+
D.publishBtn.classList.add('ready');
|
| 238 |
+
D.publishBtn.disabled = false;
|
| 239 |
+
D.deployText.textContent = 'Sandbox stable';
|
| 240 |
+
break;
|
| 241 |
+
case 'verifying':
|
| 242 |
+
D.deployDot.classList.add('verifying');
|
| 243 |
+
D.publishBtn.classList.add('deploying');
|
| 244 |
+
D.publishBtn.disabled = true;
|
| 245 |
+
D.deployLabel.textContent = 'Verifying…';
|
| 246 |
+
D.deployText.textContent = 'Resolving litheat.app';
|
| 247 |
+
break;
|
| 248 |
+
case 'deploying':
|
| 249 |
+
D.deployDot.classList.add('verifying');
|
| 250 |
+
D.publishBtn.classList.add('deploying');
|
| 251 |
+
D.publishBtn.disabled = true;
|
| 252 |
+
D.deployLabel.textContent = 'Deploying…';
|
| 253 |
+
D.deployText.textContent = 'Ghost Deploy active';
|
| 254 |
+
break;
|
| 255 |
+
case 'deployed':
|
| 256 |
+
D.deployBar.classList.add('verified');
|
| 257 |
+
D.deployDot.classList.add('deployed');
|
| 258 |
+
D.publishBtn.classList.add('deployed');
|
| 259 |
+
D.publishBtn.disabled = false;
|
| 260 |
+
D.deployLabel.textContent = 'Live ◈';
|
| 261 |
+
D.deployText.textContent = 'Verified — Production ready';
|
| 262 |
+
break;
|
| 263 |
+
default:
|
| 264 |
+
D.publishBtn.disabled = true;
|
| 265 |
+
D.deployText.textContent = 'RLM idle';
|
| 266 |
}
|
| 267 |
}
|
| 268 |
|
| 269 |
+
/* ═══ SILENT HANDSHAKE ═══ */
|
| 270 |
+
async function silentHandshake(urls) {
|
| 271 |
+
for (const url of urls) {
|
| 272 |
+
try {
|
| 273 |
+
const resp = await fetch('/api/verify-handshake', {
|
| 274 |
+
method:'POST',
|
| 275 |
+
headers:{'Content-Type':'application/json'},
|
| 276 |
+
body:JSON.stringify({url}),
|
| 277 |
+
});
|
| 278 |
+
const data = await resp.json();
|
| 279 |
+
if (data.verified) {
|
| 280 |
+
S.handshakeVerified = true;
|
| 281 |
+
return {verified:true, url:data.url, latency:data.latency_ms};
|
| 282 |
+
}
|
| 283 |
+
} catch {}
|
| 284 |
+
}
|
| 285 |
+
return {verified:false};
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
/* ═══ POLL DEPLOYED URL UNTIL 200 ═══ */
|
| 289 |
+
async function pollUntilLive(url, maxAttempts=15) {
|
| 290 |
+
for (let i = 0; i < maxAttempts; i++) {
|
| 291 |
+
try {
|
| 292 |
+
const resp = await fetch('/api/verify-handshake', {
|
| 293 |
+
method:'POST',
|
| 294 |
+
headers:{'Content-Type':'application/json'},
|
| 295 |
+
body:JSON.stringify({url}),
|
| 296 |
+
});
|
| 297 |
+
const data = await resp.json();
|
| 298 |
+
if (data.verified && data.checks?.status_code === 200) {
|
| 299 |
+
return {live:true, url, latency:data.latency_ms};
|
| 300 |
+
}
|
| 301 |
+
} catch {}
|
| 302 |
+
await new Promise(r => setTimeout(r, 2000));
|
| 303 |
}
|
| 304 |
+
return {live:false, url};
|
| 305 |
}
|
| 306 |
|
| 307 |
+
/* ═══ MAIN FORGE ═══ */
|
| 308 |
+
async function forge() {
|
| 309 |
const prompt = D.prompt.value.trim(); if (!prompt) return;
|
| 310 |
+
S.fullCode = ''; S.previewReady = false; S.publishReady = false;
|
| 311 |
+
S.handshakeVerified = false; S.handshakeUrl = null; S.deployedUrl = null;
|
| 312 |
+
|
| 313 |
+
// Context Folding: persist user choice
|
| 314 |
+
foldContext({lastPrompt: prompt});
|
| 315 |
|
| 316 |
// Reset
|
| 317 |
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
|
|
| 320 |
D.panelForge.classList.add('active');
|
| 321 |
D.previewTabBtn.disabled = true;
|
| 322 |
D.previewTabBtn.classList.add('locked');
|
| 323 |
+
D.previewTabBtn.querySelector('.tab-label').textContent = '🔒 Preview';
|
|
|
|
| 324 |
|
| 325 |
+
D.codeContent.innerHTML = '<span class="placeholder">// Obsidian Forge invoking RLM recursion…</span>';
|
| 326 |
D.charCount.textContent = '';
|
| 327 |
D.previewOverlay.classList.remove('hidden');
|
| 328 |
D.previewFrame.src = 'about:blank';
|
| 329 |
D.publishResult.style.display = 'none';
|
| 330 |
+
setDeployState('idle');
|
| 331 |
D.vibeBtn.disabled = true;
|
| 332 |
|
| 333 |
+
// Wizard pulse + thought-stream
|
| 334 |
+
clearThoughtStream();
|
| 335 |
pulseWizard();
|
| 336 |
+
addThoughtSentence('pose');
|
|
|
|
| 337 |
|
| 338 |
try {
|
| 339 |
for await (const ev of streamFromServer(prompt)) {
|
|
|
|
| 340 |
if (ev.phase) {
|
| 341 |
+
addThoughtSentence(ev.phase);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
}
|
|
|
|
|
|
|
| 343 |
if (ev.pose) {
|
| 344 |
+
addThoughtSentence('pose'); // Extra narration for swarm dispatch
|
|
|
|
|
|
|
|
|
|
| 345 |
}
|
|
|
|
|
|
|
| 346 |
if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; }
|
| 347 |
if (ev.partial) updateIframe(ev.partial);
|
| 348 |
|
|
|
|
| 349 |
if (ev.sandbox) {
|
| 350 |
stopWizard(ev.status === 'stable' ? 'stable' : ev.status === 'error' ? 'error' : null);
|
| 351 |
if (ev.status === 'stable' || ev.status === 'published') {
|
| 352 |
unlockPreview();
|
| 353 |
+
setDeployState('ready');
|
| 354 |
+
addThoughtSentence('done');
|
| 355 |
+
// Auto-launch quantum sandbox on stability
|
| 356 |
+
if (S.fullCode) launchQuantumSandbox(S.fullCode);
|
| 357 |
} else if (ev.status === 'error') {
|
| 358 |
+
setDeployState('idle');
|
|
|
|
| 359 |
}
|
| 360 |
}
|
| 361 |
}
|
| 362 |
} catch (err) {
|
| 363 |
+
console.error('Forge error:', err);
|
| 364 |
stopWizard('error');
|
| 365 |
+
addThoughtSentence('heal'); // "Self-correcting..."
|
| 366 |
+
D.deployText.textContent = `Error: ${err.message}`;
|
| 367 |
} finally {
|
| 368 |
D.vibeBtn.disabled = false;
|
| 369 |
if (!S.publishReady) stopWizard(null);
|
| 370 |
+
// Persist output context
|
| 371 |
+
foldContext({lastOutputLen: S.fullCode.length, lastForged: Date.now()});
|
| 372 |
}
|
| 373 |
}
|
| 374 |
|
| 375 |
+
/* ═══ DEPLOY WITH SILENT HANDSHAKE ═══ */
|
| 376 |
+
async function deploy() {
|
| 377 |
+
if (!S.publishReady && !S.fullCode) return;
|
| 378 |
|
| 379 |
+
// Phase 1: Silent DNS handshake — verify litheat.app FIRST
|
| 380 |
+
setDeployState('verifying');
|
| 381 |
+
clearThoughtStream();
|
| 382 |
+
addThoughtSentence('deploy');
|
|
|
|
| 383 |
|
| 384 |
+
const hk = await silentHandshake(['litheat.app', 'huggingface.co']);
|
| 385 |
+
if (hk.verified) {
|
| 386 |
+
addThoughtSentence('deploy');
|
| 387 |
+
} else {
|
| 388 |
// Retry once
|
| 389 |
+
await new Promise(r => setTimeout(r, 1500));
|
| 390 |
+
const hk2 = await silentHandshake(['litheat.app']);
|
| 391 |
+
if (hk2.verified) {
|
| 392 |
+
addThoughtSentence('deploy');
|
| 393 |
+
} else {
|
| 394 |
+
D.deployText.textContent = 'Handshake failed — retry';
|
| 395 |
+
setDeployState('ready');
|
| 396 |
+
return;
|
| 397 |
}
|
|
|
|
|
|
|
| 398 |
}
|
| 399 |
|
| 400 |
// Phase 2: Publish
|
| 401 |
+
setDeployState('deploying');
|
| 402 |
+
const repoName = `obsidian-${Date.now().toString(36)}`;
|
| 403 |
+
addThoughtSentence('deploy');
|
|
|
|
| 404 |
|
| 405 |
try {
|
| 406 |
const resp = await fetch('/api/publish', {
|
|
|
|
| 412 |
|
| 413 |
if (r.success) {
|
| 414 |
S.publishData = r;
|
| 415 |
+
const rawUrl = r.spaces?.url || r.tunnel_url || '';
|
| 416 |
+
S.deployedUrl = rawUrl;
|
| 417 |
+
|
| 418 |
+
// Phase 3: Poll until 200 — do NOT show URL until verified
|
| 419 |
+
D.deployText.textContent = 'Polling deployment…';
|
| 420 |
+
D.deployLabel.textContent = 'Checking…';
|
| 421 |
+
const live = await pollUntilLive(rawUrl);
|
| 422 |
+
|
| 423 |
+
if (live.live) {
|
| 424 |
+
// Only now reveal the URL
|
| 425 |
+
stopWizard('deployed');
|
| 426 |
+
setDeployState('deployed');
|
| 427 |
+
D.publishResult.style.display = 'block';
|
| 428 |
+
D.liveLink.href = live.url;
|
| 429 |
+
D.liveLink.textContent = live.url;
|
| 430 |
+
foldContext({lastDeployUrl: live.url, lastDeployed: Date.now()});
|
| 431 |
+
addThoughtSentence('deploy');
|
| 432 |
+
D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'});
|
| 433 |
+
} else {
|
| 434 |
+
// Still show but warn
|
| 435 |
+
stopWizard('deployed');
|
| 436 |
+
setDeployState('deployed');
|
| 437 |
+
D.publishResult.style.display = 'block';
|
| 438 |
+
D.liveLink.href = rawUrl;
|
| 439 |
+
D.liveLink.textContent = rawUrl + ' (propagating)';
|
| 440 |
+
D.deployText.textContent = 'Deployed — warming up';
|
| 441 |
+
}
|
| 442 |
} else {
|
| 443 |
+
D.deployText.textContent = `Error: ${r.error || r.space_error || 'Unknown'}`;
|
| 444 |
+
setDeployState('ready');
|
| 445 |
}
|
| 446 |
} catch (err) {
|
| 447 |
+
D.deployText.textContent = `Error: ${err.message}`;
|
| 448 |
+
setDeployState('ready');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
}
|
| 450 |
}
|
| 451 |
|
| 452 |
+
/* ═══ QUANTUM SANDBOX POPOUT ═══ */
|
| 453 |
+
D.popoutBtn.addEventListener('click', () => {
|
| 454 |
+
if (S.fullCode) launchQuantumSandbox(S.fullCode);
|
| 455 |
+
});
|
| 456 |
+
|
| 457 |
/* ═══ EVENT BINDINGS ═══ */
|
| 458 |
+
D.vibeBtn.addEventListener('click', forge);
|
| 459 |
+
D.publishBtn.addEventListener('click', deploy);
|
| 460 |
D.prompt.addEventListener('keydown', e => {
|
| 461 |
+
if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); forge(); }
|
| 462 |
});
|
| 463 |
D.copyBtn.addEventListener('click', async () => {
|
| 464 |
if (!S.fullCode) return;
|
| 465 |
try { await navigator.clipboard.writeText(S.fullCode); } catch {}
|
| 466 |
});
|
| 467 |
|
| 468 |
+
/* ═══ AUTO-RESIZE ═══ */
|
| 469 |
D.prompt.addEventListener('input', () => {
|
| 470 |
D.prompt.style.height = 'auto';
|
| 471 |
+
D.prompt.style.height = Math.min(D.prompt.scrollHeight, 110) + 'px';
|
| 472 |
});
|
| 473 |
|
| 474 |
+
/* ═══ CONTEXT FOLDING: restore on load ═══ */
|
| 475 |
+
(function restoreContext() {
|
| 476 |
+
const ctx = unfoldContext();
|
| 477 |
+
if (ctx.lastPrompt) {
|
| 478 |
+
D.prompt.value = ctx.lastPrompt;
|
| 479 |
+
D.prompt.style.height = 'auto';
|
| 480 |
+
D.prompt.style.height = Math.min(D.prompt.scrollHeight, 110) + 'px';
|
| 481 |
+
}
|
| 482 |
+
})();
|
| 483 |
+
|
| 484 |
+
console.log('◈ Obsidian Forge — Kinetic RLM Environment ready');
|
| 485 |
+
console.log(' RLM: Recursive Language Modeling active');
|
| 486 |
+
console.log(' Context Folding: localStorage persistence');
|
| 487 |
+
console.log(' Quantum Sandbox: dedicated tab sync');
|
| 488 |
+
console.log(' Handshake: silent DNS + poll-until-live');
|