diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-16 06:01:21 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-16 06:01:21 +0200 |
| commit | 6aa24b976952a698c673d350977721b27193183d (patch) | |
| tree | 5586529d0f9932c4a2db19b9aca82b05423d10ab | |
| parent | daff0939a7997b45aed12c7d157f6adcfbd087ec (diff) | |
auto: 2026-06-16_06:00 changes
| -rw-r--r-- | main.html | 9 | ||||
| -rw-r--r-- | modules/core/core-bundle.js | 168 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 7 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 4 | ||||
| -rw-r--r-- | modules/core/terminal.js | 13 | ||||
| -rw-r--r-- | modules/ext/debug-panel.js | 167 | ||||
| -rw-r--r-- | packages.json | 10 |
7 files changed, 347 insertions, 31 deletions
@@ -915,6 +915,7 @@ <button class="qa-btn" onclick="webx11.openConfigManager()">โ Config</button> <button class="qa-btn" onclick="webx11.executeCommand('clear')">๐งน Clear</button> <button class="qa-btn" id="voice-toggle-btn" onclick="webx11.toggleMicVisibility()">๐ค Mic</button> + <button class="qa-btn" onclick="webx11.toggleDebugPanel()">๐ Debug</button> <button class="qa-btn" id="sound-toggle-btn">๐ Sound</button> </div> @@ -1279,12 +1280,12 @@ } </script> - <!-- v4.5.55: Core modules + critical ext modules loaded synchronously as bundles to avoid 503 request storms --> -<script src="modules/core/core-bundle.js?v=2026-06-15-v4.5.55"></script> -<script src="modules/ext/critical-ext-bundle.js?v=2026-06-15-v4.5.55"></script> + <!-- v4.5.61: vaultmind_query passes Bearer token from localStorage --> +<script src="modules/core/core-bundle.js?v=2026-06-16-v4.5.61"></script> +<script src="modules/ext/critical-ext-bundle.js?v=2026-06-16-v4.5.61"></script> <!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules --> -<script src="modules/core/module-loader.js?v=2026-06-15-v4.5.55"></script> +<script src="modules/core/module-loader.js?v=2026-06-16-v4.5.61"></script> <script> // โโโ ONE TRUE VERSION SOURCE โโโ diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 8b73403..24ec5af 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -918,6 +918,13 @@ window.VirtualFileSystem = VirtualFileSystem; localStorage.setItem('webx11_modules', JSON.stringify(this.moduleRegistry)); } catch (e) { console.error('Module save error:', e); } } + async restorePersistedModules() { + for (const entry of this.moduleRegistry) { + if (entry.enabled && entry.url) { + await this.loadModuleFromURL(entry.url); + } + } + } async loadModuleFromURL(url) { try { this.terminal.print(`Loading module from ${url}...`, 'info'); @@ -927,10 +934,17 @@ window.VirtualFileSystem = VirtualFileSystem; const ModuleClass = this.createModuleClass(code, url); if (ModuleClass) { const instance = new ModuleClass(); - this.registerModule(instance, url); if (instance.init) { - await instance.init(this.terminal); + try { + await instance.init(this.terminal); + instance._initComplete = true; + } catch (e) { + console.error(`[ModuleManager] init() failed for ${instance.constructor.name}:`, e); + this.terminal.print(`Module '${instance.constructor.name}' init() failed: ${e.message}`, 'error'); + return false; + } } + this.registerModule(instance, url); this.terminal.print(`Module loaded successfully from ${url}`, 'success'); return true; } @@ -943,6 +957,13 @@ window.VirtualFileSystem = VirtualFileSystem; try { // Strip leading comments to avoid ASI: `return\n/**` โ `return; /**` const cleanCode = code.replace(/^\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*\s*/, ''); + // Try wrapper convention first: `return class MyModule { ... };` + const wrapperMatch = cleanCode.match(/return\s+class\s+(\w+)/); + if (wrapperMatch) { + const mod = new Function(cleanCode)(); + if (mod && typeof mod === 'function') return mod; + } + // Fall back to regex class extraction const classMatch = cleanCode.match(/class\s+(\w+)\s+(extends\s+\S+)?\s*\{/); if (classMatch) { const className = classMatch[1]; @@ -959,7 +980,15 @@ window.VirtualFileSystem = VirtualFileSystem; const name = instance.constructor.name; this.modules[name] = instance; instance.terminal = this.terminal; - if (instance.registerCommands) { instance.registerCommands(); } + instance.fs = this.terminal.fs; + try { + if (instance.registerCommands) { instance.registerCommands(); } + } catch (e) { + console.error(`[ModuleManager] registerCommands() failed for ${name}:`, e); + delete this.modules[name]; + this.terminal.print(`Module '${name}' registration failed: ${e.message}`, 'error'); + return; + } // Only persist modules loaded from absolute URLs (not boot-time ext modules) if (url && url.startsWith('http') && !this.moduleRegistry.find(m => m.name === name)) { this.moduleRegistry.push({ @@ -979,16 +1008,17 @@ window.VirtualFileSystem = VirtualFileSystem; } return false; } - loadModuleFromCode(code, url) { + async loadModuleFromCode(code, url) { try { this.terminal.print('Loading module from editor...', 'info'); const ModuleClass = this.createModuleClass(code, url); if (ModuleClass) { const instance = new ModuleClass(); - this.registerModule(instance, url); if (instance.init) { - instance.init(this.terminal); + await instance.init(this.terminal); + instance._initComplete = true; } + this.registerModule(instance, url); this.terminal.print(`Module loaded from editor: ${instance.constructor.name}`, 'success'); return true; } @@ -1054,6 +1084,31 @@ window.VirtualFileSystem = VirtualFileSystem; } return help; } + diagnose() { + const report = { + totalInModules: Object.keys(this.modules).length, + totalInRegistry: this.moduleRegistry.length, + orphaned: [], + missingFs: [], + missingTerminal: [], + uninitialized: [], + healthy: 0 + }; + const registryNames = new Set(this.moduleRegistry.map(m => m.name)); + for (const name in this.modules) { + const mod = this.modules[name]; + if (!registryNames.has(name)) { + report.orphaned.push(name); + } + if (!mod.fs) report.missingFs.push(name); + if (!mod.terminal) report.missingTerminal.push(name); + if (typeof mod.init === 'function' && !mod._initComplete) { + report.uninitialized.push(name); + } + if (mod.fs && mod.terminal) report.healthy++; + } + return report; + } } /* === ollama-manager.js === */ @@ -1915,6 +1970,11 @@ class OllamaManager { if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); const text = data.choices?.[0]?.message?.content || ''; + // Sync provider state after successful non-Ollama fallback + if (provider !== this.currentProvider) { + this.currentProvider = provider; + this._syncCompatProperties(provider); + } return { message: { role: 'assistant', content: text }, tool_calls: null, error: null }; } catch (e) { if (e.name === 'AbortError') { @@ -1941,10 +2001,11 @@ ${toolDesc} </tools> Only output XML function calls for tool tasks. If no tool is needed, answer normally.` }; - const msgsWithTools = [toolContext, ...messages]; +const msgsWithTools = [toolContext, ...messages]; const body = { model: useModel, messages: msgsWithTools, stream: false, options: { temperature: 0.1 } }; - const response = await fetch(`${this.endpoint}/api/chat`, { + const ep = this.currentProvider ? this.currentProvider.endpoint : this.endpoint; + const response = await fetch(`${ep}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal }); @@ -3411,7 +3472,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } catch (e) {} } async _loadExtModules(filterKeys) { - const autoLoadKeys = filterKeys || ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; + const autoLoadKeys = filterKeys || ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; const cacheVer = localStorage.getItem('webx11_cache_ver') || '2026-05-27-14'; let index = null; let lastErr = null; @@ -3444,7 +3505,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm } async reloadPersistedModules() { const registry = this.moduleManager.moduleRegistry; - const skipList = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console']; + const skipList = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console']; for (const mod of registry) { if (mod.enabled && mod.url) { // Skip ext modules โ they load via _loadExtModules @@ -4641,6 +4702,15 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm voiceMod.handle([]); } } + toggleDebugPanel() { + const mod = this.moduleManager.modules['DebugPanel']; + if (mod) { + mod.terminal = this; + mod.toggle(); + } else { + this.print('Debug panel not loaded', 'warning'); + } + } setFontSize(size) { const s = parseInt(size); if (isNaN(s) || s < 8 || s > 32) { @@ -5909,11 +5979,17 @@ class OllamaModule { }; this.commands['ai'] = async (args) => { if(!args[0]) return this.terminal.print("Usage: ai <prompt>", 'warning'); - await this.terminal.ollamaManager.chat(args.join(' ')); + const result = await this.terminal.ollamaManager.chat(args.join(' ')); + if (result === null) { + this.terminal.print('AI returned no response โ check provider connection', 'warning'); + } }; this.commands['ask'] = async (args) => { if(!args[0]) return this.terminal.print("Usage: ask <prompt>", 'warning'); - await this.terminal.ollamaManager.chat(args.join(' ')); + const result = await this.terminal.ollamaManager.chat(args.join(' ')); + if (result === null) { + this.terminal.print('AI returned no response โ check provider connection', 'warning'); + } }; // Merged from OllamaModelsModule this.commands['models'] = () => this.listModels(); @@ -6139,7 +6215,29 @@ class MyModule { return mm.modules[name]; } registerCommands() { - this.commands['modules'] = () => { this.terminal.openModuleManager(); }; + this.commands['modules'] = (args) => { + if (args[0] === '--diagnose' || args[0] === '-d') { + const report = this.terminal.moduleManager.diagnose(); + this.terminal.print(`=== Module Diagnostics ===`, 'info'); + this.terminal.print(`Total in modules: ${report.totalInModules}`); + this.terminal.print(`Total in registry: ${report.totalInRegistry}`); + this.terminal.print(`Healthy: ${report.healthy}`); + if (report.orphaned.length) { + this.terminal.print(`Orphaned (in modules but not registry): ${report.orphaned.join(', ')}`, 'warning'); + } + if (report.missingFs.length) { + this.terminal.print(`Missing fs: ${report.missingFs.join(', ')}`, 'error'); + } + if (report.missingTerminal.length) { + this.terminal.print(`Missing terminal: ${report.missingTerminal.join(', ')}`, 'error'); + } + if (report.uninitialized.length) { + this.terminal.print(`Uninitialized (init() never completed): ${report.uninitialized.join(', ')}`, 'warning'); + } + return; + } + this.terminal.openModuleManager(); + }; this.commands['loadmod'] = async (args) => { if(!args[0]) return this.terminal.print("Usage: loadmod <url>", 'warning'); await this.terminal.moduleManager.loadModuleFromURL(args[0]); @@ -7187,7 +7285,9 @@ class ServerMonitorModule { // Extracted from newOS4 index.html โ WebX11AIAssistantModule (Full Agent Loop) class WebX11AIAssistantModule { constructor() { - this.commands = {}; this.terminal = null; + this.commands = {}; this.commandsMeta = { + 'ai-config': { argType: 'enum', enumFn: '_getAiConfigKeys' } + }; this.terminal = null; this.systemPrompt = this.buildSystemPrompt(); this.context = []; this.maxContext = 10; this.maxContextTokens = 2000; @@ -7888,6 +7988,16 @@ buildSystemPrompt() { } async agenticLoop(messages) { let toolTurns = 0; + // Auto-connect if not connected + const om = this.terminal.ollamaManager; + if (om && !om.connected) { + this.terminal.print('โณ AI provider not connected โ attempting auto-connect...', 'info'); + const connected = await om.connect(); + if (!connected) { + this.terminal.print('โ Could not connect to any AI provider.', 'error'); + return { finalAnswer: null, toolTurns: 0 }; + } + } while (toolTurns < this.MAX_TOOL_TURNS) { if (this._cancelled) { this.terminal.print('AI loop cancelled', 'warning'); return { finalAnswer: null, toolTurns }; } toolTurns++; @@ -7936,9 +8046,9 @@ buildSystemPrompt() { const sm = this.terminal.moduleManager.modules['ServerMonitorModule']; if (sm && sm.logToolCall) sm.logToolCall(fn.name, (t1 * 1000) || 1, this._isToolError(toolResult)); if (this._debugPanelEnabled) this._updateDebugPanel(); - return { fn, toolResult, t1 }; + return { fn, toolResult, t1, toolCallId: tc.id }; })); - for (const { fn, toolResult, t1 } of safeResults) { + for (const { fn, toolResult, t1, toolCallId } of safeResults) { this.terminal.print(`๐ง Tool: ${fn.name}(${typeof fn.arguments === 'string' ? fn.arguments : JSON.stringify(fn.arguments)})`, 'info'); // Memory graph: record tool call + file access try { @@ -7965,7 +8075,7 @@ buildSystemPrompt() { this.terminal.log('INFO', 'AI', `Tool ${fn.name} ok (${t1}s)`); this.terminal.print(`๐ Result (${t1}s): ${obs}`, 'info'); } - messages.push({ role: 'tool', content: toolResult }); + messages.push({ role: 'tool', tool_call_id: toolCallId, content: toolResult }); } // Only block on destructive tools if (destructiveTools.length > 0) { @@ -8027,7 +8137,7 @@ buildSystemPrompt() { this.terminal.log('INFO', 'AI', `Tool ${fn.name} ok (${t1}s)`); this.terminal.print(`๐ Result (${t1}s): ${dObs}`, 'info'); } - messages.push({ role: 'tool', content: toolResult }); + messages.push({ role: 'tool', tool_call_id: tc.id, content: toolResult }); } } else { messages.push({ @@ -9062,13 +9172,24 @@ buildSystemPrompt() { return await this.runModuleTool(name, argsStr); case 'vaultmind_query': { const ep = a.endpoint; - const validEps = ['prices', 'portfolio', 'transactions', 'copy-trade', 'chat']; - if (!validEps.includes(ep)) { - return JSON.stringify({ error: 'Invalid endpoint. Valid: ' + validEps.join(', ') }); + const epMap = { + 'prices': '/vmai-api/market/prices', + 'portfolio': '/vmai-api/portfolio', + 'transactions': '/vmai-api/transactions/summary', + 'copy-trade': '/vmai-api/copy-trading/start', + 'chat': '/vmai-api/chat', + }; + const url = epMap[ep]; + if (!url) { + return JSON.stringify({ error: 'Invalid endpoint. Valid: ' + Object.keys(epMap).join(', ') }); } try { - let url = '/vmai/' + ep; + // Read VaultMind session token from localStorage (set by VaultMind dashboard) + const vmToken = typeof localStorage !== 'undefined' ? localStorage.getItem('vaultmind_token') : null; let opts = { method: 'GET', headers: { 'Accept': 'application/json' } }; + if (vmToken) { + opts.headers['Authorization'] = 'Bearer ' + vmToken; + } if (ep === 'chat') { opts.method = 'POST'; opts.headers['Content-Type'] = 'application/json'; @@ -9409,6 +9530,9 @@ buildSystemPrompt() { this.terminal.print('No models available. Use ollamaconnect first.', 'warning'); } } + _getAiConfigKeys() { + return ['max-turns', 'max-context', 'max-context-tokens', 'confirm-timeout', 'chat-lock-timeout', 'skill-debounce', 'trivial-optimization']; + } aiConfig(args) { if (args.length === 0) { this.terminal.print('=== AI Config ===', 'info'); diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index 44a7574..c5a91c9 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -1,7 +1,9 @@ // Extracted from newOS4 index.html โ WebX11AIAssistantModule (Full Agent Loop) class WebX11AIAssistantModule { constructor() { - this.commands = {}; this.terminal = null; + this.commands = {}; this.commandsMeta = { + 'ai-config': { argType: 'enum', enumFn: '_getAiConfigKeys' } + }; this.terminal = null; this.systemPrompt = this.buildSystemPrompt(); this.context = []; this.maxContext = 10; this.maxContextTokens = 2000; @@ -2223,6 +2225,9 @@ buildSystemPrompt() { this.terminal.print('No models available. Use ollamaconnect first.', 'warning'); } } + _getAiConfigKeys() { + return ['max-turns', 'max-context', 'max-context-tokens', 'confirm-timeout', 'chat-lock-timeout', 'skill-debounce', 'trivial-optimization']; + } aiConfig(args) { if (args.length === 0) { this.terminal.print('=== AI Config ===', 'info'); diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index bade62f..bd02873 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = '2026-06-15-v4.5.55'; + const LATEST_VER = '2026-06-16-v4.5.61'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { @@ -51,7 +51,7 @@ // Optional ext modules via packages.json if (typeof webx11._loadExtModules === 'function') { - let extKeys = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; + let extKeys = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; const saved = localStorage.getItem('webx11_startup_modules'); let selected = null; try { if (saved) selected = JSON.parse(saved); } catch (e) {} diff --git a/modules/core/terminal.js b/modules/core/terminal.js index c416b8b..504e08d 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -1279,7 +1279,7 @@ } catch (e) {} } async _loadExtModules(filterKeys) { - const autoLoadKeys = filterKeys || ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; + const autoLoadKeys = filterKeys || ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'cryptogzip', 'tutorial', 'floating', 'console']; const cacheVer = localStorage.getItem('webx11_cache_ver') || '2026-05-27-14'; let index = null; let lastErr = null; @@ -1312,7 +1312,7 @@ } async reloadPersistedModules() { const registry = this.moduleManager.moduleRegistry; - const skipList = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console']; + const skipList = ['calc', 'voice', 'debug-panel', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox', 'floating', 'console']; for (const mod of registry) { if (mod.enabled && mod.url) { // Skip ext modules โ they load via _loadExtModules @@ -2509,6 +2509,15 @@ voiceMod.handle([]); } } + toggleDebugPanel() { + const mod = this.moduleManager.modules['DebugPanel']; + if (mod) { + mod.terminal = this; + mod.toggle(); + } else { + this.print('Debug panel not loaded', 'warning'); + } + } setFontSize(size) { const s = parseInt(size); if (isNaN(s) || s < 8 || s > 32) { diff --git a/modules/ext/debug-panel.js b/modules/ext/debug-panel.js new file mode 100644 index 0000000..8c37253 --- /dev/null +++ b/modules/ext/debug-panel.js @@ -0,0 +1,167 @@ +class DebugPanel { + constructor() { + this.commands = {}; + this.terminal = null; + this._logs = []; + this._original = {}; + this._panel = null; + this._body = null; + this._visible = false; + this.MAX_LOGS = 500; + } + requirements() { + return { met: true, missing: [] }; + } + getHelpInfo() { + return { 'debug': 'debug โ Toggle the debug console panel' }; + } + registerCommands() { + this.commands['debug'] = () => this.toggle(); + } + seed() { + this._intercept(); + } + init() {} + _intercept() { + const self = this; + // Console + for (const m of ['log','warn','error','info']) { + this._original[m] = console[m]; + console[m] = function(...args) { + self._add(m, args); + self._original[m].apply(console, args); + }; + } + // window.onerror + this._original.onerror = window.onerror; + window.onerror = function(msg, url, line, col, err) { + self._add('error', [`${msg} (${url}:${line}:${col})`]); + if (self._original.onerror) self._original.onerror(msg, url, line, col, err); + }; + // Unhandled rejections + window.addEventListener('unhandledrejection', function(e) { + self._add('error', ['Unhandled Promise: ' + (e.reason?.message || e.reason)]); + }); + // Fetch + this._original.fetch = window.fetch; + window.fetch = function(...args) { + const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || '?'; + const method = (args[1]?.method || 'GET'); + const start = performance.now(); + return self._original.fetch.apply(window, args) + .then(function(resp) { + const ms = (performance.now() - start).toFixed(0); + const lvl = resp.status >= 400 ? 'error' : resp.status >= 300 ? 'warn' : 'log'; + self._add(lvl, ['๐ ' + method + ' ' + url + ' โ ' + resp.status + ' (' + ms + 'ms)']); + return resp; + }) + .catch(function(err) { + const ms = (performance.now() - start).toFixed(0); + self._add('error', ['๐ ' + method + ' ' + url + ' FAIL (' + ms + 'ms): ' + err.message]); + throw err; + }); + }; + } + _add(level, args) { + const text = args.map(function(a) { + if (typeof a === 'object') try { return JSON.stringify(a, null, 2); } catch(e) { return String(a); } + return String(a); + }).join(' '); + this._logs.push({ level, text, time: new Date().toLocaleTimeString() }); + if (this._logs.length > this.MAX_LOGS) this._logs.shift(); + if (this._visible) this._appendEntry(this._logs[this._logs.length - 1]); + } + toggle() { + if (!this._panel) this._build(); + this._visible = !this._visible; + this._panel.style.display = this._visible ? 'flex' : 'none'; + if (this._visible) { + this._body.innerHTML = ''; + for (const e of this._logs) this._appendEntry(e); + this._body.scrollTop = this._body.scrollHeight; + } + } + _build() { + const p = document.createElement('div'); + p.id = 'debug-panel'; + p.style.cssText = 'position:fixed;bottom:0;left:0;right:0;height:40vh;background:#0d0d12;border-top:2px solid #1e1e2a;z-index:9999;display:none;flex-direction:column;font-family:monospace;font-size:12px;'; + // Header + const h = document.createElement('div'); + h.style.cssText = 'display:flex;align-items:center;padding:4px 10px;background:#16161e;border-bottom:1px solid #1e1e2a;gap:6px;flex-shrink:0;'; + h.innerHTML = '<span style="color:#00e5c8;font-weight:bold;font-size:13px;">๐ Debug Console</span>'; + // Copy All + const ca = document.createElement('button'); + ca.textContent = '๐ Copy All'; + ca.onclick = () => this._copyAll(); + ca.style.cssText = 'background:#1e1e2a;color:#e8e8f0;border:1px solid #2a2a3a;padding:2px 10px;border-radius:6px;cursor:pointer;font-size:11px;font-family:inherit;'; + // Clear + const cl = document.createElement('button'); + cl.textContent = '๐ Clear'; + cl.onclick = () => { this._logs = []; if (this._body) this._body.innerHTML = ''; }; + cl.style.cssText = ca.style.cssText; + // Close + const cx = document.createElement('button'); + cx.textContent = 'โ'; + cx.onclick = () => this.toggle(); + cx.style.cssText = 'background:none;color:#8888a0;border:none;cursor:pointer;margin-left:auto;font-size:18px;padding:0 4px;'; + h.appendChild(ca); + h.appendChild(cl); + h.appendChild(cx); + p.appendChild(h); + // Body + this._body = document.createElement('div'); + this._body.style.cssText = 'flex:1;overflow-y:auto;padding:4px 10px;background:#111118;'; + p.appendChild(this._body); + document.body.appendChild(p); + this._panel = p; + } + _appendEntry(e) { + const colors = { error: '#ff6bcb', warn: '#ffa500', log: '#00e5c8', info: '#7c6bff' }; + const line = document.createElement('div'); + line.style.cssText = 'padding:3px 4px;border-bottom:1px solid #1a1a26;display:flex;align-items:flex-start;gap:6px;'; + // Badge + const b = document.createElement('span'); + b.textContent = e.level.toUpperCase(); + b.style.cssText = 'color:' + (colors[e.level] || '#888') + ';font-weight:700;font-size:10px;width:40px;flex-shrink:0;'; + // Timestamp + const ts = document.createElement('span'); + ts.textContent = e.time; + ts.style.cssText = 'color:#555;font-size:10px;width:70px;flex-shrink:0;'; + // Message + const m = document.createElement('span'); + m.textContent = e.text; + m.style.cssText = 'color:#c8c8d0;flex:1;word-break:break-all;white-space:pre-wrap;'; + // Copy + const cp = document.createElement('button'); + cp.textContent = 'Copy'; + cp.onclick = function() { + var ta = document.createElement('textarea'); + ta.value = e.text; + ta.style.cssText = 'position:fixed;left:-999px;top:0;'; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + cp.textContent = 'โ'; + setTimeout(function() { cp.textContent = 'Copy'; }, 1500); + }; + cp.style.cssText = 'background:#1e1e2a;color:#8888a0;border:1px solid #2a2a3a;border-radius:4px;cursor:pointer;font-size:10px;padding:1px 6px;flex-shrink:0;font-family:inherit;'; + line.appendChild(b); + line.appendChild(ts); + line.appendChild(m); + line.appendChild(cp); + this._body.appendChild(line); + this._body.scrollTop = this._body.scrollHeight; + } + _copyAll() { + const text = this._logs.map(function(e) { return '[' + e.level.toUpperCase() + '] ' + e.time + ' ' + e.text; }).join('\n'); + var ta = document.createElement('textarea'); + ta.value = text; + ta.style.cssText = 'position:fixed;left:-999px;top:0;'; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + if (this.terminal) this.terminal.print('๐ Debug log copied (' + this._logs.length + ' entries)', 'success'); + } +}
\ No newline at end of file diff --git a/packages.json b/packages.json index 9277701..dd1afbd 100644 --- a/packages.json +++ b/packages.json @@ -226,6 +226,16 @@ ], "category": "developer" }, + "debug-panel": { + "name": "Debug Panel", + "version": "1.0.0", + "desc": "Toggleable debug console โ intercepts console.log/error, fetch failures, JS errors. Copy All + per-line copy.", + "url": "modules/ext/debug-panel.js", + "commands": [ + "debug" + ], + "category": "developer" + }, "tutorial": { "name": "Tutorial", "version": "1.0.0", |