diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-02 06:00:29 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-02 06:00:29 +0200 |
| commit | 6d41e590a38c73ef60f1c04beaf8f5438f350f46 (patch) | |
| tree | dc94aba0816e7bb87834cf518cbfe78d7a95f397 | |
| parent | b839e58aa45da1945d0c46c8070b4e7016c59f94 (diff) | |
auto: 2026-06-02_06:00 changes
| -rw-r--r-- | main.html | 68 | ||||
| -rw-r--r-- | modules/core/module-aiassist.js | 4 | ||||
| -rw-r--r-- | modules/core/module-fs.js | 28 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 8 | ||||
| -rw-r--r-- | modules/core/module-system.js | 20 | ||||
| -rw-r--r-- | modules/core/terminal.js | 117 |
6 files changed, 155 insertions, 90 deletions
@@ -27,13 +27,13 @@ background-color: var(--bg-color); color: var(--text-color); font-family: var(--font-primary); - font-size: 16px; + font-size: var(--term-font-size, 16px); height: 100vh; display: flex; flex-direction: column; overflow: hidden; } - @media (max-width: 600px) { body { font-size: 12px; } } + @media (max-width: 600px) { body { font-size: var(--term-font-size, 12px); } } header { border-bottom: 1px solid var(--border-color); padding: 10px; @@ -378,23 +378,61 @@ #ollama-stream-toggle:checked + #ollama-stream-slider { border-color: var(--success-color); } - .copy-btn { - position: absolute; - top: 5px; - right: 5px; - background: rgba(0, 0, 0, 0.5); + /* ── Card Layout Styles ── */ + .msg-card { + margin-bottom: 8px; + border: 1px solid var(--border-color); + border-radius: 6px; + overflow: hidden; + background: var(--bg-secondary); + } + .msg-card.user { border-left: 3px solid var(--success-color); } + .msg-card.ai { border-left: 3px solid var(--ai-text-color); background: var(--ai-bg-color); } + .msg-card.system { border-left: 3px solid var(--text-muted); } + .msg-card-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 10px; + border-bottom: 1px solid var(--border-color); + background: var(--bg-tertiary); + } + .msg-role { font-weight: bold; font-size: 0.85em; white-space: nowrap; } + .msg-buttons { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; } + .msg-card-body { + padding: 10px; + white-space: pre-wrap; + word-break: break-word; + line-height: 1.4; + font-size: 0.9em; + } + .card-btn { + display: inline-flex; + padding: 4px 12px; + font-size: 0.75em; + font-family: inherit; + background: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-muted); - font-size: 0.7em; - padding: 2px 6px; + border-radius: 4px; cursor: pointer; - border-radius: 3px; - font-family: sans-serif; + touch-action: manipulation; + transition: background 0.15s; + white-space: nowrap; + line-height: 1.4; } - .copy-btn:hover { - color: var(--text-color); - background: rgba(0, 255, 0, 0.1); + .card-btn:hover, .card-btn:active { background: var(--accent-color); color: var(--bg-color); } + .msg-card.ai .ai-render-container { + margin: 0; + border: 1px solid var(--border-color); + } + .msg-card.ai .ai-content { + display: inline; + } + .msg-card.system .msg-card-body { + color: inherit; } + /* ── End Card Layout ── */ .ai-render-container { margin: 10px 0; border-radius: 6px; @@ -1057,7 +1095,7 @@ </script> <!-- Async dependency-ordered module loader (replaces 26 synchronous script tags) --> - <script src="modules/core/module-loader.js?v=35"></script> + <script src="modules/core/module-loader.js?v=39"></script> </body> </html> diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index f7be209..edc5225 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -568,7 +568,7 @@ RULES: (entry.error ? ' <span class="debug-err">⚠️</span>' : '') + '</div>'; } - panel.innerHTML = '<div class="debug-header">🔧 Tool Log <span class="debug-count">' + this._toolCallLog.length + ' calls</span></div>' + html; + panel.innerHTML = '<div class="debug-header" style="display:flex;justify-content:space-between;align-items:center;padding:6px 10px;border-bottom:1px solid #333;font-weight:bold;color:var(--success-color);font-size:0.9em;">🔧 Tool Log <span class="debug-count" style="color:var(--text-muted);font-weight:normal;font-size:0.85em;">' + this._toolCallLog.length + ' calls</span><span style="cursor:pointer;color:var(--text-muted);font-size:1.1em;touch-action:manipulation;" onclick="var p=document.getElementById(\'debug-panel\');if(p)p.style.display=\'none\';var a=window.webx11&&webx11.moduleManager&&webx11.moduleManager.modules&&webx11.moduleManager.modules.WebX11AIAssistantModule;if(a)a._debugPanelEnabled=false;">✕</span></div>' + html; } _isToolError(result) { if (!result || typeof result !== 'string') return null; @@ -931,7 +931,7 @@ RULES: // Create the debug panel panel = document.createElement('div'); panel.id = 'debug-panel'; - panel.style.cssText = 'position:fixed;right:10px;bottom:50px;width:380px;max-height:400px;overflow-y:auto;background:#0a0a0a;border:1px solid #333;border-radius:6px;z-index:9999;font-family:monospace;font-size:0.78em;display:none;'; + panel.style.cssText = 'position:fixed;right:10px;top:50px;width:340px;max-height:350px;overflow-y:auto;background:#0a0a0a;border:1px solid #333;border-radius:6px;z-index:100;font-family:monospace;font-size:0.75em;display:none;'; document.body.appendChild(panel); // Add close button handler panel.addEventListener('click', function() { diff --git a/modules/core/module-fs.js b/modules/core/module-fs.js index ae8b5f4..da2e740 100644 --- a/modules/core/module-fs.js +++ b/modules/core/module-fs.js @@ -148,37 +148,17 @@ class FileSystemModule { }; this.commands['mv'] = (args) => { if(!args[0] || !args[1]) return this.terminal.print('Usage: mv <source> <dest>', 'warning'); - const srcDir = this.terminal.fs.readDir(args[0]); - if (srcDir !== null) { - if (this.terminal.fs.moveDir(args[0], args[1])) { - this.terminal.print('Moved directory: ' + args[0] + ' to ' + args[1], 'success'); - } else { - this.terminal.print('mv: ' + args[0] + ': failed to move directory', 'error'); - } - return; - } - if (this.terminal.fs.moveFile(args[0], args[1])) { + if (this.terminal.fs.mv(args[0], args[1])) this.terminal.print('Moved: ' + args[0] + ' to ' + args[1], 'success'); - } else { + else this.terminal.print('mv: ' + args[0] + ': No such file or directory', 'error'); - } }; this.commands['cp'] = (args) => { if(!args[0] || !args[1]) return this.terminal.print('Usage: cp <source> <dest>', 'warning'); - const srcDir = this.terminal.fs.readDir(args[0]); - if (srcDir !== null) { - if (this.terminal.fs.copyDir(args[0], args[1])) { - this.terminal.print('Copied directory: ' + args[0] + ' to ' + args[1], 'success'); - } else { - this.terminal.print('cp: ' + args[0] + ': failed to copy directory', 'error'); - } - return; - } - if (this.terminal.fs.copyFile(args[0], args[1])) { + if (this.terminal.fs.cp(args[0], args[1])) this.terminal.print('Copied: ' + args[0] + ' to ' + args[1], 'success'); - } else { + else this.terminal.print('cp: ' + args[0] + ': No such file or directory', 'error'); - } }; this.commands['cd'] = (args) => { if (!args[0] || args[0] === '/') { diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 61bde20..7187fec 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -99,9 +99,11 @@ window.__moduleLoaderReload = reloadWithSelection; -const CACHE = '?v=' + (localStorage.getItem('webx11_cache_ver') || '2026-06-01-fix3'); - if (!localStorage.getItem('webx11_cache_ver')) { - localStorage.setItem('webx11_cache_ver', '2026-06-01-fix3'); +const LATEST_VER = '2026-06-02-fontfix3'; +const CACHE = '?v=' + LATEST_VER; + const stored = localStorage.getItem('webx11_cache_ver'); + if (stored !== LATEST_VER) { + localStorage.setItem('webx11_cache_ver', LATEST_VER); } async function init() { diff --git a/modules/core/module-system.js b/modules/core/module-system.js index beed699..e54a5fe 100644 --- a/modules/core/module-system.js +++ b/modules/core/module-system.js @@ -107,6 +107,26 @@ }; this.commands['health-check'] = () => this._healthCheck(); this.commands['memory-map-export'] = () => this._memoryMapExport(); + this.commands['font-size'] = (args) => { + if (!args.length || !args[0]) { + const cur = localStorage.getItem('webx11_font_size'); + this.terminal.print(`Current font-size: ${cur || '16'}px`, 'info'); + this.terminal.print('Usage: font-size <8-32>', 'info'); + return; + } + const val = parseInt(args[0], 10); + if (isNaN(val) || val < 8 || val > 32) { + this.terminal.print('font-size must be between 8 and 32', 'error'); + return; + } + localStorage.setItem('webx11_font_size', val.toString()); + // Apply directly to body — bypasses CSS inheritance issues + document.body.style.fontSize = val + 'px'; + document.getElementById('terminal-container').style.fontSize = val + 'px'; + document.getElementById('command-input').style.fontSize = val + 'px'; + this.terminal.print(`Font size set to ${val}px (saved) 👆`, 'success'); + }; + this.commands['setfont'] = this.commands['font-size']; } _autoRotateLogs(force = false) { try { diff --git a/modules/core/terminal.js b/modules/core/terminal.js index 607c51a..f0ef036 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -7,7 +7,7 @@ this.lastRouteTime = 0; this.lastRouteCmd = ''; this.blockedCount = 0; - this.destructiveCmds = new Set(['rm','delete','del','write','save','overwrite','format','kill','stop','shutdown','reboot']); + this.destructiveCmds = new Set(['delete','del','write','save','overwrite','format','kill','stop','shutdown','reboot']); this.systemCmds = new Set(['reboot','shutdown','format','kill','halt','poweroff']); this.suspiciousFlags = new Set(['--dump','--execute','--eval','--exec','--shell','--script']); this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history']); @@ -374,8 +374,16 @@ } print(text, type = '') { const line = document.createElement('div'); - line.className = `output-line ${type}`; - line.textContent = text; + // Use card layout for typed messages (error/warning/info/success/text-muted), plain for bare text + const isTyped = ['error','warning','success'].includes(type); + if (isTyped) { + line.className = 'output-line msg-card system ' + type; + const safeText = String(text).replace(/</g, "<").replace(/>/g, ">"); + line.innerHTML = '<div class="msg-card-header"><span class="msg-role">⚙ System</span><span class="msg-buttons"><button class="card-btn copy-btn" onclick="webx11.copyToClipboard(this)">Copy</button></span></div><div class="msg-card-body">' + safeText + '</div>'; + } else { + line.className = 'output-line'; + line.textContent = text; + } this.outputContainer.appendChild(line); this._historyBuffer.push({ l: text, t: type }); if (this._historyBuffer.length > this._historyMaxLines) { @@ -411,7 +419,7 @@ const before = text.slice(lastIndex, match.index); if (before.trim()) { const safeBefore = before.replace(/</g, "<").replace(/>/g, ">"); - fragments.push({ type: 'text', html: `<button class="copy-btn" onclick="webx11.copyToClipboard(this)">Copy</button><div class="ai-content">${safeBefore}</div>` }); + fragments.push({ type: 'text', html: '<div class="ai-content">' + safeBefore + '</div>' }); } fragments.push({ type: 'render', html: this._renderHTMLPreview(match[1].trim()) }); lastIndex = match.index + match[0].length; @@ -419,7 +427,7 @@ const after = text.slice(lastIndex); if (after.trim()) { const safeAfter = after.replace(/</g, "<").replace(/>/g, ">"); - fragments.push({ type: 'text', html: `<button class="copy-btn" onclick="webx11.copyToClipboard(this)">Copy</button><div class="ai-content">${safeAfter}</div>` }); + fragments.push({ type: 'text', html: '<div class="ai-content">' + safeAfter + '</div>' }); } return fragments.length > 0 ? fragments : null; } @@ -427,23 +435,24 @@ this._flushStreamChunks(); const fragments = this._renderHTMLBlock(text); if (fragments) { + const wrapper = document.createElement('div'); + wrapper.className = 'output-line msg-card ai'; + wrapper.innerHTML = '<div class="msg-card-header"><span class="msg-role">🤖 AI</span><span class="msg-buttons"><button class="card-btn copy-btn" onclick="webx11.copyToClipboard(this)">Copy</button></span></div><div class="msg-card-body"></div>'; + const body = wrapper.querySelector('.msg-card-body'); for (const frag of fragments) { - const wrapper = document.createElement('div'); - wrapper.className = 'output-line' + (frag.type === 'text' ? ' ollama' : ''); - wrapper.innerHTML = frag.html; - this.outputContainer.appendChild(wrapper); + const div = document.createElement('div'); + div.innerHTML = frag.html; + body.appendChild(div); } + this.outputContainer.appendChild(wrapper); this.scrollToBottom(); this._trimOutput(); return; } const wrapper = document.createElement('div'); - wrapper.className = 'output-line ollama'; + wrapper.className = 'output-line msg-card ai'; const safeText = text.replace(/</g, "<").replace(/>/g, ">"); - wrapper.innerHTML = ` - <button class="copy-btn" onclick="webx11.copyToClipboard(this)">Copy</button> - <div class="ai-content">${safeText}</div> - `; + wrapper.innerHTML = '<div class="msg-card-header"><span class="msg-role">🤖 AI</span><span class="msg-buttons"><button class="card-btn copy-btn" onclick="webx11.copyToClipboard(this)">Copy</button></span></div><div class="msg-card-body">' + safeText + '</div>'; this.outputContainer.appendChild(wrapper); this.scrollToBottom(); this._trimOutput(); @@ -453,15 +462,12 @@ if (!append) { const safeText = text.replace(/</g, "<").replace(/>/g, ">"); const wrapper = document.createElement('div'); - wrapper.className = 'output-line ollama'; - wrapper.innerHTML = ` - <button class="copy-btn" onclick="webx11.copyToClipboard(this)">Copy</button> - <div class="ai-content">${safeText}</div> - `; + wrapper.className = 'output-line msg-card ai'; + wrapper.innerHTML = '<div class="msg-card-header"><span class="msg-role">🤖 AI</span><span class="msg-buttons"><button class="card-btn copy-btn" onclick="webx11.copyToClipboard(this)">Copy</button></span></div><div class="msg-card-body">' + safeText + '</div>'; this.outputContainer.appendChild(wrapper); this._streamChunks = []; } else { - const contentDiv = this.outputContainer.querySelector('.output-line.ollama:last-child .ai-content'); + const contentDiv = this.outputContainer.querySelector('.msg-card.ai:last-child .msg-card-body'); if (contentDiv) { this._streamChunks.push(text); if (this._streamChunks.length >= 5) { @@ -477,7 +483,7 @@ this._trimOutput(); } _flushStreamChunks() { - const contentDiv = this.outputContainer.querySelector('.output-line.ollama:last-child .ai-content'); + const contentDiv = this.outputContainer.querySelector('.msg-card.ai:last-child .msg-card-body'); if (contentDiv && this._streamChunks && this._streamChunks.length > 0) { contentDiv.textContent += this._streamChunks.join(''); this._streamChunks = []; @@ -485,17 +491,13 @@ } printUserPrompt(rawInput) { const wrapper = document.createElement('div'); - wrapper.className = 'output-line user-prompt-line'; + wrapper.className = 'output-line msg-card user'; const safe = rawInput.replace(/</g, '<').replace(/>/g, '>'); const hasHtml = /<[a-z][\s\S]*>/i.test(rawInput.trim()); - const previewBtn = hasHtml ? `<button class="copy-btn" onclick="webx11.previewHtmlFromPrompt(this)" data-content="${safe.replace(/"/g, '"')}" title="Preview HTML">👁 Preview</button>` : ''; - wrapper.innerHTML = ` - <span class="prompt-prefix">webx11@module:~$ </span> - <span class="prompt-text">${safe}</span> - <button class="copy-btn user-copy-btn" onclick="webx11.copyToClipboard(this)" title="Copy prompt">Copy</button> - ${previewBtn} - <button class="edit-btn" onclick="webx11.editPrompt('${safe.replace(/'/g, "\\'")}')" title="Edit prompt">Edit</button> - `; + let buttons = '<button class="card-btn copy-btn" onclick="webx11.copyToClipboard(this)">Copy</button>'; + if (hasHtml) buttons += '<button class="card-btn preview-btn" onclick="webx11.previewHtmlFromPrompt(this)" data-content="' + safe.replace(/"/g, '"') + '">Preview</button>'; + buttons += '<button class="card-btn edit-btn" onclick="webx11.editPrompt(\'' + safe.replace(/'/g, "\\'") + '\')">Edit</button>'; + wrapper.innerHTML = '<div class="msg-card-header"><span class="msg-role">👤 User</span><span class="msg-buttons">' + buttons + '</span></div><div class="msg-card-body">' + safe + '</div>'; this.outputContainer.appendChild(wrapper); this._historyBuffer.push({ l: rawInput, t: 'command-prompt' }); if (this._historyBuffer.length > this._historyMaxLines) this._historyBuffer.shift(); @@ -508,22 +510,37 @@ this.inputField.setSelectionRange(text.length, text.length); } copyToClipboard(btn) { - let text; - if (btn.classList.contains('user-copy-btn')) { - // User prompt: text is in .prompt-text sibling - const pSpan = btn.parentElement.querySelector('.prompt-text'); - if (pSpan) text = pSpan.textContent; + let text = ''; + const card = btn.closest('.msg-card'); + if (card) { + const cardBody = card.querySelector('.msg-card-body'); + if (cardBody) text = cardBody.textContent; } else { - // AI response: text is in next sibling .ai-content - const contentDiv = btn.nextElementSibling; - if (contentDiv) text = contentDiv.innerText; + const nd = btn.nextElementSibling; + if (nd) text = nd.innerText || nd.textContent; } if (!text) return; - navigator.clipboard.writeText(text).then(() => { - const originalText = btn.textContent; - btn.textContent = 'Copied!'; - setTimeout(() => { btn.textContent = originalText; }, 2000); - }).catch(err => { console.error('Failed to copy: ', err); }); + btn.textContent = 'Copied!'; + setTimeout(function(){ btn.textContent = 'Copy'; }, 2000); + // Use navigator.clipboard on secure contexts (iOS 14+, modern browsers). + // This is the preferred API — NOTHING blocks it on HTTPS pages. + if (navigator.clipboard) { + navigator.clipboard.writeText(text).catch(function(){}); + } + // Also try textarea+execCommand synchronously for older browsers. + // DO NOT wrap in setTimeout — iOS Safari requires execCommand to run + // in the same microtask as the user's click event. + try { + var ta = document.createElement('textarea'); + ta.value = text; + ta.style.cssText = 'position:fixed;left:10px;top:10px;width:200px;height:40px;font-size:16px;padding:4px;z-index:-1;opacity:0.01'; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + ta.setSelectionRange(0, text.length); + document.execCommand('copy'); + document.body.removeChild(ta); + } catch(e) {} } printClickable(items) { const line = document.createElement('div'); @@ -1853,7 +1870,13 @@ const saved = localStorage.getItem('webx11_font_size'); if (saved) { const s = parseInt(saved); - if (s >= 8 && s <= 32) document.documentElement.style.fontSize = s + 'px'; + if (s >= 8 && s <= 32) { + document.body.style.fontSize = s + 'px'; + const tc = document.getElementById('terminal-container'); + if (tc) tc.style.fontSize = s + 'px'; + const inp = document.getElementById('command-input'); + if (inp) inp.style.fontSize = s + 'px'; + } } } catch (e) {} } @@ -2004,8 +2027,10 @@ this.print('Usage: font-size <8-32>', 'warning'); return; } - document.documentElement.style.fontSize = s + 'px'; + document.body.style.fontSize = s + 'px'; + document.getElementById('terminal-container').style.fontSize = s + 'px'; + document.getElementById('command-input').style.fontSize = s + 'px'; try { localStorage.setItem('webx11_font_size', String(s)); } catch (e) {} - this.print('Font size set to ' + s + 'px', 'success'); + this.print('Font size set to ' + s + 'px 👆', 'success'); } } |