Spaces:
Running on Zero
Running on Zero
Download app.js from Shree-Lms-Developers2025/NovaAI: direct link, hf CLI and curl.
- Browser
- Download file 81.8 kB
-
https://huggingface.co/spaces/Shree-Lms-Developers2025/NovaAI/resolve/main/app.js
- Command line
-
hf download hf://spaces/Shree-Lms-Developers2025/NovaAI/app.js
-
curl -L -o app.js https://huggingface.co/spaces/Shree-Lms-Developers2025/NovaAI/resolve/main/app.js
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, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/"/g, '"') | |
| .replace(/'/g, '''); | |
| } | |
| } | |
| // 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 <think> 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); | |
| } | |
| }); | |