| |
| |
| |
| |
| |
| |
| |
|
|
| const S = { |
| sessionId: null, fullCode: '', previewReady: false, |
| publishReady: false, publishData: null, handshakeVerified: false, |
| handshakeUrl: null, deployedUrl: null, |
| quantumWindow: null, |
| }; |
|
|
| const D = { |
| prompt: document.getElementById('prompt-input'), |
| vibeBtn: document.getElementById('vibe-btn'), |
| codeContent: document.getElementById('code-content'), |
| charCount: document.getElementById('char-count'), |
| previewFrame: document.getElementById('preview-frame'), |
| previewOverlay: document.getElementById('preview-overlay'), |
| previewTabBtn: document.getElementById('preview-tab-btn'), |
| panelForge: document.getElementById('panel-forge'), |
| panelPreview: document.getElementById('panel-preview'), |
| publishBtn: document.getElementById('publish-btn'), |
| deployBar: document.getElementById('deploy-bar'), |
| deployDot: document.getElementById('deploy-dot'), |
| deployText: document.getElementById('deploy-text'), |
| deployLabel: document.getElementById('deploy-label'), |
| publishResult: document.getElementById('publish-result'), |
| liveLink: document.getElementById('live-link'), |
| wizardHat: document.getElementById('wizard-hat'), |
| orbitRing: document.getElementById('orbit-ring'), |
| thoughtStream: document.getElementById('thought-stream'), |
| streamText: document.getElementById('stream-text'), |
| popoutBtn: document.getElementById('popout-btn'), |
| copyBtn: document.getElementById('copy-btn'), |
| }; |
|
|
| |
| const RLM_NARRATIVES = { |
| pose: [ |
| 'Recursively partitioning the Liquid Glass layers...', |
| 'Calibrating the RLM feedback loop for structural perfection...', |
| 'Analyzing architectural constraints through the Obsidian lens...', |
| 'Dispatching sub-agents to the Quantum Sandbox...', |
| 'Mapping design topology across the neural manifold...', |
| ], |
| generate: [ |
| 'Synthing frontend lattice from compressed semantic schemas...', |
| 'Folding context vectors into the DOM manifold...', |
| 'Applying Aetheric CSS transforms to the structural lattice...', |
| 'Generating Liquid Glass components from RLM state...', |
| 'Weaving the Obsidian gradient across the output surface...', |
| ], |
| audit: [ |
| 'Auditing code integrity through recursive type propagation...', |
| 'Verifying HTML topology against the Liquid Glass schema...', |
| 'Scanning for structural anomalies in the generated lattice...', |
| 'Cross-referencing output against the RLM design constraints...', |
| ], |
| heal: [ |
| 'Applying Reflect-Select healing to identified anomalies...', |
| 'Self-correcting structural deviations in real-time...', |
| 'Merging healed fragments into the coherent output stream...', |
| ], |
| sandbox: [ |
| 'Validating sandbox integrity through the Quantum Sandbox...', |
| 'Performing final consistency check on the generated artifact...', |
| 'Sealing the output in the Obsidian forge...', |
| ], |
| done: [ |
| 'RLM recursion complete. Output stable.', |
| 'Forge cycle terminated — artifact ready.', |
| 'All sub-agents returned. Sandbox synchronized.', |
| ], |
| deploy: [ |
| 'Initiating Ghost Deploy protocol...', |
| 'Resolving DNS handshake with litheat.app...', |
| 'Verifying deployment endpoint reachability...', |
| 'Propagating artifact to production surface...', |
| ], |
| }; |
|
|
| let _narrativeIdx = {}; |
| function pickNarrative(phase) { |
| const pool = RLM_NARRATIVES[phase] || RLM_NARRATIVES['pose']; |
| if (!_narrativeIdx[phase]) _narrativeIdx[phase] = 0; |
| const idx = _narrativeIdx[phase]++ % pool.length; |
| return pool[idx]; |
| } |
|
|
| function showThoughtStream() { |
| D.thoughtStream.style.display = 'flex'; |
| requestAnimationFrame(() => D.thoughtStream.classList.add('visible')); |
| } |
| function addThoughtSentence(phase) { |
| showThoughtStream(); |
| const sentence = pickNarrative(phase); |
| const el = document.createElement('span'); |
| el.className = 'sentence'; |
| el.textContent = sentence; |
| D.streamText.appendChild(el); |
| |
| D.streamText.scrollTop = D.streamText.scrollHeight; |
| } |
| function clearThoughtStream() { |
| D.streamText.innerHTML = ''; |
| D.thoughtStream.classList.remove('visible'); |
| } |
|
|
| |
| const CTX_KEY = 'obsidian-forge-context'; |
| function foldContext(data) { |
| try { |
| const ctx = JSON.parse(localStorage.getItem(CTX_KEY) || '{}'); |
| Object.assign(ctx, data, {_folded: Date.now()}); |
| localStorage.setItem(CTX_KEY, JSON.stringify(ctx)); |
| } catch {} |
| } |
| function unfoldContext() { |
| try { |
| const ctx = JSON.parse(localStorage.getItem(CTX_KEY) || '{}'); |
| if (ctx.lastPrompt) D.prompt.value = ctx.lastPrompt; |
| return ctx; |
| } catch { return {}; } |
| } |
|
|
| |
| document.querySelectorAll('.tab-btn').forEach(btn => { |
| btn.addEventListener('click', () => { |
| if (btn.disabled) return; |
| document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); |
| document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active')); |
| btn.classList.add('active'); |
| document.getElementById('panel-' + btn.dataset.tab).classList.add('active'); |
| }); |
| }); |
|
|
| function unlockPreview() { |
| if (S.previewReady) return; |
| S.previewReady = true; |
| D.previewTabBtn.disabled = false; |
| D.previewTabBtn.classList.remove('locked'); |
| D.previewTabBtn.querySelector('.tab-label').textContent = '🔮 View Preview'; |
| } |
|
|
| |
| function launchQuantumSandbox(code) { |
| const blob = new Blob([code], {type:'text/html'}); |
| const url = URL.createObjectURL(blob); |
| if (S.quantumWindow && !S.quantumWindow.closed) { |
| S.quantumWindow.location.href = url; |
| S.quantumWindow.focus(); |
| } else { |
| S.quantumWindow = window.open(url, 'obsidian-quantum-sandbox', 'width=1400,height=900'); |
| } |
| return S.quantumWindow; |
| } |
| function syncQuantumSandbox(code) { |
| if (S.quantumWindow && !S.quantumWindow.closed) { |
| const blob = new Blob([code], {type:'text/html'}); |
| const url = URL.createObjectURL(blob); |
| S.quantumWindow.location.href = url; |
| } |
| } |
|
|
| |
| function pulseWizard() { |
| D.wizardHat.classList.add('pulse'); |
| D.orbitRing.classList.add('forging'); |
| } |
| function stopWizard(state) { |
| D.wizardHat.classList.remove('pulse','stable','error','deployed'); |
| D.orbitRing.classList.remove('forging'); |
| if (state) D.wizardHat.classList.add(state); |
| } |
|
|
| |
| 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 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'); |
| |
| syncQuantumSandbox(code); |
| } |
|
|
| 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; |
| D.charCount.textContent = `${S.fullCode.length.toLocaleString()} chars`; |
| } |
|
|
| |
| function setDeployState(state) { |
| D.deployBar.classList.remove('ready','verified'); |
| D.deployDot.classList.remove('ready','verifying','deployed'); |
| D.publishBtn.classList.remove('ready','deploying','deployed'); |
| D.deployLabel.textContent = 'Deploy'; |
|
|
| switch(state) { |
| case 'ready': |
| D.deployBar.classList.add('ready'); |
| D.deployDot.classList.add('ready'); |
| D.publishBtn.classList.add('ready'); |
| D.publishBtn.disabled = false; |
| D.deployText.textContent = 'Sandbox stable'; |
| break; |
| case 'verifying': |
| D.deployDot.classList.add('verifying'); |
| D.publishBtn.classList.add('deploying'); |
| D.publishBtn.disabled = true; |
| D.deployLabel.textContent = 'Verifying…'; |
| D.deployText.textContent = 'Resolving litheat.app'; |
| break; |
| case 'deploying': |
| D.deployDot.classList.add('verifying'); |
| D.publishBtn.classList.add('deploying'); |
| D.publishBtn.disabled = true; |
| D.deployLabel.textContent = 'Deploying…'; |
| D.deployText.textContent = 'Ghost Deploy active'; |
| break; |
| case 'deployed': |
| D.deployBar.classList.add('verified'); |
| D.deployDot.classList.add('deployed'); |
| D.publishBtn.classList.add('deployed'); |
| D.publishBtn.disabled = false; |
| D.deployLabel.textContent = 'Live ◈'; |
| D.deployText.textContent = 'Verified — Production ready'; |
| break; |
| default: |
| D.publishBtn.disabled = true; |
| D.deployText.textContent = 'RLM idle'; |
| } |
| } |
|
|
| |
| async function silentHandshake(urls) { |
| for (const url of urls) { |
| try { |
| const resp = await fetch('/api/verify-handshake', { |
| method:'POST', |
| headers:{'Content-Type':'application/json'}, |
| body:JSON.stringify({url}), |
| }); |
| const data = await resp.json(); |
| if (data.verified) { |
| S.handshakeVerified = true; |
| return {verified:true, url:data.url, latency:data.latency_ms}; |
| } |
| } catch {} |
| } |
| return {verified:false}; |
| } |
|
|
| |
| async function pollUntilLive(url, maxAttempts=15) { |
| for (let i = 0; i < maxAttempts; i++) { |
| try { |
| const resp = await fetch('/api/verify-handshake', { |
| method:'POST', |
| headers:{'Content-Type':'application/json'}, |
| body:JSON.stringify({url}), |
| }); |
| const data = await resp.json(); |
| if (data.verified && data.checks?.status_code === 200) { |
| return {live:true, url, latency:data.latency_ms}; |
| } |
| } catch {} |
| await new Promise(r => setTimeout(r, 2000)); |
| } |
| return {live:false, url}; |
| } |
|
|
| |
| async function forge() { |
| const prompt = D.prompt.value.trim(); if (!prompt) return; |
| S.fullCode = ''; S.previewReady = false; S.publishReady = false; |
| S.handshakeVerified = false; S.handshakeUrl = null; S.deployedUrl = null; |
|
|
| |
| foldContext({lastPrompt: prompt}); |
|
|
| |
| document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); |
| document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active')); |
| document.querySelector('.tab-btn[data-tab="forge"]').classList.add('active'); |
| D.panelForge.classList.add('active'); |
| D.previewTabBtn.disabled = true; |
| D.previewTabBtn.classList.add('locked'); |
| D.previewTabBtn.querySelector('.tab-label').textContent = '🔒 Preview'; |
|
|
| D.codeContent.innerHTML = '<span class="placeholder">// Obsidian Forge invoking RLM recursion…</span>'; |
| D.charCount.textContent = ''; |
| D.previewOverlay.classList.remove('hidden'); |
| D.previewFrame.src = 'about:blank'; |
| D.publishResult.style.display = 'none'; |
| setDeployState('idle'); |
| D.vibeBtn.disabled = true; |
|
|
| |
| clearThoughtStream(); |
| pulseWizard(); |
| addThoughtSentence('pose'); |
|
|
| try { |
| for await (const ev of streamFromServer(prompt)) { |
| if (ev.phase) { |
| addThoughtSentence(ev.phase); |
| } |
| if (ev.pose) { |
| addThoughtSentence('pose'); |
| } |
| if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; } |
| if (ev.partial) updateIframe(ev.partial); |
|
|
| if (ev.sandbox) { |
| stopWizard(ev.status === 'stable' ? 'stable' : ev.status === 'error' ? 'error' : null); |
| if (ev.status === 'stable' || ev.status === 'published') { |
| unlockPreview(); |
| setDeployState('ready'); |
| addThoughtSentence('done'); |
| |
| if (S.fullCode) launchQuantumSandbox(S.fullCode); |
| } else if (ev.status === 'error') { |
| setDeployState('idle'); |
| } |
| } |
| } |
| } catch (err) { |
| console.error('Forge error:', err); |
| stopWizard('error'); |
| addThoughtSentence('heal'); |
| D.deployText.textContent = `Error: ${err.message}`; |
| } finally { |
| D.vibeBtn.disabled = false; |
| if (!S.publishReady) stopWizard(null); |
| |
| foldContext({lastOutputLen: S.fullCode.length, lastForged: Date.now()}); |
| } |
| } |
|
|
| |
| async function deploy() { |
| if (!S.publishReady && !S.fullCode) return; |
|
|
| |
| setDeployState('verifying'); |
| clearThoughtStream(); |
| addThoughtSentence('deploy'); |
|
|
| const hk = await silentHandshake(['litheat.app', 'huggingface.co']); |
| if (hk.verified) { |
| addThoughtSentence('deploy'); |
| } else { |
| |
| await new Promise(r => setTimeout(r, 1500)); |
| const hk2 = await silentHandshake(['litheat.app']); |
| if (hk2.verified) { |
| addThoughtSentence('deploy'); |
| } else { |
| D.deployText.textContent = 'Handshake failed — retry'; |
| setDeployState('ready'); |
| return; |
| } |
| } |
|
|
| |
| setDeployState('deploying'); |
| const repoName = `obsidian-${Date.now().toString(36)}`; |
| addThoughtSentence('deploy'); |
|
|
| 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; |
| const rawUrl = r.spaces?.url || r.tunnel_url || ''; |
| S.deployedUrl = rawUrl; |
|
|
| |
| D.deployText.textContent = 'Polling deployment…'; |
| D.deployLabel.textContent = 'Checking…'; |
| const live = await pollUntilLive(rawUrl); |
|
|
| if (live.live) { |
| |
| stopWizard('deployed'); |
| setDeployState('deployed'); |
| D.publishResult.style.display = 'block'; |
| D.liveLink.href = live.url; |
| D.liveLink.textContent = live.url; |
| foldContext({lastDeployUrl: live.url, lastDeployed: Date.now()}); |
| addThoughtSentence('deploy'); |
| D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'}); |
| } else { |
| |
| stopWizard('deployed'); |
| setDeployState('deployed'); |
| D.publishResult.style.display = 'block'; |
| D.liveLink.href = rawUrl; |
| D.liveLink.textContent = rawUrl + ' (propagating)'; |
| D.deployText.textContent = 'Deployed — warming up'; |
| } |
| } else { |
| D.deployText.textContent = `Error: ${r.error || r.space_error || 'Unknown'}`; |
| setDeployState('ready'); |
| } |
| } catch (err) { |
| D.deployText.textContent = `Error: ${err.message}`; |
| setDeployState('ready'); |
| } |
| } |
|
|
| |
| D.popoutBtn.addEventListener('click', () => { |
| if (S.fullCode) launchQuantumSandbox(S.fullCode); |
| }); |
|
|
| |
| D.vibeBtn.addEventListener('click', forge); |
| D.publishBtn.addEventListener('click', deploy); |
| D.prompt.addEventListener('keydown', e => { |
| if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); forge(); } |
| }); |
| D.copyBtn.addEventListener('click', async () => { |
| if (!S.fullCode) return; |
| try { await navigator.clipboard.writeText(S.fullCode); } catch {} |
| }); |
|
|
| |
| D.prompt.addEventListener('input', () => { |
| D.prompt.style.height = 'auto'; |
| D.prompt.style.height = Math.min(D.prompt.scrollHeight, 110) + 'px'; |
| }); |
|
|
| |
| (function restoreContext() { |
| const ctx = unfoldContext(); |
| if (ctx.lastPrompt) { |
| D.prompt.value = ctx.lastPrompt; |
| D.prompt.style.height = 'auto'; |
| D.prompt.style.height = Math.min(D.prompt.scrollHeight, 110) + 'px'; |
| } |
| })(); |
|
|
| console.log('◈ Obsidian Forge — Kinetic RLM Environment ready'); |
| console.log(' RLM: Recursive Language Modeling active'); |
| console.log(' Context Folding: localStorage persistence'); |
| console.log(' Quantum Sandbox: dedicated tab sync'); |
| console.log(' Handshake: silent DNS + poll-until-live'); |