aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/core/module-aiassist.js4
-rw-r--r--modules/core/module-fs.js28
-rw-r--r--modules/core/module-loader.js8
-rw-r--r--modules/core/module-system.js20
-rw-r--r--modules/core/terminal.js117
5 files changed, 102 insertions, 75 deletions
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, "&lt;").replace(/>/g, "&gt;");
+ 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, "&lt;").replace(/>/g, "&gt;");
- 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, "&lt;").replace(/>/g, "&gt;");
- 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, "&lt;").replace(/>/g, "&gt;");
- 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, "&lt;").replace(/>/g, "&gt;");
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, '&lt;').replace(/>/g, '&gt;');
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, '&quot;')}" 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, '&quot;') + '">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');
}
}