|
5259 | 5259 | /* ════════════════════════════════════════════════════════ |
5260 | 5260 | CHAT |
5261 | 5261 | ════════════════════════════════════════════════════════ */ |
| 5262 | +/** |
| 5263 | + * Collapses older "Screen elements:" user bubbles in the visible chat to |
| 5264 | + * "no longer relevant", keeping only the N most recent intact |
| 5265 | + * (N = _executionPolicy.maxRelevantScreenElementMessages, default 3). |
| 5266 | + * Only user bubbles are affected. Termux output bubbles are not touched. |
| 5267 | + * Called after every addUserBubble() and after _renderRestoredChatBubbles(). |
| 5268 | + */ |
| 5269 | +function _truncateScreenElementBubblesInDom() { |
| 5270 | + const marker = (_operationalTuning && _operationalTuning.screenElementsMarker) |
| 5271 | + ? _operationalTuning.screenElementsMarker |
| 5272 | + : 'Screen elements:'; |
| 5273 | + const termuxMarker = 'Termux output:'; |
| 5274 | + const noLongerRelevant = 'no longer relevant'; |
| 5275 | + const maxKeep = (_executionPolicy && typeof _executionPolicy.maxRelevantScreenElementMessages === 'number') |
| 5276 | + ? _executionPolicy.maxRelevantScreenElementMessages |
| 5277 | + : 3; |
| 5278 | + |
| 5279 | + // Collect all user bubbles that contain screen elements (but not termux-only bubbles) |
| 5280 | + const allUserBubbles = [...document.querySelectorAll('#chat-messages .bubble-wrap.user-wrap .bubble.user')]; |
| 5281 | + const screenElementBubbles = allUserBubbles.filter(el => { |
| 5282 | + const text = el.textContent || ''; |
| 5283 | + const hasScreen = text.toLowerCase().includes(marker.toLowerCase()); |
| 5284 | + if (!hasScreen) return false; |
| 5285 | + // Exclude bubbles where the only output is Termux (no Screen elements section) |
| 5286 | + return true; |
| 5287 | + }); |
| 5288 | + |
| 5289 | + if (screenElementBubbles.length <= maxKeep) return; |
| 5290 | + |
| 5291 | + // The oldest ones (all but the last maxKeep) get truncated |
| 5292 | + const toTruncate = screenElementBubbles.slice(0, screenElementBubbles.length - maxKeep); |
| 5293 | + for (const bubble of toTruncate) { |
| 5294 | + const text = bubble.textContent || ''; |
| 5295 | + const markerIdx = text.toLowerCase().indexOf(marker.toLowerCase()); |
| 5296 | + if (markerIdx < 0) continue; |
| 5297 | + // Check if already replaced |
| 5298 | + const afterMarker = text.substring(markerIdx + marker.length).trim(); |
| 5299 | + if (afterMarker.toLowerCase() === noLongerRelevant) continue; |
| 5300 | + // Replace the text content: keep everything before the marker, add marker + no longer relevant |
| 5301 | + const prefix = text.substring(0, markerIdx); |
| 5302 | + // We need to update only the text node (the bubble may contain an img child for screenshots) |
| 5303 | + // Strategy: walk child nodes, replace text nodes to rewrite content |
| 5304 | + _replaceBubbleTextUpToMarker(bubble, prefix, marker, noLongerRelevant); |
| 5305 | + } |
| 5306 | +} |
| 5307 | + |
| 5308 | +/** |
| 5309 | + * Rewrites the text content of a user bubble element so that everything from |
| 5310 | + * the screen-elements marker onwards is replaced with "no longer relevant", |
| 5311 | + * while preserving any non-text child elements (e.g. screenshot thumbnails). |
| 5312 | + */ |
| 5313 | +function _replaceBubbleTextUpToMarker(bubble, prefix, marker, replacement) { |
| 5314 | + // Build the new plain-text portion |
| 5315 | + const newPlainText = prefix + marker + ' ' + replacement; |
| 5316 | + // Remove all text nodes (keep element children like img) |
| 5317 | + const childNodes = [...bubble.childNodes]; |
| 5318 | + for (const node of childNodes) { |
| 5319 | + if (node.nodeType === Node.TEXT_NODE) { |
| 5320 | + node.remove(); |
| 5321 | + } |
| 5322 | + } |
| 5323 | + // Prepend the new text as a text node at the start |
| 5324 | + bubble.insertBefore(document.createTextNode(newPlainText), bubble.firstChild); |
| 5325 | +} |
| 5326 | + |
5262 | 5327 | function addUserBubble(text, imageDataUri) { |
5263 | 5328 | const wrap = document.createElement('div'); |
5264 | 5329 | wrap.className = 'bubble-wrap user-wrap'; |
|
5271 | 5336 | : ''; |
5272 | 5337 | wrap.innerHTML = `<div class="bubble user">${escHtml(text)}${thumbHtml}</div>${undoBtnHtml}`; |
5273 | 5338 | document.getElementById('chat-messages').appendChild(wrap); |
| 5339 | + _truncateScreenElementBubblesInDom(); |
5274 | 5340 | scrollToBottom(); |
5275 | 5341 | } |
5276 | 5342 | /** Shows a placeholder user-side bubble with a spinner while waiting for Termux output. */ |
|
5954 | 6020 | if (m.role === 'user') addUserBubble(m.text || '', m.imageDataUri); |
5955 | 6021 | else addModelBubble(m.text, false); |
5956 | 6022 | } |
| 6023 | + // After restoring all bubbles, apply screen-element truncation once |
| 6024 | + _truncateScreenElementBubblesInDom(); |
5957 | 6025 | } |
5958 | 6026 |
|
5959 | 6027 | /** |
|
0 commit comments