diff options
| author | Agando <roduccomm@gmail.com> | 2026-05-31 06:00:55 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-05-31 06:00:55 +0200 |
| commit | 7cc82d2d32e44ab1c5b7d042d5522d036d9aa0be (patch) | |
| tree | 0a64e32f8b77043677e62485d7a0ecdf77e5c2d3 /modules/core/terminal.js | |
| parent | d8bf7fa398d6d928325be9b5c64ca24875bfeef8 (diff) | |
auto: 2026-05-31_06:00 changes
Diffstat (limited to 'modules/core/terminal.js')
| -rw-r--r-- | modules/core/terminal.js | 97 |
1 files changed, 87 insertions, 10 deletions
diff --git a/modules/core/terminal.js b/modules/core/terminal.js index de2f4a7..d53ab70 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -419,6 +419,7 @@ return fragments.length > 0 ? fragments : null; } printAIResponse(text) { + this._flushStreamChunks(); const fragments = this._renderHTMLBlock(text); if (fragments) { for (const frag of fragments) { @@ -443,6 +444,7 @@ this._trimOutput(); } printStreamingResponse(text, append) { + if (!this._streamChunks) this._streamChunks = []; if (!append) { const safeText = text.replace(/</g, "<").replace(/>/g, ">"); const wrapper = document.createElement('div'); @@ -452,16 +454,30 @@ <div class="ai-content">${safeText}</div> `; this.outputContainer.appendChild(wrapper); + this._streamChunks = []; } else { const contentDiv = this.outputContainer.querySelector('.output-line.ollama:last-child .ai-content'); if (contentDiv) { - const safeText = text.replace(/</g, "<").replace(/>/g, ">"); - contentDiv.textContent += safeText; + this._streamChunks.push(text); + if (this._streamChunks.length >= 5) { + contentDiv.textContent += this._streamChunks.join(''); + this._streamChunks = []; + if (contentDiv.textContent.length > 50000) { + contentDiv.textContent = contentDiv.textContent.slice(-30000); + } + } } } this.scrollToBottom(); this._trimOutput(); } + _flushStreamChunks() { + const contentDiv = this.outputContainer.querySelector('.output-line.ollama:last-child .ai-content'); + if (contentDiv && this._streamChunks && this._streamChunks.length > 0) { + contentDiv.textContent += this._streamChunks.join(''); + this._streamChunks = []; + } + } copyToClipboard(btn) { const contentDiv = btn.nextElementSibling; const text = contentDiv.innerText; @@ -576,8 +592,9 @@ } if (toRender.length > batchSize) { const remaining = toRender.slice(batchSize); - const doBatch = (index) => { - if (index >= remaining.length) { + const MAX_BATCHES = 50; + const doBatch = (index, batchCount) => { + if (index >= remaining.length || batchCount >= MAX_BATCHES) { this.scrollToBottom(); return; } @@ -589,10 +606,10 @@ line.textContent = remaining[i].l || ''; this.outputContainer.appendChild(line); } - doBatch(end); + doBatch(end, (batchCount || 0) + 1); }); }; - doBatch(0); + doBatch(0, 0); } else { this.scrollToBottom(); } @@ -652,12 +669,29 @@ this._autoSaveSession(); this._dirty = false; }, 180000); + window.addEventListener('beforeunload', () => { + if (this._autoSaveTimer) { + clearInterval(this._autoSaveTimer); + this._autoSaveTimer = null; + } + }); } _autoSaveSession() { try { this.fs.mkdirRecursive('_sessions'); - // Clone fsRoot and strip _sessions to prevent recursive nesting - var rootClone = JSON.parse(JSON.stringify(this.fs.root)); + var rootClone; + try { + const json = JSON.stringify(this.fs.root); + if (json.length > 5242880) { // 5MB cap + rootClone = { type: 'directory', name: '/', content: {}, _truncated: true }; + this.print('⚠ Auto-save skipped (VFS too large)', 'warning'); + return; + } + rootClone = JSON.parse(json); + } catch (e) { + rootClone = { type: 'directory', name: '/', content: {}, _error: true }; + return; + } if (rootClone && rootClone.content && rootClone.content['_sessions']) { delete rootClone.content['_sessions']; } @@ -669,8 +703,12 @@ version: 1, terminalLines: this._historyBuffer.slice(-50) }; - this.fs.writeFile('_sessions/_auto.json', JSON.stringify(snapshot, null, 2)); - // Also persist AI context concurrently + const snapshotJson = JSON.stringify(snapshot); + if (snapshotJson.length > 5242880) { + this.print('⚠ Auto-save snapshot too large, skipping', 'warning'); + return; + } + this.fs.writeFile('_sessions/_auto.json', snapshotJson); try { const ctx = localStorage.getItem('webx11_ai_context'); if (ctx) this.fs.writeFile('_sessions/_auto-context.json', ctx); @@ -1451,6 +1489,28 @@ } this.ollamaManager.saveSettings(); } + toggleStream() { + const cb = document.getElementById('ollama-stream-toggle'); + const label = document.getElementById('ollama-stream-label'); + const slider = document.getElementById('ollama-stream-slider'); + const enabled = cb.checked; + this.ollamaManager.setStreamEnabled(enabled); + if (label) { + label.textContent = enabled ? 'Enabled' : 'Disabled'; + label.style.color = enabled ? 'var(--success-color)' : 'var(--text-muted)'; + } + if (slider) { + slider.style.background = enabled ? 'var(--accent-color)' : 'var(--bg-tertiary)'; + slider.style.borderColor = enabled ? 'var(--success-color)' : 'var(--border-color)'; + } + } + updateStreamIndicator() { + const el = document.getElementById('stream-status'); + if (!el) return; + const enabled = this.ollamaManager?.streamEnabled !== false; + el.textContent = enabled ? '▶ Stream' : '🔇 No-Stream'; + el.style.color = enabled ? 'var(--success-color)' : 'var(--text-muted)'; + } saveValidatorConfig() { const select = document.getElementById('validator-model-select'); const endpoint = document.getElementById('validator-endpoint-input')?.value?.trim() || 'http://localhost:11434'; @@ -1529,6 +1589,23 @@ valSlider.style.borderColor = enabled ? 'var(--info-color)' : 'var(--border-color)'; } } + // Initialize stream toggle from stored state + const strCb = document.getElementById('ollama-stream-toggle'); + const strLabel = document.getElementById('ollama-stream-label'); + const strSlider = document.getElementById('ollama-stream-slider'); + if (strCb && this.ollamaManager) { + const enabled = this.ollamaManager.streamEnabled !== false; + strCb.checked = enabled; + if (strLabel) { + strLabel.textContent = enabled ? 'Enabled' : 'Disabled'; + strLabel.style.color = enabled ? 'var(--success-color)' : 'var(--text-muted)'; + } + if (strSlider) { + strSlider.style.background = enabled ? 'var(--accent-color)' : 'var(--bg-tertiary)'; + strSlider.style.borderColor = enabled ? 'var(--success-color)' : 'var(--border-color)'; + } + } + this.updateStreamIndicator(); // Populate validator model dropdown from stored config const valSelect = document.getElementById('validator-model-select'); const valEndpoint = document.getElementById('validator-endpoint-input'); |