aboutsummaryrefslogtreecommitdiff
path: root/modules/core/terminal.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-01 18:00:38 +0200
committerAgando <roduccomm@gmail.com>2026-06-01 18:00:38 +0200
commitb839e58aa45da1945d0c46c8070b4e7016c59f94 (patch)
treed394c7ee9c338e8f449f9f09e53e59f8d41ec5c1 /modules/core/terminal.js
parent6aed9c9e0252b0c8f681a358e5a8707271454137 (diff)
auto: 2026-06-01_18:00 changes
Diffstat (limited to 'modules/core/terminal.js')
-rw-r--r--modules/core/terminal.js70
1 files changed, 64 insertions, 6 deletions
diff --git a/modules/core/terminal.js b/modules/core/terminal.js
index 5008842..607c51a 100644
--- a/modules/core/terminal.js
+++ b/modules/core/terminal.js
@@ -399,7 +399,7 @@
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/`/g, '&#96;');
- return `<div class="ai-render-container"><iframe sandbox="allow-scripts" srcdoc="${safeSrcdoc}" class="ai-render-iframe" onload="var _s=this;setTimeout(function(){_s.style.height=Math.max((_s.contentWindow.document.documentElement.scrollHeight||200),200)+'px'},100)"></iframe></div>`;
+ return `<div class="ai-render-container"><div style="display:flex;gap:6px;padding:4px 8px;background:var(--bg-tertiary);border-bottom:1px solid var(--border-color);align-items:center;"><span style="color:var(--text-muted);font-size:0.75em;">📄 Rendered HTML</span><span style="flex:1"></span><button class="qa-btn" style="padding:2px 10px;font-size:0.75em;" onclick="webx11.openRenderPreview(this)" data-content="${safeSrcdoc.replace(/"/g, '&quot;')}">⛶ Fullscreen</button></div><iframe sandbox="allow-scripts" srcdoc="${safeSrcdoc}" class="ai-render-iframe" onload="var _s=this;setTimeout(function(){_s.style.height=Math.max((_s.contentWindow.document.documentElement.scrollHeight||200),200)+'px'},100)"></iframe></div>`;
}
_renderHTMLBlock(text) {
const renderRegex = /:::render-html\r?\n([\s\S]*?)\r?\n:::/g;
@@ -483,9 +483,42 @@
this._streamChunks = [];
}
}
+ printUserPrompt(rawInput) {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'output-line user-prompt-line';
+ 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>
+ `;
+ this.outputContainer.appendChild(wrapper);
+ this._historyBuffer.push({ l: rawInput, t: 'command-prompt' });
+ if (this._historyBuffer.length > this._historyMaxLines) this._historyBuffer.shift();
+ this.scrollToBottom();
+ this._trimOutput();
+ }
+ editPrompt(text) {
+ this.inputField.value = text;
+ this.inputField.focus();
+ this.inputField.setSelectionRange(text.length, text.length);
+ }
copyToClipboard(btn) {
- const contentDiv = btn.nextElementSibling;
- const text = contentDiv.innerText;
+ 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;
+ } else {
+ // AI response: text is in next sibling .ai-content
+ const contentDiv = btn.nextElementSibling;
+ if (contentDiv) text = contentDiv.innerText;
+ }
+ if (!text) return;
navigator.clipboard.writeText(text).then(() => {
const originalText = btn.textContent;
btn.textContent = 'Copied!';
@@ -675,6 +708,8 @@
this._dirty = false;
}, 180000);
window.addEventListener('beforeunload', () => {
+ // Flush session before tab closes — avoids losing up to 3min of output on crash/close
+ try { this._autoSaveSession(); } catch (e) {}
if (this._autoSaveTimer) {
clearInterval(this._autoSaveTimer);
this._autoSaveTimer = null;
@@ -1008,7 +1043,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'];
+ const skipList = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr', 'cryptogzip', 'shell', 'tutorial', 'busybox'];
for (const mod of registry) {
if (mod.enabled && mod.url) {
// Skip ext modules — they load via _loadExtModules
@@ -1021,7 +1056,7 @@
async executeCommand(rawInput) {
if (!rawInput.trim()) return;
this.markDirty();
- this.print(`webx11@module:~$ ${rawInput}`, 'command-prompt');
+ this.printUserPrompt(rawInput);
const pipeParts = this.splitPipe(rawInput.trim());
if (pipeParts.length > 1) {
await this.executePipeChain(pipeParts);
@@ -1248,6 +1283,29 @@
iframe.src = 'about:blank';
this.inputField.focus();
}
+ openRenderPreview(btn) {
+ const content = btn.getAttribute('data-content');
+ if (!content) return;
+ const iframe = document.getElementById('preview-iframe');
+ // The srcdoc content is already HTML-escaped from _renderHTMLPreview
+ const decoded = content.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#96;/g, '`');
+ iframe.srcdoc = decoded;
+ document.getElementById('preview-title').textContent = '📄 AI Rendered Preview (Fullscreen)';
+ document.getElementById('preview-modal').style.display = 'flex';
+ }
+ previewHtmlFromPrompt(btn) {
+ const content = btn.getAttribute('data-content');
+ if (!content) return;
+ const iframe = document.getElementById('preview-iframe');
+ // Content is HTML-escaped, decode it back
+ const decoded = content.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#96;/g, '`');
+ // Wrap in a basic HTML template if it doesn't have html/head/body tags
+ const hasOwnDoc = /<html|<head|<body/i.test(decoded);
+ const fullDoc = hasOwnDoc ? decoded : `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><style>body{background:#000;color:#0f0;font-family:monospace;margin:16px;}</style></head><body>${decoded}</body></html>`;
+ iframe.srcdoc = fullDoc;
+ document.getElementById('preview-title').textContent = '📄 User HTML Preview';
+ document.getElementById('preview-modal').style.display = 'flex';
+ }
openSkillsManager() {
const modal = document.getElementById('skills-manager');
// Ensure /skills/ directory exists
@@ -1671,7 +1729,7 @@
'module-fetcher.js','module-skills.js','module-chatstudio.js'
];
const extFiles = [
- 'calc.js','voice.js','cron.js','theme.js','wpm.js','mindmap.js','skill-pack-core.js'
+ 'calc.js','voice.js','cron.js','theme.js','wpm.js','mindmap.js','webdav.js','skill-pack-core.js','editor.js','diff.js','alias.js','finder.js','filemgr.js','cryptogzip.js','tutorial.js','shell.js','busybox.js'
];
const addSection = (title) => {