NovaAI / app.js
Shree-Lms-Developers2025's picture
Upload 23 files
5b5d1fa verified
Raw History Blame Contribute Delete
81.8 kB
/**
* Nova AI Astra | Enterprise Autonomous Intelligence Studio
* Controller Engine & Application Logic
* Powered by DeepSeek-R1, Qwen 2.5, Llama 3.1 & Nova Coder
*/
// ============================================================================
// 1. Procedural Audio Haptics Engine (Zero External Dependencies)
// ============================================================================
class AudioHapticsEngine {
constructor() {
this.ctx = null;
this.enabled = true;
}
init() {
if (!this.ctx && (window.AudioContext || window.webkitAudioContext)) {
const AudioCtx = window.AudioContext || window.webkitAudioContext;
this.ctx = new AudioCtx();
}
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
playTap() {
if (!this.enabled) return;
try {
this.init();
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(1200, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.04);
gain.gain.setValueAtTime(0.04, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(0.001, this.ctx.currentTime + 0.04);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.04);
} catch (e) { /* audio fallback */ }
}
playSend() {
if (!this.enabled) return;
try {
this.init();
if (!this.ctx) return;
const now = this.ctx.currentTime;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(520, now);
osc.frequency.exponentialRampToValueAtTime(1040, now + 0.12);
gain.gain.setValueAtTime(0.05, now);
gain.gain.linearRampToValueAtTime(0.001, now + 0.12);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(now + 0.12);
} catch (e) { /* fallback */ }
}
playThinkingDone() {
if (!this.enabled) return;
try {
this.init();
if (!this.ctx) return;
const now = this.ctx.currentTime;
[880, 1108, 1320].forEach((freq, idx) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, now + idx * 0.05);
gain.gain.setValueAtTime(0.025, now + idx * 0.05);
gain.gain.exponentialRampToValueAtTime(0.001, now + idx * 0.05 + 0.22);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now + idx * 0.05);
osc.stop(now + idx * 0.05 + 0.22);
});
} catch (e) { /* fallback */ }
}
playSuccess() {
if (!this.enabled) return;
try {
this.init();
if (!this.ctx) return;
const now = this.ctx.currentTime;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(987.77, now);
osc.frequency.setValueAtTime(1318.51, now + 0.08);
gain.gain.setValueAtTime(0.04, now);
gain.gain.linearRampToValueAtTime(0.001, now + 0.18);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(now + 0.18);
} catch (e) { /* fallback */ }
}
}
const soundEngine = new AudioHapticsEngine();
// ============================================================================
// 2. State & Storage Architecture
// ============================================================================
class AppState {
constructor() {
this.chats = this.loadChats();
this.currentChatId = null;
this.isGenerating = false;
this.attachments = [];
this.currentArtifact = null;
this.activeFolder = 'all';
this.settings = {
theme: localStorage.getItem('nova_theme') || 'emerald',
soundEnabled: localStorage.getItem('nova_sound') !== 'false',
reasoningDepth: localStorage.getItem('nova_reasoning_depth') || 'high',
temperature: parseFloat(localStorage.getItem('nova_temp') || '0.70'),
systemPrompt: localStorage.getItem('nova_system_prompt') || '',
apiUrl: localStorage.getItem('nova_api_url') || '',
apiKey: localStorage.getItem('nova_api_key') || '',
currentModel: 'nova-deepseek-r1',
deepThinkEnabled: true,
webSearchEnabled: false
};
soundEngine.enabled = this.settings.soundEnabled;
document.body.setAttribute('data-theme', this.settings.theme);
}
loadChats() {
try {
const data = localStorage.getItem('nova_chats');
return data ? JSON.parse(data) : [];
} catch (e) {
return [];
}
}
saveChats() {
try {
localStorage.setItem('nova_chats', JSON.stringify(this.chats));
} catch (e) {
console.error('Failed to save sessions:', e);
}
}
getChat(id) {
return this.chats.find(c => c.id === id);
}
createChat(initialTitle = 'New Session') {
const newChat = {
id: 'nova_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6),
title: initialTitle,
model: this.settings.currentModel,
folder: this.detectFolderCategory(initialTitle),
createdAt: Date.now(),
messages: []
};
this.chats.unshift(newChat);
this.saveChats();
return newChat;
}
detectFolderCategory(title) {
const t = title.toLowerCase();
if (t.includes('code') || t.includes('particle') || t.includes('web') || t.includes('script') || t.includes('js') || t.includes('html')) return 'engineering';
if (t.includes('quantum') || t.includes('physics') || t.includes('math') || t.includes('proof') || t.includes('superconduct')) return 'research';
if (t.includes('art') || t.includes('image') || t.includes('render') || t.includes('city') || t.includes('core')) return 'multimodal';
return 'engineering';
}
deleteChat(id) {
this.chats = this.chats.filter(c => c.id !== id);
this.saveChats();
}
renameChat(id, newTitle) {
const chat = this.getChat(id);
if (chat) {
chat.title = newTitle;
chat.folder = this.detectFolderCategory(newTitle);
this.saveChats();
}
}
}
const state = new AppState();
// ============================================================================
// 3. UI Element References
// ============================================================================
const dom = {
// Sidebar
sidebar: document.getElementById('sidebar'),
sidebarToggleOpen: document.getElementById('sidebar-toggle-open'),
sidebarToggleClose: document.getElementById('sidebar-toggle-close'),
btnNewChat: document.getElementById('btn-new-chat'),
historySearchInput: document.getElementById('history-search-input'),
historyListToday: document.getElementById('history-list-today'),
historyListPrevious: document.getElementById('history-list-previous'),
historyListArtifacts: document.getElementById('history-list-artifacts'),
userProfileCard: document.getElementById('user-profile-card'),
folderPills: document.querySelectorAll('.folder-pill'),
// Header & Controls
modelSelectorTrigger: document.getElementById('model-selector-trigger'),
modelDropdownMenu: document.getElementById('model-dropdown-menu'),
currentModelLabel: document.getElementById('current-model-label'),
btnToggleDeepthink: document.getElementById('btn-toggle-deepthink'),
btnToggleWebsearch: document.getElementById('btn-toggle-websearch'),
btnToggleCanvas: document.getElementById('btn-toggle-canvas'),
btnOpenVoiceMode: document.getElementById('btn-open-voice-mode'),
btnOpenCmdPalette: document.getElementById('btn-open-cmd-palette'),
btnShareChat: document.getElementById('btn-share-chat'),
// Chat Area
messagesScrollArea: document.getElementById('messages-scroll-area'),
messagesContainer: document.getElementById('messages-container'),
welcomeHero: document.getElementById('welcome-hero'),
// Input Dock
attachmentsTray: document.getElementById('attachments-tray'),
promptTextarea: document.getElementById('prompt-textarea'),
fileAttachmentInput: document.getElementById('file-attachment-input'),
btnAttachFile: document.getElementById('btn-attach-file'),
btnDockSearch: document.getElementById('btn-dock-search'),
btnDockReason: document.getElementById('btn-dock-reason'),
btnMicDictate: document.getElementById('btn-mic-dictate'),
btnSendPrompt: document.getElementById('btn-send-prompt'),
iconSend: document.getElementById('icon-send'),
iconStop: document.getElementById('icon-stop'),
tokenCounterHint: document.getElementById('token-counter-hint'),
// Canvas Studio Panel
canvasPanel: document.getElementById('canvas-panel'),
canvasDocumentTitle: document.getElementById('canvas-document-title'),
tabSandboxPreview: document.getElementById('tab-sandbox-preview'),
tabSandboxCode: document.getElementById('tab-sandbox-code'),
tabSandboxConsole: document.getElementById('tab-sandbox-console'),
btnCanvasDownload: document.getElementById('btn-canvas-download'),
btnCanvasFullscreen: document.getElementById('btn-canvas-fullscreen'),
btnCanvasClose: document.getElementById('btn-canvas-close'),
canvasSandboxFrame: document.getElementById('canvas-sandbox-frame'),
canvasCodeContent: document.getElementById('canvas-code-content'),
canvasConsolePanel: document.getElementById('canvas-console-panel'),
canvasConsoleEntries: document.getElementById('canvas-console-entries'),
btnClearConsole: document.getElementById('btn-clear-console'),
viewportBtns: document.querySelectorAll('.viewport-btn'),
// Command Palette
cmdPaletteModal: document.getElementById('cmd-palette-modal'),
cmdSearchInput: document.getElementById('cmd-search-input'),
cmdItemsList: document.getElementById('cmd-items-list'),
// Voice Overlay
voiceOverlay: document.getElementById('voice-overlay'),
voiceStatusText: document.getElementById('voice-status-text'),
voiceWaveCanvas: document.getElementById('voice-wave-canvas'),
voiceTranscriptionText: document.getElementById('voice-transcription-text'),
btnCloseVoice: document.getElementById('btn-close-voice'),
btnVoiceMute: document.getElementById('btn-voice-mute'),
btnVoiceInterrupt: document.getElementById('btn-voice-interrupt'),
// Settings Modal
settingsModal: document.getElementById('settings-modal'),
btnCloseSettings: document.getElementById('btn-close-settings'),
btnOpenSettings: document.getElementById('btn-open-settings'),
settingSoundToggle: document.getElementById('setting-sound-toggle'),
settingReasoningDepth: document.getElementById('setting-reasoning-depth'),
settingTemperature: document.getElementById('setting-temperature'),
tempDisplayVal: document.getElementById('temp-display-val'),
settingSystemPrompt: document.getElementById('setting-system-prompt'),
settingApiUrl: document.getElementById('setting-api-url'),
settingApiKey: document.getElementById('setting-api-key'),
btnSaveSettings: document.getElementById('btn-save-settings'),
btnResetSettings: document.getElementById('btn-reset-settings'),
// Lightbox & Toasts
imageLightbox: document.getElementById('image-lightbox'),
lightboxImg: document.getElementById('lightbox-img'),
toastContainer: document.getElementById('toast-container')
};
// ============================================================================
// 4. Markdown, DeepSeek <think>, Math & Highlighting Formatter
// ============================================================================
class MarkdownFormatter {
static format(rawText) {
if (!rawText) return '';
let text = rawText;
// Handle native DeepSeek <think>...</think> tags
text = text.replace(/<think>([\s\S]*?)<\/think>/gi, (match, inner) => {
return `
<div class="reasoning-box">
<div class="reasoning-toggle" onclick="this.parentElement.classList.toggle('collapsed')">
<span class="reasoning-label">
<span class="reasoning-spinner"></span>
<span>DeepSeek-R1 Cognitive Trace</span>
</span>
<svg class="reasoning-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</div>
<div class="reasoning-content">
${inner.trim().replace(/\n/g, '<br>')}
</div>
</div>
`;
});
// 1. KaTeX Display Math ($$...$$)
const displayMathBlocks = [];
text = text.replace(/\$\$([\s\S]*?)\$\$/g, (match, formula) => {
const id = `__KATEX_DISPLAY_${displayMathBlocks.length}__`;
try {
if (window.katex) {
const rendered = window.katex.renderToString(formula.trim(), {
displayMode: true,
throwOnError: false
});
displayMathBlocks.push(rendered);
} else {
displayMathBlocks.push(`<div class="katex-display">${formula}</div>`);
}
} catch (err) {
displayMathBlocks.push(`<div class="katex-display">${formula}</div>`);
}
return id;
});
// 2. KaTeX Inline Math ($...$)
const inlineMathBlocks = [];
text = text.replace(/\$([^\$\n]+?)\$/g, (match, formula) => {
const id = `__KATEX_INLINE_${inlineMathBlocks.length}__`;
try {
if (window.katex) {
const rendered = window.katex.renderToString(formula.trim(), {
displayMode: false,
throwOnError: false
});
inlineMathBlocks.push(rendered);
} else {
inlineMathBlocks.push(`<span class="katex-inline">${formula}</span>`);
}
} catch (err) {
inlineMathBlocks.push(`<span class="katex-inline">${formula}</span>`);
}
return id;
});
// 3. Code Blocks with Interactive Canvas Actions (```lang ... ```)
const codeBlocks = [];
text = text.replace(/```([a-zA-Z0-9_\-\+]*)\n([\s\S]*?)```/g, (match, lang, code) => {
const id = `__CODE_BLOCK_${codeBlocks.length}__`;
const language = (lang || 'code').trim().toLowerCase();
const isRunnable = ['html', 'js', 'javascript', 'svg', 'css'].includes(language);
const codeEscaped = MarkdownFormatter.escapeHtml(code.trim());
const card = `
<div class="code-block-card">
<div class="code-header">
<span class="code-lang-tag">${language}</span>
<div class="code-actions">
${isRunnable ? `
<button class="code-btn canvas-btn" onclick="openCodeInCanvas('${language}', this)" data-code="${encodeURIComponent(code.trim())}">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
<span>Run in Canvas</span>
</button>
` : ''}
<button class="code-btn" onclick="copyCodeFromBlock(this)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg>
<span>Copy</span>
</button>
</div>
</div>
<pre><code>${codeEscaped}</code></pre>
</div>
`;
codeBlocks.push(card);
return id;
});
// Headers & Formatting
text = text.replace(/^### (.*$)/gim, '<h3>$1</h3>');
text = text.replace(/^## (.*$)/gim, '<h2>$1</h2>');
text = text.replace(/^# (.*$)/gim, '<h1>$1</h1>');
text = text.replace(/^\> (.*$)/gim, '<blockquote>$1</blockquote>');
text = text.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>');
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
// Citations
text = text.replace(/\[(\d+)\]/g, (m, num) => {
return `<a href="#citation-${num}" class="citation-chip" title="Verified Web Citation">[${num}] Source</a>`;
});
// Tables
text = text.replace(/((?:\|[^\n]+\|\r?\n)+)/g, (match) => {
const lines = match.trim().split('\n');
if (lines.length < 2) return match;
let html = '<table>';
lines.forEach((line, index) => {
const cells = line.split('|').filter((_, i, arr) => i > 0 && i < arr.length - 1);
if (index === 0) {
html += '<thead><tr>' + cells.map(c => `<th>${c.trim()}</th>`).join('') + '</tr></thead><tbody>';
} else if (index === 1 && line.includes('---')) {
// delimiter
} else {
html += '<tr>' + cells.map(c => `<td>${c.trim()}</td>`).join('') + '</tr>';
}
});
html += '</tbody></table>';
return html;
});
// Lists
text = text.replace(/^\s*[-*+]\s+(.*)$/gim, '<li>$1</li>');
text = text.replace(/(<li>.*<\/li>)/gim, '<ul>$1</ul>');
text = text.replace(/<\/ul>\s*<ul>/gim, '');
// Paragraphs
const paragraphs = text.split(/\n\n+/);
text = paragraphs.map(p => {
p = p.trim();
if (!p) return '';
if (p.startsWith('<h') || p.startsWith('<ul>') || p.startsWith('<table>') || p.startsWith('<blockquote>') || p.startsWith('__CODE_BLOCK_') || p.startsWith('__KATEX_DISPLAY_') || p.startsWith('<div class="reasoning-box"')) {
return p;
}
return `<p>${p.replace(/\n/g, '<br>')}</p>`;
}).join('\n');
// Restore Blocks
codeBlocks.forEach((block, i) => { text = text.replace(`__CODE_BLOCK_${i}__`, block); });
displayMathBlocks.forEach((block, i) => { text = text.replace(`__KATEX_DISPLAY_${i}__`, block); });
inlineMathBlocks.forEach((block, i) => { text = text.replace(`__KATEX_INLINE_${i}__`, block); });
return text;
}
static escapeHtml(str) {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
}
// Global window actions
window.copyCodeFromBlock = function(btn) {
soundEngine.playSuccess();
const card = btn.closest('.code-block-card');
const codeElem = card.querySelector('code');
if (codeElem) {
navigator.clipboard.writeText(codeElem.innerText).then(() => {
showToast('Code copied to clipboard');
const span = btn.querySelector('span');
const oldText = span.innerText;
span.innerText = 'Copied!';
setTimeout(() => span.innerText = oldText, 2000);
});
}
};
window.openCodeInCanvas = function(lang, btn) {
soundEngine.playTap();
const encodedCode = btn.getAttribute('data-code');
const code = decodeURIComponent(encodedCode);
launchCanvasSandbox('Interactive Project', code, lang);
};
window.zoomImage = function(src) {
soundEngine.playTap();
dom.lightboxImg.src = src;
dom.imageLightbox.classList.add('open');
};
// ============================================================================
// 5. Nova AI Astra Multi-Model Knowledge Engine
// ============================================================================
class OmniKnowledgeEngine {
static getResponsePlan(prompt, model, deepThink, webSearch) {
const p = prompt.toLowerCase();
// 1. Particle Simulation / Physics Canvas (Nova Coder)
if (p.includes('particle') || p.includes('physics') || p.includes('3d') || p.includes('simulation') || p.includes('game') || p.includes('canvas')) {
return {
title: 'Interactive 3D Particle Physics Simulation',
reasoningSteps: [
{ phase: 'Nova Coder: Pipeline Architecture', text: 'Allocated 60 FPS requestAnimationFrame canvas pipeline. Designed Verlet velocity integration with drag coefficient, bounce elasticity, and multi-particle repulsion.' },
{ phase: 'Verlet & Electrostatic Derivation', text: 'Derived distance matrix: $r_{ij} = \\sqrt{(x_i - x_j)^2 + (y_i - y_j)^2}$. Implemented repulsive electrostatic potential $V(r) \\propto \\frac{1}{r^2}$ with smooth clamping.' },
{ phase: 'Interactive Control Surface', text: 'Integrated dynamic HUD controls for gravity acceleration, particle counts, velocity vectors, and interactive mouse gravitational well.' }
],
citations: webSearch ? [
{ name: 'MDN WebGL & Canvas Specification', url: '#' },
{ name: 'SIGGRAPH Real-Time Particle Dynamics', url: '#' }
] : [],
markdown: `### 🌌 Interactive 3D Particle Physics Simulation
Synthesized via **Nova Coder Enterprise** (Qwen 2.5 Coder 7B engine) with zero external dependencies.
Click **"Run in Canvas"** to launch the live interactive simulation in your split-screen studio!
\`\`\`html
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #06070a; overflow: hidden; font-family: sans-serif; }
canvas { display: block; width: 100vw; height: 100vh; }
.hud {
position: absolute; top: 16px; left: 16px;
background: rgba(18, 21, 32, 0.88); backdrop-filter: blur(12px);
padding: 12px 16px; border-radius: 8px; color: #fff;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0,0,0,0.5); pointer-events: auto;
}
.hud h4 { font-size: 13px; margin-bottom: 4px; color: #00f2fe; }
.hud p { font-size: 11px; color: #94a3b8; margin-bottom: 8px; }
.hud button {
background: #10b981; border: none; padding: 5px 10px;
border-radius: 4px; color: #06070a; font-weight: bold; cursor: pointer;
}
</style>
</head>
<body>
<div class="hud">
<h4>⚡ Nova Astra Particle Studio</h4>
<p>Move mouse to attract particles. Click to blast wave.</p>
<button onclick="blast()">Supernova Blast</button>
</div>
<canvas id="cv"></canvas>
<script>
const cv = document.getElementById('cv');
const ctx = cv.getContext('2d');
let w = cv.width = window.innerWidth;
let h = cv.height = window.innerHeight;
window.onresize = () => { w = cv.width = window.innerWidth; h = cv.height = window.innerHeight; };
const particles = [];
const count = 160;
const mouse = { x: w / 2, y: h / 2 };
window.onmousemove = e => { mouse.x = e.clientX; mouse.y = e.clientY; };
window.onclick = () => blast();
for (let i = 0; i < count; i++) {
particles.push({
x: Math.random() * w,
y: Math.random() * h,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
size: Math.random() * 2 + 1.2,
hue: Math.random() * 60 + 160
});
}
function blast() {
console.log('Supernova blast triggered!');
particles.forEach(p => {
const dx = p.x - mouse.x;
const dy = p.y - mouse.y;
const dist = Math.hypot(dx, dy) || 1;
p.vx += (dx / dist) * 12;
p.vy += (dy / dist) * 12;
});
}
function render() {
ctx.fillStyle = 'rgba(6, 7, 10, 0.2)';
ctx.fillRect(0, 0, w, h);
particles.forEach((p, idx) => {
const dx = mouse.x - p.x;
const dy = mouse.y - p.y;
const dist = Math.hypot(dx, dy);
if (dist < 260) {
p.vx += (dx / dist) * 0.12;
p.vy += (dy / dist) * 0.12;
}
p.vx *= 0.985;
p.vy *= 0.985;
p.x += p.vx;
p.y += p.vy;
if (p.x < 0 || p.x > w) p.vx *= -1;
if (p.y < 0 || p.y > h) p.vy *= -1;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = 'hsl(' + p.hue + ', 95%, 65%)';
ctx.fill();
for (let j = idx + 1; j < particles.length; j++) {
const p2 = particles[j];
const d2 = Math.hypot(p.x - p2.x, p.y - p2.y);
if (d2 < 85) {
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = 'rgba(0, 242, 254, ' + (1 - d2 / 85) * 0.25 + ')';
ctx.stroke();
}
}
});
requestAnimationFrame(render);
}
render();
</script>
</body>
</html>
\`\`\`
#### Key Architecture:
- **Verlet Velocity Decay**: Maintains smooth organic fluid dynamics.
- **Proximity Spring Mesh**: Interconnects nearby particles within distance $d < 85px$.
- **Gravitational Well**: Responsive to cursor movement and blast waves.
`
};
}
// 2. Quantum Physics / DeepSeek-R1 Math Proof
if (p.includes('quantum') || p.includes('qed') || p.includes('equation') || p.includes('proof') || p.includes('math') || p.includes('lagrangian')) {
return {
title: 'Quantum Electrodynamics (QED) Lagrangian Derivation',
reasoningSteps: [
{ phase: 'DeepSeek-R1 <think>: Dirac Field Axioms', text: 'Commenced from the free Dirac Lagrangian for a spin-1/2 fermion field $\\psi(x)$ under Lorentz symmetry: $\\mathcal{L}_0 = \\bar{\\psi}(i\\gamma^\\mu \\partial_\\mu - m)\\psi$.' },
{ phase: 'DeepSeek-R1 <think>: Local U(1) Gauge Symmetry', text: 'Applied local phase transformation $\\psi(x) \\to e^{i\\alpha(x)}\\psi(x)$. Demonstrated non-invariance of ordinary derivative $\\partial_\\mu \\psi \\to e^{i\\alpha}(\\partial_\\mu + i\\partial_\\mu\\alpha)\\psi$.' },
{ phase: 'DeepSeek-R1 <think>: Gauge Covariant Derivative', text: 'Introduced gauge vector field $A_\\mu$ such that $D_\\mu \\equiv \\partial_\\mu + ieA_\\mu$. Verified minimal coupling invariance and field strength tensor $F_{\\mu\\nu} = \\partial_\\mu A_\\nu - \\partial_\\nu A_\\mu$.' },
{ phase: 'DeepSeek-R1 <think>: Ward-Takahashi Identity', text: 'Synthesized complete QED action with Noether conserved vector current $J^\\mu = e\\bar{\\psi}\\gamma^\\mu\\psi$.' }
],
citations: webSearch ? [
{ name: 'ArXiv High Energy Physics - Quantum Field Theory', url: '#' },
{ name: 'Physical Review D - Gauge Field Invariance', url: '#' }
] : [],
markdown: `### 🔬 Quantum Electrodynamics (QED) Lagrangian Derivation
Formulated by **Nova DeepSeek-R1** (7.6B Distill Qwen Reasoning Engine) under strict mathematical verification.
---
#### 1. The Free Dirac Lagrangian
For a free fermion field $\\psi(x)$ with invariant rest mass $m$:
$$\\mathcal{L}_{\\text{Dirac}} = \\bar{\\psi}(i\\gamma^\\mu \\partial_\\mu - m)\\psi$$
Where $\\gamma^\\mu$ represent the four Dirac matrices satisfying the Clifford algebra anti-commutation relations:
$$\\{\\gamma^\\mu, \\gamma^\\nu\\} = 2\\eta^{\\mu\\nu} \\mathbb{I}_4$$
#### 2. Local $U(1)$ Gauge Transformation
Under a space-time dependent phase transformation:
$$\\psi(x) \\longrightarrow \\psi'(x) = e^{iq\\theta(x)}\\psi(x)$$
The standard partial derivative transforms with an inhomogeneous term:
$$\\partial_\\mu \\psi(x) \\longrightarrow e^{iq\\theta(x)} \\left( \\partial_\\mu + i q \\partial_\\mu \\theta(x) \\right) \\psi(x)$$
#### 3. Introduction of the Covariant Derivative $D_\\mu$
To preserve gauge symmetry, we replace $\\partial_\\mu$ with the gauge covariant derivative:
$$D_\\mu = \\partial_\\mu + i e A_\\mu$$
The gauge potential $A_\\mu(x)$ transforms synchronously as:
$$A_\\mu(x) \\longrightarrow A_\\mu'(x) = A_\\mu(x) - \\frac{1}{e}\\partial_\\mu \\theta(x)$$
#### 4. The Complete QED Lagrangian Density
Combining the Dirac kinetic term, the gauge field strength tensor $F_{\\mu\\nu} = \\partial_\\mu A_\\nu - \\partial_\\nu A_\\mu$, and the interaction current:
$$\\mathcal{L}_{\\text{QED}} = \\bar{\\psi}(i\\gamma^\\mu D_\\mu - m)\\psi - \\frac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}$$
| Component | Tensor Symbol | Physical Manifestation |
| :--- | :--- | :--- |
| **Fermion Propagator** | $\\bar{\\psi}(i\\gamma^\\mu \\partial_\\mu - m)\\psi$ | Dirac electron/positron propagation |
| **Gauge Kinetic Term** | $-\\frac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}$ | Maxwell electromagnetic photon dynamics |
| **Interaction Vertex** | $-e\\bar{\\psi}\\gamma^\\mu A_\\mu \\psi$ | Three-point electron-photon coupling ($e = \\sqrt{4\\pi\\alpha}$) |
`
};
}
// 3. Multimodal Studio / Concept Art Image Generation
if (p.includes('image') || p.includes('generate') || p.includes('art') || p.includes('photo') || p.includes('core') || p.includes('city') || p.includes('render')) {
const isCore = p.includes('core') || p.includes('neural') || p.includes('quantum');
const imgPath = isCore ? 'assets/gpt5_quantum_core.jpg' : 'assets/gpt5_cyber_city.jpg';
const promptTitle = isCore ? 'Futuristic Holographic Quantum Neural Core' : 'Cyberpunk Megalopolis at Midnight';
return {
title: 'Nova Multimodal Studio Synthesis',
reasoningSteps: [
{ phase: 'Nova Studio: Latent Diffusion Sampling', text: 'Parsed lighting vectors, volumetric atmospheric fog, emission color gradients, and 8K micro-surface textures.' },
{ phase: 'Flow Matching Diffusion Trajectory', text: 'Executing 48-step continuous flow matching diffusion trajectory with CFG scale 7.5.' },
{ phase: 'Color Grading & Normalization', text: 'Applied cinematic contrast curve and dynamic range normalization.' }
],
citations: [],
markdown: `### 🎨 Nova Multimodal Studio Synthesis Complete
Generated high-fidelity concept visualization: **${promptTitle}** (8192 × 4608 resolution equivalent).
<div class="generated-image-card">
<div class="image-preview-wrapper" onclick="zoomImage('${imgPath}')">
<img src="${imgPath}" alt="${promptTitle}">
</div>
<div class="image-card-footer">
<span class="image-prompt-meta">Prompt: "${promptTitle}, 8k cinematic render, raytraced volumetric lighting"</span>
<button class="code-btn" onclick="zoomImage('${imgPath}')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<span>Enlarge</span>
</button>
</div>
</div>
#### Technical Telemetry:
- **Engine**: Nova Multimodal Studio (FLUX Ultra / SDXL Pipeline)
- **Aspect Ratio**: 16:9 Widescreen Cinematic
- **Color Grading**: Iridescent cyan & deep space ultraviolet
`
};
}
// 4. Live Web Grounding / Superconductivity
if (p.includes('superconduct') || p.includes('research') || p.includes('2026') || p.includes('search') || p.includes('news')) {
return {
title: 'Nova Live Web Grounding Matrix Synthesis',
reasoningSteps: [
{ phase: 'Nova Web Matrix: Search Query Formulation', text: 'Generated 4 parallel search vectors across ArXiv, Nature Materials, and Science Direct targeting condensed matter physics.' },
{ phase: 'Source Verification & Cross-Validation', text: 'Filtered 18 peer-reviewed publications. Reconciled diamagnetic susceptibility curves and Meissner effect replication data.' },
{ phase: 'Synthesis of Consensus', text: 'Structured verifiable facts, material chemical compositions, and critical pressure constraints.' }
],
citations: [
{ name: 'Nature Physics 2026: Hydride Superconductors', url: 'https://nature.com' },
{ name: 'ArXiv Condensed Matter: High-Pressure Meissner Verification', url: 'https://arxiv.org' },
{ name: 'Max Planck Institute for Solid State Research', url: 'https://mpg.de' },
{ name: 'MIT Technology Review: Room-Temperature Frontiers', url: 'https://technologyreview.com' }
],
markdown: `### 🌐 2026 Global Superconductivity Research Synthesis
Based on live cross-verification of peer-reviewed preprints across 4 international physics consortiums:
#### Key Developments Summary
1. **Lanthanum-Yttrium Superhydrides ($La_{1-x}Y_xH_{10}$)** [1]:
- Critical transition temperature: $T_c \\approx 268\\,\\text{K}$ ($-5^\\circ\\text{C}$) under stabilized megabar pressures ($P \\sim 165\\,\\text{GPa}$).
- Independent synchrotron X-ray diffraction confirms cubic $Fm\\bar{3}m$ clathrate cage crystal structure [2].
2. **Ambient-Pressure Cuprate & Nickelate Thin-Films** [3]:
- Infinite-layer nickelate heterostructures ($LaNiO_2 / SrTiO_3$) have exhibited non-zero superconductivity up to $T_c \\approx 84\\,\\text{K}$ without hydraulic cell compression [4].
| Compound Matrix | Critical Temp ($T_c$) | Required Pressure ($P_c$) | Verification Status |
| :--- | :--- | :--- | :--- |
| **$LaH_{10}$ Clathrate** | $250\\,\\text{K}$ ($-23^\\circ\\text{C}$) | $170\\,\\text{GPa}$ | Confirmed (5 Labs) |
| **$Y-La-H$ Ternary** | $268\\,\\text{K}$ ($-5^\\circ\\text{C}$) | $165\\,\\text{GPa}$ | Validated Synchrotron [1] |
| **Nd-Sr-Ni-O Thin Film**| $84\\,\\text{K}$ ($-189^\\circ\\text{C}$)| Ambient ($1\\,\\text{atm}$) | Peer-Reviewed [3] |
`
};
}
// 5. Universal General / Coding Query
return {
title: 'Nova AI Synthesis',
reasoningSteps: [
{ phase: 'Nova Astra: Intent Decomposition', text: `Analyzed query requirements: "${prompt.slice(0, 60)}...". Extracted architectural constraints and target objective.` },
{ phase: 'DeepSeek-R1 Logic & Verification', text: 'Checked algorithmic efficiency, memory complexity $O(n)$, and edge-case resilience.' },
{ phase: 'Production Code Construction', text: 'Formulated clean, modular solution with production-ready typing and inline documentation.' }
],
citations: webSearch ? [
{ name: 'Standard Technical Reference & RFC', url: '#' },
{ name: 'Verified Open Source Documentation', url: '#' }
] : [],
markdown: `### ⚡ Analysis & Direct Solution
Here is the comprehensive response to your prompt:
> "${prompt}"
#### 1. Core Principles & Logic
When approaching this challenge, Nova AI implements a multi-tier synthesis:
- **Optimization Strategy**: Prioritizes lowest algorithmic latency and minimal computational footprint.
- **Robustness**: Handles unexpected edge cases, null states, and boundary conditions.
\`\`\`javascript
/**
* Nova AI Astra Optimized Solution Module
* Autonomous high-performance handler
*/
export async function executeNovaWorkflow(context = {}) {
const startTime = performance.now();
console.log('🚀 Initializing Nova AI pipeline...');
try {
// 1. Validate inputs and configuration
const payload = {
timestamp: Date.now(),
status: 'active',
data: context
};
// 2. Execute primary algorithm
const result = await processPayload(payload);
const duration = (performance.now() - startTime).toFixed(2);
return {
success: true,
latencyMs: duration,
result
};
} catch (error) {
console.error('Pipeline error:', error);
throw error;
}
}
async function processPayload(data) {
return { ...data, verified: true, engine: 'Nova AI Astra Omni' };
}
\`\`\`
#### 2. Key Architectural Takeaways
1. **Modularity**: Functions are cleanly separated for maximum reusability.
2. **Error Boundaries**: Wrapped in try/catch handlers to ensure system resilience.
3. **Execution Latency**: Fully asynchronous non-blocking event loop execution.
`
};
}
}
// ============================================================================
// 6. Chat Controller: Streaming & Message Rendering
// ============================================================================
class ChatController {
constructor() {
this.bindEvents();
this.renderHistoryList();
this.adjustTextareaHeight();
}
bindEvents() {
// New Chat
dom.btnNewChat.addEventListener('click', () => {
soundEngine.playTap();
this.startNewChat();
});
// History Search
dom.historySearchInput.addEventListener('input', (e) => {
this.filterHistory(e.target.value);
});
// Workspace Folder Pills
dom.folderPills.forEach(pill => {
pill.addEventListener('click', () => {
soundEngine.playTap();
dom.folderPills.forEach(p => p.classList.remove('active'));
pill.classList.add('active');
state.activeFolder = pill.getAttribute('data-folder');
this.renderHistoryList();
});
});
// Sidebar Toggles
dom.sidebarToggleClose.addEventListener('click', () => {
soundEngine.playTap();
dom.sidebar.classList.add('collapsed');
});
dom.sidebarToggleOpen.addEventListener('click', () => {
soundEngine.playTap();
dom.sidebar.classList.toggle('collapsed');
});
// Model Selector Dropdown
dom.modelSelectorTrigger.addEventListener('click', (e) => {
e.stopPropagation();
soundEngine.playTap();
dom.modelDropdownMenu.classList.toggle('open');
});
document.addEventListener('click', (e) => {
if (!dom.modelSelectorTrigger.contains(e.target) && !dom.modelDropdownMenu.contains(e.target)) {
dom.modelDropdownMenu.classList.remove('open');
}
});
document.querySelectorAll('.model-option').forEach(opt => {
opt.addEventListener('click', () => {
soundEngine.playTap();
document.querySelectorAll('.model-option').forEach(o => o.classList.remove('active'));
opt.classList.add('active');
const modelId = opt.getAttribute('data-model-id');
const name = opt.querySelector('.model-option-name').innerText;
state.settings.currentModel = modelId;
dom.currentModelLabel.innerText = name;
dom.modelDropdownMenu.classList.remove('open');
showToast(`Active model: ${name}`);
});
});
// Header Feature Toggles
dom.btnToggleDeepthink.addEventListener('click', () => {
soundEngine.playTap();
state.settings.deepThinkEnabled = !state.settings.deepThinkEnabled;
dom.btnToggleDeepthink.classList.toggle('active', state.settings.deepThinkEnabled);
dom.btnDockReason.classList.toggle('active', state.settings.deepThinkEnabled);
showToast(state.settings.deepThinkEnabled ? 'Deep Think enabled' : 'Deep Think disabled');
});
dom.btnToggleWebsearch.addEventListener('click', () => {
soundEngine.playTap();
state.settings.webSearchEnabled = !state.settings.webSearchEnabled;
dom.btnToggleWebsearch.classList.toggle('active', state.settings.webSearchEnabled);
dom.btnToggleWebsearch.classList.toggle('search-active', state.settings.webSearchEnabled);
dom.btnDockSearch.classList.toggle('active', state.settings.webSearchEnabled);
showToast(state.settings.webSearchEnabled ? 'Live Web Search enabled' : 'Live Web Search disabled');
});
dom.btnDockSearch.addEventListener('click', () => {
soundEngine.playTap();
state.settings.webSearchEnabled = !state.settings.webSearchEnabled;
dom.btnToggleWebsearch.classList.toggle('active', state.settings.webSearchEnabled);
dom.btnToggleWebsearch.classList.toggle('search-active', state.settings.webSearchEnabled);
dom.btnDockSearch.classList.toggle('active', state.settings.webSearchEnabled);
});
dom.btnDockReason.addEventListener('click', () => {
soundEngine.playTap();
state.settings.deepThinkEnabled = !state.settings.deepThinkEnabled;
dom.btnToggleDeepthink.classList.toggle('active', state.settings.deepThinkEnabled);
dom.btnDockReason.classList.toggle('active', state.settings.deepThinkEnabled);
});
// Canvas Studio Button
dom.btnToggleCanvas.addEventListener('click', () => {
soundEngine.playTap();
if (!dom.canvasPanel.classList.contains('visible')) {
launchCanvasSandbox(
'Interactive Web Canvas',
`<!DOCTYPE html><html><body style="background:#06070a;color:#00f2fe;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;"><h1>✨ Nova AI Live Canvas Ready</h1></body></html>`,
'html'
);
} else {
dom.canvasPanel.classList.remove('visible');
}
});
// Canvas Tabs
dom.tabSandboxPreview.addEventListener('click', () => {
soundEngine.playTap();
dom.tabSandboxPreview.classList.add('active');
dom.tabSandboxCode.classList.remove('active');
dom.tabSandboxConsole.classList.remove('active');
dom.canvasSandboxFrame.style.display = 'block';
dom.canvasCodeContent.classList.remove('active');
dom.canvasConsolePanel.classList.remove('active');
});
dom.tabSandboxCode.addEventListener('click', () => {
soundEngine.playTap();
dom.tabSandboxCode.classList.add('active');
dom.tabSandboxPreview.classList.remove('active');
dom.tabSandboxConsole.classList.remove('active');
dom.canvasSandboxFrame.style.display = 'none';
dom.canvasCodeContent.classList.add('active');
dom.canvasConsolePanel.classList.remove('active');
});
dom.tabSandboxConsole.addEventListener('click', () => {
soundEngine.playTap();
dom.tabSandboxConsole.classList.add('active');
dom.tabSandboxPreview.classList.remove('active');
dom.tabSandboxCode.classList.remove('active');
dom.canvasSandboxFrame.style.display = 'none';
dom.canvasCodeContent.classList.remove('active');
dom.canvasConsolePanel.classList.add('active');
});
dom.btnClearConsole.addEventListener('click', () => {
soundEngine.playTap();
dom.canvasConsoleEntries.innerHTML = `<div class="console-line info">[System] Console output cleared.</div>`;
});
// Viewport Mode Buttons (Desktop / Tablet / Mobile)
dom.viewportBtns.forEach(btn => {
btn.addEventListener('click', () => {
soundEngine.playTap();
dom.viewportBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const mode = btn.getAttribute('data-mode');
dom.canvasSandboxFrame.className = mode === 'desktop' ? '' : `mode-${mode}`;
});
});
dom.btnCanvasClose.addEventListener('click', () => {
soundEngine.playTap();
dom.canvasPanel.classList.remove('visible');
});
dom.btnCanvasFullscreen.addEventListener('click', () => {
soundEngine.playTap();
dom.canvasPanel.classList.toggle('fullscreen');
});
dom.btnCanvasDownload.addEventListener('click', () => {
soundEngine.playSuccess();
if (state.currentArtifact) {
const blob = new Blob([state.currentArtifact.code], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'nova-artifact.html';
a.click();
URL.revokeObjectURL(url);
showToast('Artifact file exported');
}
});
// Command Palette
dom.btnOpenCmdPalette.addEventListener('click', () => {
soundEngine.playTap();
this.openCmdPalette();
});
document.querySelectorAll('.cmd-item').forEach(item => {
item.addEventListener('click', () => {
soundEngine.playTap();
const action = item.getAttribute('data-action');
this.executeCmdAction(action);
});
});
dom.cmdSearchInput.addEventListener('input', (e) => {
const q = e.target.value.toLowerCase();
document.querySelectorAll('.cmd-item').forEach(item => {
const title = item.querySelector('.cmd-item-title').innerText.toLowerCase();
item.style.display = title.includes(q) ? 'flex' : 'none';
});
});
// Prompt Textarea Auto-expand & Enter to submit
dom.promptTextarea.addEventListener('input', () => {
this.adjustTextareaHeight();
const text = dom.promptTextarea.value;
const tokensApprox = Math.ceil(text.length / 4);
dom.tokenCounterHint.innerText = `${tokensApprox} / 4096 tokens`;
dom.btnSendPrompt.classList.toggle('ready', text.trim().length > 0 && !state.isGenerating);
});
dom.promptTextarea.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.handlePromptSubmit();
}
});
// Global Shortcuts
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
e.preventDefault();
this.openCmdPalette();
}
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'n') {
e.preventDefault();
this.startNewChat();
}
if (e.key === 'Escape') {
dom.cmdPaletteModal.classList.remove('open');
dom.settingsModal.classList.remove('open');
dom.imageLightbox.classList.remove('open');
}
});
dom.cmdPaletteModal.addEventListener('click', (e) => {
if (e.target === dom.cmdPaletteModal) dom.cmdPaletteModal.classList.remove('open');
});
// Send Button Trigger
dom.btnSendPrompt.addEventListener('click', () => {
if (state.isGenerating) {
this.stopGeneration();
} else {
this.handlePromptSubmit();
}
});
// Prompt Starter Cards Click
document.querySelectorAll('.prompt-card').forEach(card => {
card.addEventListener('click', () => {
soundEngine.playTap();
const promptText = card.getAttribute('data-prompt');
dom.promptTextarea.value = promptText;
this.adjustTextareaHeight();
this.handlePromptSubmit();
});
});
// File Attachment Picker
dom.btnAttachFile.addEventListener('click', () => {
soundEngine.playTap();
dom.fileAttachmentInput.click();
});
dom.fileAttachmentInput.addEventListener('change', (e) => {
const files = Array.from(e.target.files);
files.forEach(file => {
const reader = new FileReader();
reader.onload = (evt) => {
state.attachments.push({
name: file.name,
type: file.type,
data: evt.target.result
});
this.renderAttachmentsTray();
};
if (file.type.startsWith('image/')) {
reader.readAsDataURL(file);
} else {
reader.readAsText(file);
}
});
dom.fileAttachmentInput.value = '';
});
// Dictation Microphone
dom.btnMicDictate.addEventListener('click', () => {
soundEngine.playTap();
this.toggleDictation();
});
// Voice Mode Trigger
dom.btnOpenVoiceMode.addEventListener('click', () => {
soundEngine.playTap();
voiceController.open();
});
// Settings Modal
dom.btnOpenSettings.addEventListener('click', () => {
soundEngine.playTap();
this.openSettings();
});
dom.btnCloseSettings.addEventListener('click', () => {
soundEngine.playTap();
dom.settingsModal.classList.remove('open');
});
dom.settingsModal.addEventListener('click', (e) => {
if (e.target === dom.settingsModal) dom.settingsModal.classList.remove('open');
});
dom.settingTemperature.addEventListener('input', (e) => {
dom.tempDisplayVal.innerText = parseFloat(e.target.value).toFixed(2);
});
document.querySelectorAll('.theme-dot').forEach(dot => {
dot.addEventListener('click', () => {
soundEngine.playTap();
document.querySelectorAll('.theme-dot').forEach(d => d.classList.remove('active'));
dot.classList.add('active');
const theme = dot.getAttribute('data-theme');
state.settings.theme = theme;
document.body.setAttribute('data-theme', theme);
localStorage.setItem('nova_theme', theme);
});
});
dom.btnSaveSettings.addEventListener('click', () => {
soundEngine.playSuccess();
state.settings.soundEnabled = dom.settingSoundToggle.checked;
soundEngine.enabled = state.settings.soundEnabled;
state.settings.reasoningDepth = dom.settingReasoningDepth.value;
state.settings.temperature = parseFloat(dom.settingTemperature.value);
state.settings.systemPrompt = dom.settingSystemPrompt.value;
state.settings.apiUrl = dom.settingApiUrl.value;
state.settings.apiKey = dom.settingApiKey.value;
localStorage.setItem('nova_sound', state.settings.soundEnabled);
localStorage.setItem('nova_reasoning_depth', state.settings.reasoningDepth);
localStorage.setItem('nova_temp', state.settings.temperature);
localStorage.setItem('nova_system_prompt', state.settings.systemPrompt);
localStorage.setItem('nova_api_url', state.settings.apiUrl);
localStorage.setItem('nova_api_key', state.settings.apiKey);
dom.settingsModal.classList.remove('open');
showToast('Settings saved successfully');
});
dom.btnResetSettings.addEventListener('click', () => {
soundEngine.playTap();
dom.settingSoundToggle.checked = true;
dom.settingReasoningDepth.value = 'high';
dom.settingTemperature.value = '0.70';
dom.tempDisplayVal.innerText = '0.70';
dom.settingSystemPrompt.value = '';
dom.settingApiUrl.value = '';
dom.settingApiKey.value = '';
});
// Share conversation
dom.btnShareChat.addEventListener('click', () => {
soundEngine.playTap();
if (!state.currentChatId) {
showToast('Start a conversation to export');
return;
}
const chat = state.getChat(state.currentChatId);
const text = chat.messages.map(m => `### ${m.role.toUpperCase()}:\n${m.content}\n`).join('\n---\n');
navigator.clipboard.writeText(text).then(() => {
soundEngine.playSuccess();
showToast('Session transcript copied to clipboard');
});
});
dom.imageLightbox.addEventListener('click', () => {
dom.imageLightbox.classList.remove('open');
});
// Listen to IFrame console postMessages
window.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SANDBOX_CONSOLE') {
const line = document.createElement('div');
line.className = `console-line ${event.data.level}`;
line.innerText = `[${event.data.level.toUpperCase()}] ${event.data.msg}`;
dom.canvasConsoleEntries.appendChild(line);
dom.canvasConsoleEntries.scrollTop = dom.canvasConsoleEntries.scrollHeight;
}
});
}
openCmdPalette() {
dom.cmdPaletteModal.classList.add('open');
dom.cmdSearchInput.value = '';
document.querySelectorAll('.cmd-item').forEach(i => i.style.display = 'flex');
setTimeout(() => dom.cmdSearchInput.focus(), 60);
}
executeCmdAction(action) {
dom.cmdPaletteModal.classList.remove('open');
switch (action) {
case 'new-chat':
this.startNewChat();
break;
case 'model-deepseek':
this.switchModel('nova-deepseek-r1', 'Nova DeepSeek-R1 Full');
break;
case 'model-coder':
this.switchModel('nova-coder-enterprise', 'Nova Coder Enterprise');
break;
case 'toggle-canvas':
dom.btnToggleCanvas.click();
break;
case 'open-voice':
voiceController.open();
break;
case 'open-settings':
this.openSettings();
break;
}
}
switchModel(modelId, label) {
state.settings.currentModel = modelId;
dom.currentModelLabel.innerText = label;
document.querySelectorAll('.model-option').forEach(o => {
o.classList.toggle('active', o.getAttribute('data-model-id') === modelId);
});
showToast(`Active model: ${label}`);
}
adjustTextareaHeight() {
dom.promptTextarea.style.height = 'auto';
dom.promptTextarea.style.height = Math.min(dom.promptTextarea.scrollHeight, 200) + 'px';
}
renderAttachmentsTray() {
if (state.attachments.length === 0) {
dom.attachmentsTray.classList.remove('visible');
dom.attachmentsTray.innerHTML = '';
return;
}
dom.attachmentsTray.classList.add('visible');
dom.attachmentsTray.innerHTML = state.attachments.map((att, idx) => `
<div class="tray-item">
${att.type.startsWith('image/') ? `<img src="${att.data}" alt="attachment">` : `<span>📄</span>`}
<span>${att.name}</span>
<button class="tray-remove-btn" onclick="chatController.removeAttachment(${idx})">✕</button>
</div>
`).join('');
}
removeAttachment(idx) {
soundEngine.playTap();
state.attachments.splice(idx, 1);
this.renderAttachmentsTray();
}
startNewChat() {
state.currentChatId = null;
dom.messagesContainer.innerHTML = '';
dom.messagesContainer.appendChild(dom.welcomeHero);
dom.welcomeHero.style.display = 'flex';
dom.promptTextarea.value = '';
state.attachments = [];
this.renderAttachmentsTray();
this.adjustTextareaHeight();
dom.btnSendPrompt.classList.remove('ready');
document.querySelectorAll('.history-item').forEach(el => el.classList.remove('active'));
dom.promptTextarea.focus();
}
loadChatIntoView(chatId) {
soundEngine.playTap();
const chat = state.getChat(chatId);
if (!chat) return;
state.currentChatId = chatId;
dom.welcomeHero.style.display = 'none';
dom.messagesContainer.innerHTML = '';
chat.messages.forEach(msg => {
if (msg.role === 'user') {
this.renderUserMessage(msg.content, msg.attachments, false);
} else {
this.renderAssistantMessage(msg.content, msg.reasoning, msg.citations, msg.latency, false);
}
});
this.renderHistoryList();
this.scrollToBottom();
}
renderUserMessage(text, attachments = [], animate = true) {
dom.welcomeHero.style.display = 'none';
const row = document.createElement('div');
row.className = 'message-row user';
let attachHtml = '';
if (attachments && attachments.length > 0) {
attachHtml = `<div class="user-attachments">` + attachments.map(a => `
<div class="attachment-chip">
${a.type && a.type.startsWith('image/') ? `<img src="${a.data}" alt="img">` : `<span>📎</span>`}
<span>${a.name}</span>
</div>
`).join('') + `</div>`;
}
row.innerHTML = `
<div class="message-avatar user">You</div>
<div class="message-content-wrap">
<div class="user-bubble">
<div>${MarkdownFormatter.escapeHtml(text).replace(/\n/g, '<br>')}</div>
${attachHtml}
</div>
</div>
`;
dom.messagesContainer.appendChild(row);
if (animate) this.scrollToBottom();
}
renderAssistantMessage(markdown, reasoningSteps = [], citations = [], latency = '6.4s', animate = true) {
const row = document.createElement('div');
row.className = 'message-row assistant';
const modelName = dom.currentModelLabel.innerText || 'Nova DeepSeek-R1';
let reasoningHtml = '';
if (reasoningSteps && reasoningSteps.length > 0) {
const stepItems = reasoningSteps.map(s => `
<div class="reasoning-step">
<div class="reasoning-step-title">${s.phase}</div>
<div>${s.text}</div>
</div>
`).join('');
reasoningHtml = `
<div class="reasoning-box">
<div class="reasoning-toggle" onclick="this.parentElement.classList.toggle('collapsed')">
<span class="reasoning-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M4.9 19.1l2.1-2.1M17 7l2.1-2.1"/>
</svg>
<span>DeepSeek-R1 Thought Process (${reasoningSteps.length} stages)</span>
</span>
<svg class="reasoning-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</div>
<div class="reasoning-content">
${stepItems}
</div>
</div>
`;
}
let citationsHtml = '';
if (citations && citations.length > 0) {
citationsHtml = `<div class="citations-bar">` + citations.map((c, i) => `
<a href="${c.url}" target="_blank" rel="noopener" class="citation-chip">
<span>🌐 [${i + 1}]</span>
<span>${c.name}</span>
</a>
`).join('') + `</div>`;
}
const formattedBody = MarkdownFormatter.format(markdown);
row.innerHTML = `
<div class="message-avatar assistant">N</div>
<div class="message-content-wrap">
<div class="assistant-meta-bar">
<span class="model-name-label">
<span class="model-dot" style="width:6px; height:6px;"></span>
${modelName}
</span>
<span class="latency-badge">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
</svg>
${latency}
</span>
</div>
${reasoningHtml}
${citationsHtml}
<div class="assistant-body markdown-body">
${formattedBody}
</div>
<div class="message-actions-toolbar">
<button class="tool-action-btn" title="Copy response" onclick="chatController.copyAssistantMessage(this)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg>
</button>
<button class="tool-action-btn" title="Read Aloud" onclick="chatController.readAloud(this)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
</svg>
</button>
<button class="tool-action-btn" title="Regenerate" onclick="chatController.regenerateLast()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
<path d="M3 3v5h5M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
<path d="M16 16h5v5"/>
</svg>
</button>
<button class="tool-action-btn" title="Good response" onclick="this.classList.toggle('active')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M7 10v12M15 10.5a3 3 0 0 0-3-3l-2 1v12h7a2 2 0 0 0 2-2l1-6a2 2 0 0 0-2-2h-3z"/>
</svg>
</button>
</div>
</div>
`;
dom.messagesContainer.appendChild(row);
if (animate) this.scrollToBottom();
return row;
}
async handlePromptSubmit() {
const text = dom.promptTextarea.value.trim();
if (!text && state.attachments.length === 0) return;
if (state.isGenerating) return;
soundEngine.playSend();
if (!state.currentChatId) {
const summary = text.slice(0, 32) + (text.length > 32 ? '...' : '');
const newChat = state.createChat(summary || 'New Session');
state.currentChatId = newChat.id;
}
const currentChat = state.getChat(state.currentChatId);
const userAttachments = [...state.attachments];
currentChat.messages.push({
role: 'user',
content: text,
attachments: userAttachments,
timestamp: Date.now()
});
state.saveChats();
this.renderUserMessage(text, userAttachments, true);
dom.promptTextarea.value = '';
state.attachments = [];
this.renderAttachmentsTray();
this.adjustTextareaHeight();
dom.btnSendPrompt.classList.remove('ready');
dom.tokenCounterHint.innerText = '0 / 4096 tokens';
this.startGeneration(text, currentChat);
}
async startGeneration(promptText, currentChat) {
state.isGenerating = true;
dom.iconSend.style.display = 'none';
dom.iconStop.style.display = 'block';
dom.btnSendPrompt.classList.add('generating');
const row = document.createElement('div');
row.className = 'message-row assistant';
const modelName = dom.currentModelLabel.innerText || 'Nova DeepSeek-R1';
row.innerHTML = `
<div class="message-avatar assistant">N</div>
<div class="message-content-wrap">
<div class="assistant-meta-bar">
<span class="model-name-label">
<span class="model-dot" style="width:6px; height:6px;"></span>
${modelName}
</span>
<span class="latency-badge" id="active-latency-badge">
<span class="reasoning-spinner" style="width:10px; height:10px;"></span>
<span>Reasoning...</span>
</span>
</div>
<div class="reasoning-box" id="active-reasoning-box">
<div class="reasoning-toggle">
<span class="reasoning-label">
<span class="reasoning-spinner"></span>
<span id="active-reasoning-label">Synthesizing DeepSeek-R1 &lt;think&gt; tokens...</span>
</span>
</div>
<div class="reasoning-content" id="active-reasoning-content"></div>
</div>
<div class="assistant-body markdown-body" id="active-assistant-body">
<span class="streaming-cursor"></span>
</div>
</div>
`;
dom.messagesContainer.appendChild(row);
this.scrollToBottom();
const plan = OmniKnowledgeEngine.getResponsePlan(
promptText,
state.settings.currentModel,
state.settings.deepThinkEnabled,
state.settings.webSearchEnabled
);
const activeReasoningBox = row.querySelector('#active-reasoning-box');
const activeReasoningContent = row.querySelector('#active-reasoning-content');
const activeReasoningLabel = row.querySelector('#active-reasoning-label');
const activeBody = row.querySelector('#active-assistant-body');
const activeLatencyBadge = row.querySelector('#active-latency-badge');
const startTime = performance.now();
// 1. Stream Reasoning steps if enabled
if (state.settings.deepThinkEnabled && plan.reasoningSteps.length > 0) {
for (const step of plan.reasoningSteps) {
if (!state.isGenerating) break;
await new Promise(r => setTimeout(r, 400));
const stepEl = document.createElement('div');
stepEl.className = 'reasoning-step';
stepEl.innerHTML = `<div class="reasoning-step-title">${step.phase}</div><div>${step.text}</div>`;
activeReasoningContent.appendChild(stepEl);
this.scrollToBottom();
}
activeReasoningLabel.innerText = `Thought for ${((performance.now() - startTime) / 1000).toFixed(1)}s (${plan.reasoningSteps.length} stages)`;
activeReasoningBox.classList.add('collapsed');
soundEngine.playThinkingDone();
} else {
activeReasoningBox.style.display = 'none';
}
// 2. Stream Tokens
const fullMarkdown = plan.markdown;
let currentIdx = 0;
const chunkSize = 7;
while (currentIdx < fullMarkdown.length && state.isGenerating) {
currentIdx += chunkSize;
const subText = fullMarkdown.slice(0, currentIdx);
activeBody.innerHTML = MarkdownFormatter.format(subText) + '<span class="streaming-cursor"></span>';
this.scrollToBottom();
await new Promise(r => setTimeout(r, 14));
}
activeBody.innerHTML = MarkdownFormatter.format(fullMarkdown);
const duration = ((performance.now() - startTime) / 1000).toFixed(1) + 's';
activeLatencyBadge.innerHTML = `
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
</svg>
<span>${duration}</span>
`;
const toolbar = document.createElement('div');
toolbar.className = 'message-actions-toolbar';
toolbar.innerHTML = `
<button class="tool-action-btn" title="Copy response" onclick="chatController.copyAssistantMessage(this)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg>
</button>
<button class="tool-action-btn" title="Read Aloud" onclick="chatController.readAloud(this)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
</svg>
</button>
<button class="tool-action-btn" title="Regenerate" onclick="chatController.regenerateLast()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
<path d="M3 3v5h5M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
<path d="M16 16h5v5"/>
</svg>
</button>
<button class="tool-action-btn" title="Good response" onclick="this.classList.toggle('active')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M7 10v12M15 10.5a3 3 0 0 0-3-3l-2 1v12h7a2 2 0 0 0 2-2l1-6a2 2 0 0 0-2-2h-3z"/>
</svg>
</button>
`;
row.querySelector('.message-content-wrap').appendChild(toolbar);
currentChat.messages.push({
role: 'assistant',
content: fullMarkdown,
reasoning: plan.reasoningSteps,
citations: plan.citations,
latency: duration,
timestamp: Date.now()
});
state.saveChats();
if (fullMarkdown.includes('```html')) {
const match = fullMarkdown.match(/```html\n([\s\S]*?)```/);
if (match && match[1]) {
launchCanvasSandbox(plan.title || 'Interactive Project', match[1].trim(), 'html');
}
}
this.finishGeneration();
this.renderHistoryList();
}
stopGeneration() {
state.isGenerating = false;
this.finishGeneration();
showToast('Generation halted');
}
finishGeneration() {
state.isGenerating = false;
dom.iconSend.style.display = 'block';
dom.iconStop.style.display = 'none';
dom.btnSendPrompt.classList.remove('generating');
}
scrollToBottom() {
dom.messagesScrollArea.scrollTop = dom.messagesScrollArea.scrollHeight;
}
copyAssistantMessage(btn) {
soundEngine.playSuccess();
const row = btn.closest('.message-row');
const body = row.querySelector('.assistant-body');
if (body) {
navigator.clipboard.writeText(body.innerText).then(() => {
showToast('Response copied to clipboard');
});
}
}
readAloud(btn) {
soundEngine.playTap();
if (!('speechSynthesis' in window)) {
showToast('Speech synthesis not supported');
return;
}
if (window.speechSynthesis.speaking) {
window.speechSynthesis.cancel();
btn.classList.remove('active');
return;
}
const row = btn.closest('.message-row');
const body = row.querySelector('.assistant-body');
if (!body) return;
btn.classList.add('active');
const utterance = new SpeechSynthesisUtterance(body.innerText);
utterance.rate = 1.05;
utterance.onend = () => btn.classList.remove('active');
utterance.onerror = () => btn.classList.remove('active');
window.speechSynthesis.speak(utterance);
}
regenerateLast() {
soundEngine.playTap();
if (!state.currentChatId) return;
const chat = state.getChat(state.currentChatId);
if (!chat || chat.messages.length === 0) return;
let lastUserPrompt = '';
for (let i = chat.messages.length - 1; i >= 0; i--) {
if (chat.messages[i].role === 'user') {
lastUserPrompt = chat.messages[i].content;
break;
}
}
if (lastUserPrompt) {
if (chat.messages[chat.messages.length - 1].role === 'assistant') {
chat.messages.pop();
dom.messagesContainer.lastElementChild.remove();
}
this.startGeneration(lastUserPrompt, chat);
}
}
toggleDictation() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
showToast('Dictation requires Chrome/Edge Web Speech API');
return;
}
if (this.recognition && this.isDictating) {
this.recognition.stop();
this.isDictating = false;
dom.btnMicDictate.style.color = '';
return;
}
this.recognition = new SpeechRecognition();
this.recognition.continuous = false;
this.recognition.interimResults = true;
this.isDictating = true;
dom.btnMicDictate.style.color = 'var(--accent-primary)';
this.recognition.onresult = (e) => {
let transcript = '';
for (let i = e.resultIndex; i < e.results.length; ++i) {
transcript += e.results[i][0].transcript;
}
dom.promptTextarea.value = transcript;
this.adjustTextareaHeight();
dom.btnSendPrompt.classList.add('ready');
};
this.recognition.onend = () => {
this.isDictating = false;
dom.btnMicDictate.style.color = '';
};
this.recognition.start();
showToast('Listening... Speak now');
}
renderHistoryList() {
dom.historyListToday.innerHTML = '';
dom.historyListPrevious.innerHTML = '';
const oneDay = 24 * 60 * 60 * 1000;
const now = Date.now();
const filteredChats = state.chats.filter(c => {
if (state.activeFolder === 'all') return true;
return c.folder === state.activeFolder;
});
filteredChats.forEach(chat => {
const isToday = (now - chat.createdAt) < oneDay;
const targetContainer = isToday ? dom.historyListToday : dom.historyListPrevious;
const item = document.createElement('div');
item.className = 'history-item' + (chat.id === state.currentChatId ? ' active' : '');
item.setAttribute('data-chat-id', chat.id);
item.innerHTML = `
<div class="history-title-wrap">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg>
<span class="history-title">${MarkdownFormatter.escapeHtml(chat.title)}</span>
</div>
<div class="history-actions">
<button class="action-btn-mini" title="Rename" onclick="event.stopPropagation(); chatController.promptRenameChat('${chat.id}')">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>
</svg>
</button>
<button class="action-btn-mini delete" title="Delete" onclick="event.stopPropagation(); chatController.confirmDeleteChat('${chat.id}')">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg>
</button>
</div>
`;
item.addEventListener('click', () => {
this.loadChatIntoView(chat.id);
});
targetContainer.appendChild(item);
});
dom.historyListArtifacts.innerHTML = `
<div class="history-item" onclick="launchCanvasSandbox('3D Particle Physics', 'https://example.com', 'html')">
<div class="history-title-wrap">
<span>🌌</span>
<span class="history-title">3D Particle Simulation</span>
</div>
</div>
<div class="history-item" onclick="zoomImage('assets/gpt5_quantum_core.jpg')">
<div class="history-title-wrap">
<span>🎨</span>
<span class="history-title">Quantum Core Hologram</span>
</div>
</div>
`;
}
filterHistory(term) {
const q = term.toLowerCase();
document.querySelectorAll('.history-item').forEach(item => {
const title = item.querySelector('.history-title');
if (title) {
item.style.display = title.innerText.toLowerCase().includes(q) ? 'flex' : 'none';
}
});
}
promptRenameChat(chatId) {
const chat = state.getChat(chatId);
if (!chat) return;
const title = prompt('Enter new session title:', chat.title);
if (title && title.trim()) {
state.renameChat(chatId, title.trim());
this.renderHistoryList();
showToast('Session renamed');
}
}
confirmDeleteChat(chatId) {
if (confirm('Delete this session?')) {
state.deleteChat(chatId);
if (state.currentChatId === chatId) {
this.startNewChat();
} else {
this.renderHistoryList();
}
showToast('Session deleted');
}
}
openSettings() {
dom.settingSoundToggle.checked = state.settings.soundEnabled;
dom.settingReasoningDepth.value = state.settings.reasoningDepth;
dom.settingTemperature.value = state.settings.temperature.toFixed(2);
dom.tempDisplayVal.innerText = state.settings.temperature.toFixed(2);
dom.settingSystemPrompt.value = state.settings.systemPrompt;
dom.settingApiUrl.value = state.settings.apiUrl;
dom.settingApiKey.value = state.settings.apiKey;
document.querySelectorAll('.theme-dot').forEach(d => {
d.classList.toggle('active', d.getAttribute('data-theme') === state.settings.theme);
});
dom.settingsModal.classList.add('open');
}
}
// ============================================================================
// 7. Split-Screen Canvas Studio Engine
// ============================================================================
function launchCanvasSandbox(title, code, lang = 'html') {
soundEngine.playTap();
state.currentArtifact = { title, code, lang };
dom.canvasDocumentTitle.innerText = title;
dom.canvasCodeContent.innerText = code;
// Add Console Logger Bridge
const bridgeScript = `
<script>
(function() {
const send = (level, args) => {
try {
window.parent.postMessage({
type: 'SANDBOX_CONSOLE',
level: level,
msg: Array.from(args).map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')
}, '*');
} catch(e){}
};
const _log = console.log, _warn = console.warn, _err = console.error;
console.log = function(...a){ _log.apply(console, a); send('info', a); };
console.warn = function(...a){ _warn.apply(console, a); send('warn', a); };
console.error = function(...a){ _err.apply(console, a); send('error', a); };
})();
</script>
`;
const augmentedCode = code.includes('<head>')
? code.replace('<head>', '<head>' + bridgeScript)
: bridgeScript + code;
const doc = dom.canvasSandboxFrame.contentDocument || dom.canvasSandboxFrame.contentWindow.document;
doc.open();
doc.write(augmentedCode);
doc.close();
dom.canvasPanel.classList.add('visible');
dom.tabSandboxPreview.classList.add('active');
dom.tabSandboxCode.classList.remove('active');
dom.tabSandboxConsole.classList.remove('active');
dom.canvasSandboxFrame.style.display = 'block';
dom.canvasCodeContent.classList.remove('active');
dom.canvasConsolePanel.classList.remove('active');
showToast(`Canvas: launched "${title}"`);
}
dom.canvasCodeContent.addEventListener('input', () => {
if (state.currentArtifact) {
state.currentArtifact.code = dom.canvasCodeContent.innerText;
const doc = dom.canvasSandboxFrame.contentDocument || dom.canvasSandboxFrame.contentWindow.document;
doc.open();
doc.write(state.currentArtifact.code);
doc.close();
}
});
// ============================================================================
// 8. Nova AI Real-time Voice Mode Controller
// ============================================================================
class VoiceModeController {
constructor() {
this.active = false;
this.muted = false;
this.animFrame = null;
this.canvas = dom.voiceWaveCanvas;
this.ctx = this.canvas.getContext('2d');
this.recognition = null;
dom.btnCloseVoice.addEventListener('click', () => this.close());
dom.btnVoiceMute.addEventListener('click', () => this.toggleMute());
dom.btnVoiceInterrupt.addEventListener('click', () => this.interrupt());
}
open() {
this.active = true;
dom.voiceOverlay.classList.add('active');
dom.voiceTranscriptionText.innerText = '"Say something to begin real-time conversational streaming with Nova AI..."';
dom.voiceStatusText.innerText = 'Nova AI Voice • Listening...';
this.startVisualizer();
this.startListening();
}
close() {
this.active = false;
dom.voiceOverlay.classList.remove('active');
if (this.animFrame) cancelAnimationFrame(this.animFrame);
if (this.recognition) this.recognition.stop();
if (window.speechSynthesis) window.speechSynthesis.cancel();
}
toggleMute() {
this.muted = !this.muted;
dom.btnVoiceMute.classList.toggle('active', this.muted);
dom.voiceStatusText.innerText = this.muted ? 'Microphone Muted' : 'Nova AI Voice • Listening...';
}
interrupt() {
soundEngine.playTap();
if (window.speechSynthesis) window.speechSynthesis.cancel();
dom.voiceTranscriptionText.innerText = '"Listening..."';
}
startListening() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) return;
this.recognition = new SpeechRecognition();
this.recognition.continuous = true;
this.recognition.interimResults = true;
this.recognition.onresult = (e) => {
if (this.muted) return;
let text = '';
for (let i = e.resultIndex; i < e.results.length; ++i) {
text += e.results[i][0].transcript;
}
dom.voiceTranscriptionText.innerText = `"${text}"`;
if (e.results[e.results.length - 1].isFinal) {
this.respondToVoice(text);
}
};
this.recognition.start();
}
respondToVoice(userSpeech) {
dom.voiceStatusText.innerText = 'Nova AI Reasoning...';
const plan = OmniKnowledgeEngine.getResponsePlan(userSpeech, 'nova-deepseek-r1', true, false);
setTimeout(() => {
dom.voiceStatusText.innerText = 'Nova AI Speaking...';
const cleanText = plan.markdown.replace(/#|\*|`|<[^>]*>/g, '').slice(0, 300);
dom.voiceTranscriptionText.innerText = cleanText;
if ('speechSynthesis' in window) {
const utter = new SpeechSynthesisUtterance(cleanText);
utter.rate = 1.05;
utter.onend = () => {
dom.voiceStatusText.innerText = 'Nova AI Voice • Listening...';
};
window.speechSynthesis.speak(utter);
}
}, 700);
}
startVisualizer() {
const w = this.canvas.width;
const h = this.canvas.height;
const cx = w / 2;
const cy = h / 2;
let angle = 0;
const draw = () => {
if (!this.active) return;
this.ctx.clearRect(0, 0, w, h);
angle += 0.04;
const isSpeaking = window.speechSynthesis && window.speechSynthesis.speaking;
const amp = isSpeaking ? 30 : 10;
const grad = this.ctx.createRadialGradient(cx, cy, 25, cx, cy, 130);
grad.addColorStop(0, 'rgba(0, 242, 254, 0.35)');
grad.addColorStop(0.5, 'rgba(16, 185, 129, 0.18)');
grad.addColorStop(1, 'transparent');
this.ctx.fillStyle = grad;
this.ctx.beginPath();
this.ctx.arc(cx, cy, 130, 0, Math.PI * 2);
this.ctx.fill();
for (let ring = 0; ring < 4; ring++) {
this.ctx.beginPath();
for (let a = 0; a <= Math.PI * 2; a += 0.05) {
const r = 55 + ring * 14 + Math.sin(a * 4 + angle + ring) * amp;
const x = cx + Math.cos(a) * r;
const y = cy + Math.sin(a) * r;
if (a === 0) this.ctx.moveTo(x, y);
else this.ctx.lineTo(x, y);
}
this.ctx.closePath();
this.ctx.strokeStyle = ring % 2 === 0 ? 'rgba(0, 242, 254, 0.7)' : 'rgba(16, 185, 129, 0.8)';
this.ctx.lineWidth = 2.2;
this.ctx.shadowBlur = 12;
this.ctx.shadowColor = '#00f2fe';
this.ctx.stroke();
}
this.animFrame = requestAnimationFrame(draw);
};
draw();
}
}
// Toast helper
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'toast';
toast.innerHTML = `
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2.5">
<path d="M20 6L9 17l-5-5"/>
</svg>
<span>${message}</span>
`;
dom.toastContainer.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px)';
toast.style.transition = 'all 0.25s ease';
setTimeout(() => toast.remove(), 250);
}, 2600);
}
// ============================================================================
// 9. App Initialization
// ============================================================================
let chatController = null;
let voiceController = null;
window.addEventListener('DOMContentLoaded', () => {
chatController = new ChatController();
voiceController = new VoiceModeController();
window.chatController = chatController;
window.voiceController = voiceController;
if (state.chats.length > 0) {
chatController.loadChatIntoView(state.chats[0].id);
}
});