pygpt-net 2.6.38__py3-none-any.whl → 2.6.40__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygpt_net/CHANGELOG.txt +9 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/model/editor.py +20 -42
- pygpt_net/controller/painter/common.py +0 -8
- pygpt_net/core/render/web/body.py +292 -441
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/css/web-blocks.css +1 -1
- pygpt_net/data/css/web-chatgpt.css +1 -1
- pygpt_net/data/css/web-chatgpt_wide.css +1 -1
- pygpt_net/provider/core/config/patch.py +9 -0
- pygpt_net/ui/dialog/models.py +157 -9
- pygpt_net/utils.py +12 -13
- {pygpt_net-2.6.38.dist-info → pygpt_net-2.6.40.dist-info}/METADATA +11 -2
- {pygpt_net-2.6.38.dist-info → pygpt_net-2.6.40.dist-info}/RECORD +18 -18
- {pygpt_net-2.6.38.dist-info → pygpt_net-2.6.40.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.38.dist-info → pygpt_net-2.6.40.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.38.dist-info → pygpt_net-2.6.40.dist-info}/entry_points.txt +0 -0
|
@@ -46,30 +46,29 @@ class Body:
|
|
|
46
46
|
<script type="text/javascript" src="qrc:///js/highlight.min.js"></script>
|
|
47
47
|
<script type="text/javascript" src="qrc:///js/katex.min.js"></script>
|
|
48
48
|
<script>
|
|
49
|
-
if (hljs) {
|
|
49
|
+
if (typeof hljs !== 'undefined') {
|
|
50
50
|
hljs.configure({
|
|
51
51
|
ignoreUnescapedHTML: true,
|
|
52
52
|
});
|
|
53
|
-
}
|
|
54
|
-
|
|
53
|
+
}
|
|
54
|
+
const streamQ = [];
|
|
55
|
+
let DEBUG_MODE = false;
|
|
55
56
|
let bridgeConnected = false;
|
|
56
57
|
let streamHandler;
|
|
57
58
|
let nodeHandler;
|
|
58
59
|
let nodeReplaceHandler;
|
|
59
|
-
let scrollTimeout = null;
|
|
60
60
|
let prevScroll = 0;
|
|
61
61
|
let bridge;
|
|
62
|
-
let streamQ = [];
|
|
63
62
|
let streamRAF = 0;
|
|
63
|
+
let batching = false;
|
|
64
|
+
let needScroll = false;
|
|
64
65
|
let pid = """
|
|
65
66
|
_HTML_P2 = """
|
|
66
67
|
let collapsed_idx = [];
|
|
67
|
-
let domOutputStream =
|
|
68
|
-
let domOutput = document.getElementById('_output_');
|
|
69
|
-
let domInput = document.getElementById('_input_');
|
|
68
|
+
let domOutputStream = null;
|
|
70
69
|
let domLastCodeBlock = null;
|
|
71
70
|
let domLastParagraphBlock = null;
|
|
72
|
-
let
|
|
71
|
+
let domStreamMsg = null;
|
|
73
72
|
let tips = """
|
|
74
73
|
_HTML_P3 = """;
|
|
75
74
|
let tips_hidden = false;
|
|
@@ -78,70 +77,77 @@ class Body:
|
|
|
78
77
|
let highlightScheduled = false;
|
|
79
78
|
let pendingHighlightRoot = null;
|
|
80
79
|
let pendingHighlightMath = false;
|
|
81
|
-
let highlightRAF = 0;
|
|
80
|
+
let highlightRAF = 0;
|
|
82
81
|
let scrollScheduled = false;
|
|
83
82
|
|
|
84
|
-
// Auto-follow state: when false, live stream auto-scroll is suppressed
|
|
85
83
|
let autoFollow = true;
|
|
86
84
|
let lastScrollTop = 0;
|
|
87
|
-
// Tracks whether user has performed any scroll-related interaction
|
|
88
85
|
let userInteracted = false;
|
|
89
|
-
const AUTO_FOLLOW_REENABLE_PX = 8;
|
|
86
|
+
const AUTO_FOLLOW_REENABLE_PX = 8;
|
|
90
87
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
let currentFabAction = 'none'; // tracks current FAB state to avoid redundant work
|
|
88
|
+
const SHOW_DOWN_THRESHOLD_PX = 0;
|
|
89
|
+
let currentFabAction = 'none';
|
|
94
90
|
|
|
95
|
-
// timers
|
|
96
91
|
let tipsTimers = [];
|
|
97
92
|
|
|
98
|
-
// observers
|
|
99
|
-
let roDoc = null;
|
|
100
|
-
let roContainer = null;
|
|
101
|
-
|
|
102
|
-
// FAB (scroll-to-top/bottom) scheduling
|
|
103
93
|
let scrollFabUpdateScheduled = false;
|
|
104
94
|
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
95
|
+
const STREAM_MAX_PER_FRAME = 8;
|
|
96
|
+
const STREAM_EMERGENCY_COALESCE_LEN = 1500;
|
|
97
|
+
|
|
98
|
+
Object.defineProperty(window,'SE',{get(){return document.scrollingElement || document.documentElement;}});
|
|
99
|
+
|
|
100
|
+
let wheelHandler = null;
|
|
101
|
+
let scrollHandler = null;
|
|
102
|
+
let resizeHandler = null;
|
|
103
|
+
let fabClickHandler = null;
|
|
104
|
+
let containerMouseOverHandler = null;
|
|
105
|
+
let containerMouseOutHandler = null;
|
|
106
|
+
let containerClickHandler = null;
|
|
107
|
+
let keydownHandler = null;
|
|
108
|
+
let docClickFocusHandler = null;
|
|
109
|
+
|
|
110
|
+
let fabFreezeUntil = 0;
|
|
111
|
+
const FAB_TOGGLE_DEBOUNCE_MS = 100;
|
|
108
112
|
|
|
109
|
-
// clear previous references
|
|
110
113
|
function resetEphemeralDomRefs() {
|
|
111
114
|
domLastCodeBlock = null;
|
|
112
115
|
domLastParagraphBlock = null;
|
|
116
|
+
domStreamMsg = null;
|
|
113
117
|
}
|
|
114
118
|
function dropIfDetached() {
|
|
115
119
|
if (domLastCodeBlock && !domLastCodeBlock.isConnected) domLastCodeBlock = null;
|
|
116
120
|
if (domLastParagraphBlock && !domLastParagraphBlock.isConnected) domLastParagraphBlock = null;
|
|
121
|
+
if (domStreamMsg && !domStreamMsg.isConnected) domStreamMsg = null;
|
|
117
122
|
}
|
|
118
123
|
function stopTipsTimers() {
|
|
119
124
|
tipsTimers.forEach(clearTimeout);
|
|
120
125
|
tipsTimers = [];
|
|
121
126
|
}
|
|
122
127
|
|
|
123
|
-
function
|
|
124
|
-
|
|
128
|
+
function cleanup() {
|
|
129
|
+
try { if (highlightRAF) { cancelAnimationFrame(highlightRAF); highlightRAF = 0; } } catch (e) {}
|
|
130
|
+
try { if (streamRAF) { cancelAnimationFrame(streamRAF); streamRAF = 0; } } catch (e) {}
|
|
125
131
|
stopTipsTimers();
|
|
126
|
-
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// Clear streaming queue to release memory immediately
|
|
132
|
+
try { bridgeDisconnect(); } catch (e) {}
|
|
133
|
+
if (wheelHandler) document.removeEventListener('wheel', wheelHandler, { passive: true });
|
|
134
|
+
if (scrollHandler) window.removeEventListener('scroll', scrollHandler, { passive: true });
|
|
135
|
+
if (resizeHandler) window.removeEventListener('resize', resizeHandler, { passive: true });
|
|
136
|
+
if (fabClickHandler && els.scrollFab) els.scrollFab.removeEventListener('click', fabClickHandler, { passive: false });
|
|
137
|
+
if (containerMouseOverHandler && els.container) els.container.removeEventListener('mouseover', containerMouseOverHandler, { passive: true });
|
|
138
|
+
if (containerMouseOutHandler && els.container) els.container.removeEventListener('mouseout', containerMouseOutHandler, { passive: true });
|
|
139
|
+
if (containerClickHandler && els.container) els.container.removeEventListener('click', containerClickHandler, { passive: false });
|
|
140
|
+
if (keydownHandler) document.removeEventListener('keydown', keydownHandler, { passive: false });
|
|
141
|
+
if (docClickFocusHandler) document.removeEventListener('click', docClickFocusHandler, { passive: true });
|
|
137
142
|
streamQ.length = 0;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
collapsed_idx.length = 0;
|
|
144
|
+
resetEphemeralDomRefs();
|
|
145
|
+
els = {};
|
|
146
|
+
try { history.scrollRestoration = "auto"; } catch (e) {}
|
|
141
147
|
}
|
|
142
148
|
|
|
143
149
|
history.scrollRestoration = "manual";
|
|
144
|
-
|
|
150
|
+
keydownHandler = function(event) {
|
|
145
151
|
if (event.ctrlKey && event.key === 'f') {
|
|
146
152
|
window.location.href = 'bridge://open_find:' + pid;
|
|
147
153
|
event.preventDefault();
|
|
@@ -150,12 +156,17 @@ class Body:
|
|
|
150
156
|
window.location.href = 'bridge://escape';
|
|
151
157
|
event.preventDefault();
|
|
152
158
|
}
|
|
153
|
-
}
|
|
154
|
-
document.addEventListener('
|
|
159
|
+
};
|
|
160
|
+
document.addEventListener('keydown', keydownHandler, { passive: false });
|
|
161
|
+
|
|
162
|
+
docClickFocusHandler = function(event) {
|
|
163
|
+
if (event.target.closest('#scrollFab')) return;
|
|
155
164
|
if (event.target.tagName !== 'A' && !event.target.closest('a')) {
|
|
156
165
|
window.location.href = 'bridge://focus';
|
|
157
166
|
}
|
|
158
|
-
}
|
|
167
|
+
};
|
|
168
|
+
document.addEventListener('click', docClickFocusHandler, { passive: true });
|
|
169
|
+
|
|
159
170
|
function log(text) {
|
|
160
171
|
if (bridge) {
|
|
161
172
|
bridge.log(text);
|
|
@@ -171,16 +182,13 @@ class Body:
|
|
|
171
182
|
els.footer = document.getElementById('_footer_');
|
|
172
183
|
els.loader = document.getElementById('_loader_');
|
|
173
184
|
els.tips = document.getElementById('tips');
|
|
174
|
-
// FAB refs
|
|
175
185
|
els.scrollFab = document.getElementById('scrollFab');
|
|
176
186
|
els.scrollFabIcon = document.getElementById('scrollFabIcon');
|
|
177
187
|
}
|
|
188
|
+
|
|
178
189
|
function bridgeConnect() {
|
|
179
|
-
|
|
180
|
-
if (!bridge || !bridge.chunk || typeof bridge.chunk.connect !== 'function') return false;
|
|
190
|
+
if (!bridge) return false;
|
|
181
191
|
if (bridgeConnected) return true;
|
|
182
|
-
|
|
183
|
-
// Ensure handler exists and is stable (same identity for disconnect/connect)
|
|
184
192
|
if (!streamHandler) {
|
|
185
193
|
streamHandler = (name, html, chunk, replace, isCode) => {
|
|
186
194
|
appendStream(name, html, chunk, replace, isCode);
|
|
@@ -193,9 +201,9 @@ class Body:
|
|
|
193
201
|
};
|
|
194
202
|
}
|
|
195
203
|
try {
|
|
196
|
-
bridge.chunk.connect(streamHandler);
|
|
197
|
-
bridge.node.connect(nodeHandler);
|
|
198
|
-
bridge.nodeReplace.connect(nodeReplaceHandler);
|
|
204
|
+
if (bridge.chunk && typeof bridge.chunk.connect === 'function') bridge.chunk.connect(streamHandler);
|
|
205
|
+
if (bridge.node && typeof bridge.node.connect === 'function') bridge.node.connect(nodeHandler);
|
|
206
|
+
if (bridge.nodeReplace && typeof bridge.nodeReplace.connect === 'function') bridge.nodeReplace.connect(nodeReplaceHandler);
|
|
199
207
|
bridgeConnected = true;
|
|
200
208
|
return true;
|
|
201
209
|
} catch (e) {
|
|
@@ -203,29 +211,24 @@ class Body:
|
|
|
203
211
|
return false;
|
|
204
212
|
}
|
|
205
213
|
}
|
|
206
|
-
|
|
207
214
|
function bridgeDisconnect() {
|
|
208
|
-
|
|
209
|
-
if (!bridge || !bridge.chunk || typeof bridge.chunk.disconnect !== 'function') return false;
|
|
215
|
+
if (!bridge) return false;
|
|
210
216
|
if (!bridgeConnected) return true;
|
|
211
|
-
|
|
212
217
|
try {
|
|
213
|
-
bridge.chunk.disconnect(streamHandler);
|
|
214
|
-
bridge.node.disconnect(nodeHandler);
|
|
215
|
-
bridge.nodeReplace.disconnect(nodeReplaceHandler);
|
|
216
|
-
} catch (e) {
|
|
218
|
+
if (bridge.chunk && typeof bridge.chunk.disconnect === 'function') bridge.chunk.disconnect(streamHandler);
|
|
219
|
+
if (bridge.node && typeof bridge.node.disconnect === 'function') bridge.node.disconnect(nodeHandler);
|
|
220
|
+
if (bridge.nodeReplace && typeof bridge.nodeReplace.disconnect === 'function') bridge.nodeReplace.disconnect(nodeReplaceHandler);
|
|
221
|
+
} catch (e) { }
|
|
217
222
|
bridgeConnected = false;
|
|
218
|
-
|
|
219
|
-
// Stop scheduled work and release pending chunks immediately
|
|
220
|
-
try { if (streamRAF) { cancelAnimationFrame(streamRAF); streamRAF = 0; } } catch (e) { /* ignore */ }
|
|
223
|
+
try { if (streamRAF) { cancelAnimationFrame(streamRAF); streamRAF = 0; } } catch (e) { }
|
|
221
224
|
streamQ.length = 0;
|
|
222
225
|
return true;
|
|
223
226
|
}
|
|
224
|
-
|
|
225
227
|
function bridgeReconnect() {
|
|
226
228
|
bridgeDisconnect();
|
|
227
229
|
return bridgeConnect();
|
|
228
230
|
}
|
|
231
|
+
|
|
229
232
|
function scheduleHighlight(root, withMath = true) {
|
|
230
233
|
const scope = root && root.nodeType === 1 ? root : document;
|
|
231
234
|
if (!pendingHighlightRoot || pendingHighlightRoot === document) {
|
|
@@ -237,7 +240,6 @@ class Body:
|
|
|
237
240
|
if (highlightScheduled) return;
|
|
238
241
|
highlightScheduled = true;
|
|
239
242
|
if (highlightRAF) {
|
|
240
|
-
// Ensure we do not queue multiple highlight frames
|
|
241
243
|
cancelAnimationFrame(highlightRAF);
|
|
242
244
|
highlightRAF = 0;
|
|
243
245
|
}
|
|
@@ -258,14 +260,10 @@ class Body:
|
|
|
258
260
|
});
|
|
259
261
|
if (withMath) {
|
|
260
262
|
renderMath(root);
|
|
261
|
-
if (DEBUG_MODE) log("math");
|
|
262
263
|
}
|
|
263
|
-
if (DEBUG_MODE) log("execute highlight");
|
|
264
264
|
}
|
|
265
265
|
function highlightCode(withMath = true, root = null) {
|
|
266
|
-
|
|
267
|
-
highlightCodeInternal(root || document, withMath); // prevent blink on fast updates
|
|
268
|
-
// scheduleHighlight(root || document, withMath); // disabled
|
|
266
|
+
highlightCodeInternal(root || document, withMath);
|
|
269
267
|
}
|
|
270
268
|
function hideTips() {
|
|
271
269
|
if (tips_hidden) return;
|
|
@@ -304,7 +302,6 @@ class Body:
|
|
|
304
302
|
showNextTip();
|
|
305
303
|
}
|
|
306
304
|
function renderMath(root) {
|
|
307
|
-
if (DEBUG_MODE) log("execute math");
|
|
308
305
|
const scope = root || document;
|
|
309
306
|
const scripts = scope.querySelectorAll('script[type^="math/tex"]');
|
|
310
307
|
scripts.forEach(function(script) {
|
|
@@ -323,53 +320,46 @@ class Body:
|
|
|
323
320
|
if (parent) parent.replaceChild(element, script);
|
|
324
321
|
});
|
|
325
322
|
}
|
|
323
|
+
|
|
326
324
|
function isNearBottom(marginPx = 100) {
|
|
327
|
-
const el =
|
|
325
|
+
const el = SE;
|
|
328
326
|
const distanceToBottom = el.scrollHeight - el.clientHeight - el.scrollTop;
|
|
329
327
|
return distanceToBottom <= marginPx;
|
|
330
328
|
}
|
|
331
329
|
function scheduleScroll(live = false) {
|
|
332
|
-
// Skip scheduling live auto-scroll when user disabled follow
|
|
333
330
|
if (live === true && autoFollow !== true) return;
|
|
334
331
|
if (scrollScheduled) return;
|
|
335
332
|
scrollScheduled = true;
|
|
336
333
|
requestAnimationFrame(function() {
|
|
337
334
|
scrollScheduled = false;
|
|
338
335
|
scrollToBottom(live);
|
|
339
|
-
// keep FAB state in sync after any programmatic scroll
|
|
340
336
|
scheduleScrollFabUpdate();
|
|
341
337
|
});
|
|
342
338
|
}
|
|
343
|
-
// Force immediate scroll to bottom (pre-interaction bootstrap)
|
|
344
339
|
function forceScrollToBottomImmediate() {
|
|
345
|
-
const el =
|
|
346
|
-
el.scrollTop = el.scrollHeight;
|
|
340
|
+
const el = SE;
|
|
341
|
+
el.scrollTop = el.scrollHeight;
|
|
347
342
|
prevScroll = el.scrollHeight;
|
|
348
343
|
}
|
|
349
344
|
function scrollToBottom(live = false, force = false) {
|
|
350
|
-
const el =
|
|
345
|
+
const el = SE;
|
|
351
346
|
const marginPx = 450;
|
|
352
347
|
const behavior = (live === true) ? 'instant' : 'smooth';
|
|
353
|
-
|
|
354
|
-
// Respect user-follow state during live updates
|
|
355
348
|
if (live === true && autoFollow !== true) {
|
|
356
|
-
// Keep prevScroll consistent for potential consumers
|
|
357
349
|
prevScroll = el.scrollHeight;
|
|
358
350
|
return;
|
|
359
351
|
}
|
|
360
|
-
|
|
361
|
-
// Allow initial auto-follow before any user interaction
|
|
362
352
|
if ((live === true && userInteracted === false) || isNearBottom(marginPx) || live == false || force) {
|
|
363
353
|
el.scrollTo({ top: el.scrollHeight, behavior });
|
|
364
354
|
}
|
|
365
355
|
prevScroll = el.scrollHeight;
|
|
366
356
|
}
|
|
357
|
+
|
|
367
358
|
function appendToInput(content) {
|
|
368
359
|
userInteracted = false;
|
|
369
360
|
const element = els.appendInput || document.getElementById('_append_input_');
|
|
370
361
|
if (element) {
|
|
371
362
|
element.insertAdjacentHTML('beforeend', content);
|
|
372
|
-
highlightCode(true, element);
|
|
373
363
|
scheduleScroll();
|
|
374
364
|
}
|
|
375
365
|
}
|
|
@@ -383,28 +373,49 @@ class Body:
|
|
|
383
373
|
}
|
|
384
374
|
return element;
|
|
385
375
|
}
|
|
376
|
+
function getStreamMsg(create = true, name_header = '') {
|
|
377
|
+
const container = getStreamContainer();
|
|
378
|
+
if (!container) return null;
|
|
379
|
+
if (domStreamMsg && domStreamMsg.isConnected) return domStreamMsg;
|
|
380
|
+
let box = container.querySelector('.msg-box');
|
|
381
|
+
let msg = null;
|
|
382
|
+
if (!box && create) {
|
|
383
|
+
box = document.createElement('div');
|
|
384
|
+
box.classList.add('msg-box');
|
|
385
|
+
box.classList.add('msg-bot');
|
|
386
|
+
if (name_header != '') {
|
|
387
|
+
const name = document.createElement('div');
|
|
388
|
+
name.classList.add('name-header');
|
|
389
|
+
name.classList.add('name-bot');
|
|
390
|
+
name.innerHTML = name_header;
|
|
391
|
+
box.appendChild(name);
|
|
392
|
+
}
|
|
393
|
+
msg = document.createElement('div');
|
|
394
|
+
msg.classList.add('msg');
|
|
395
|
+
box.appendChild(msg);
|
|
396
|
+
container.appendChild(box);
|
|
397
|
+
} else if (box) {
|
|
398
|
+
msg = box.querySelector('.msg');
|
|
399
|
+
}
|
|
400
|
+
if (msg) domStreamMsg = msg;
|
|
401
|
+
return msg;
|
|
402
|
+
}
|
|
386
403
|
function appendNode(content) {
|
|
387
404
|
userInteracted = false;
|
|
388
|
-
if (DEBUG_MODE) {
|
|
389
|
-
log("APPEND NODE: {" + content + "}");
|
|
390
|
-
}
|
|
391
405
|
clearStreamBefore();
|
|
392
406
|
prevScroll = 0;
|
|
393
407
|
const element = els.nodes || document.getElementById('_nodes_');
|
|
394
408
|
if (element) {
|
|
395
409
|
element.classList.remove('empty_list');
|
|
396
410
|
element.insertAdjacentHTML('beforeend', content);
|
|
397
|
-
|
|
398
|
-
scrollToBottom(false);
|
|
411
|
+
scheduleHighlight(element, true);
|
|
412
|
+
scrollToBottom(false);
|
|
399
413
|
scheduleScrollFabUpdate();
|
|
400
414
|
}
|
|
401
415
|
clearHighlightCache();
|
|
402
416
|
}
|
|
403
417
|
function replaceNodes(content) {
|
|
404
418
|
userInteracted = false;
|
|
405
|
-
if (DEBUG_MODE) {
|
|
406
|
-
log("REPLACE NODES: {" + content + "}");
|
|
407
|
-
}
|
|
408
419
|
clearStreamBefore();
|
|
409
420
|
prevScroll = 0;
|
|
410
421
|
const element = els.nodes || document.getElementById('_nodes_');
|
|
@@ -412,16 +423,13 @@ class Body:
|
|
|
412
423
|
element.classList.remove('empty_list');
|
|
413
424
|
element.replaceChildren();
|
|
414
425
|
element.insertAdjacentHTML('beforeend', content);
|
|
415
|
-
|
|
416
|
-
scrollToBottom(false, true);
|
|
426
|
+
scheduleHighlight(element, true);
|
|
427
|
+
scrollToBottom(false, true);
|
|
417
428
|
scheduleScrollFabUpdate();
|
|
418
429
|
}
|
|
419
430
|
clearHighlightCache();
|
|
420
431
|
}
|
|
421
432
|
function clean() {
|
|
422
|
-
if (DEBUG_MODE) {
|
|
423
|
-
log("-- CLEAN DOM --");
|
|
424
|
-
}
|
|
425
433
|
userInteracted = false;
|
|
426
434
|
const el = els.nodes || document.getElementById('_nodes_');
|
|
427
435
|
if (el) {
|
|
@@ -429,15 +437,6 @@ class Body:
|
|
|
429
437
|
}
|
|
430
438
|
resetEphemeralDomRefs();
|
|
431
439
|
els = {};
|
|
432
|
-
/*
|
|
433
|
-
try {
|
|
434
|
-
if (window.gc) {
|
|
435
|
-
window.gc();
|
|
436
|
-
}
|
|
437
|
-
} catch (e) {
|
|
438
|
-
// gc not available
|
|
439
|
-
}
|
|
440
|
-
*/
|
|
441
440
|
}
|
|
442
441
|
function clearHighlightCache() {
|
|
443
442
|
//
|
|
@@ -450,7 +449,7 @@ class Body:
|
|
|
450
449
|
const extra = element.querySelector('.msg-extra');
|
|
451
450
|
if (extra) {
|
|
452
451
|
extra.insertAdjacentHTML('beforeend', content);
|
|
453
|
-
|
|
452
|
+
scheduleHighlight(extra, true);
|
|
454
453
|
scheduleScroll();
|
|
455
454
|
}
|
|
456
455
|
}
|
|
@@ -484,58 +483,33 @@ class Body:
|
|
|
484
483
|
}
|
|
485
484
|
});
|
|
486
485
|
resetEphemeralDomRefs();
|
|
487
|
-
|
|
486
|
+
scheduleHighlight(container, true);
|
|
488
487
|
scheduleScroll();
|
|
489
488
|
}
|
|
490
489
|
}
|
|
491
490
|
function clearStream() {
|
|
492
491
|
hideTips();
|
|
493
|
-
if (DEBUG_MODE) {
|
|
494
|
-
log("STREAM CLEAR");
|
|
495
|
-
}
|
|
496
492
|
domLastParagraphBlock = null;
|
|
497
493
|
domLastCodeBlock = null;
|
|
494
|
+
domStreamMsg = null;
|
|
498
495
|
domOutputStream = null;
|
|
499
|
-
|
|
500
|
-
if (
|
|
501
|
-
|
|
502
|
-
let msg;
|
|
503
|
-
if (!box) {
|
|
504
|
-
box = document.createElement('div');
|
|
505
|
-
box.classList.add('msg-box');
|
|
506
|
-
box.classList.add('msg-bot');
|
|
507
|
-
msg = document.createElement('div');
|
|
508
|
-
msg.classList.add('msg');
|
|
509
|
-
box.appendChild(msg);
|
|
510
|
-
element.appendChild(box);
|
|
511
|
-
} else {
|
|
512
|
-
msg = box.querySelector('.msg');
|
|
513
|
-
}
|
|
514
|
-
if (msg) {
|
|
515
|
-
msg.replaceChildren();
|
|
516
|
-
}
|
|
517
|
-
}
|
|
496
|
+
el = getStreamContainer();
|
|
497
|
+
if (!el) return;
|
|
498
|
+
el.replaceChildren();
|
|
518
499
|
}
|
|
519
500
|
function beginStream() {
|
|
520
501
|
hideTips();
|
|
521
|
-
if (DEBUG_MODE) {
|
|
522
|
-
log("STREAM BEGIN");
|
|
523
|
-
}
|
|
524
502
|
userInteracted = false;
|
|
525
503
|
clearOutput();
|
|
526
|
-
// Ensure initial auto-follow baseline before any chunks overflow
|
|
527
504
|
forceScrollToBottomImmediate();
|
|
528
505
|
scheduleScroll();
|
|
529
506
|
}
|
|
530
507
|
function endStream() {
|
|
531
|
-
if (DEBUG_MODE) {
|
|
532
|
-
log("STREAM END");
|
|
533
|
-
}
|
|
534
508
|
clearOutput();
|
|
535
509
|
bridgeReconnect();
|
|
536
510
|
}
|
|
511
|
+
|
|
537
512
|
function enqueueStream(name_header, content, chunk, replace = false, is_code_block = false) {
|
|
538
|
-
// Push incoming chunk; scheduling is done with RAF to batch DOM ops
|
|
539
513
|
streamQ.push({name_header, content, chunk, replace, is_code_block});
|
|
540
514
|
if (!streamRAF) {
|
|
541
515
|
streamRAF = requestAnimationFrame(drainStream);
|
|
@@ -544,139 +518,112 @@ class Body:
|
|
|
544
518
|
function drainStream() {
|
|
545
519
|
streamRAF = 0;
|
|
546
520
|
let processed = 0;
|
|
547
|
-
|
|
548
|
-
// Emergency coalescing if queue grows too large
|
|
549
521
|
const shouldAggressiveCoalesce = streamQ.length >= STREAM_EMERGENCY_COALESCE_LEN;
|
|
550
|
-
|
|
522
|
+
|
|
523
|
+
batching = true; // start partii
|
|
551
524
|
while (streamQ.length && processed < STREAM_MAX_PER_FRAME) {
|
|
552
525
|
let {name_header, content, chunk, replace, is_code_block} = streamQ.shift();
|
|
553
|
-
|
|
554
|
-
// Coalesce contiguous simple appends to reduce DOM churn
|
|
555
526
|
if (!replace && !content && (chunk && chunk.length > 0)) {
|
|
556
|
-
// Collect chunks into an array to avoid O(n^2) string concatenation
|
|
557
527
|
const chunks = [chunk];
|
|
558
528
|
while (streamQ.length) {
|
|
559
529
|
const next = streamQ[0];
|
|
560
530
|
if (!next.replace && !next.content && next.is_code_block === is_code_block && next.name_header === name_header) {
|
|
561
531
|
chunks.push(next.chunk);
|
|
562
532
|
streamQ.shift();
|
|
563
|
-
if (!shouldAggressiveCoalesce)
|
|
564
|
-
|
|
565
|
-
break;
|
|
566
|
-
}
|
|
567
|
-
} else {
|
|
568
|
-
break;
|
|
569
|
-
}
|
|
533
|
+
if (!shouldAggressiveCoalesce) break;
|
|
534
|
+
} else break;
|
|
570
535
|
}
|
|
571
536
|
chunk = chunks.join('');
|
|
572
537
|
}
|
|
573
|
-
|
|
574
538
|
applyStream(name_header, content, chunk, replace, is_code_block);
|
|
575
539
|
processed++;
|
|
576
540
|
}
|
|
577
|
-
|
|
578
|
-
|
|
541
|
+
batching = false; // koniec partii
|
|
542
|
+
|
|
543
|
+
// jedno przewinięcie na partię
|
|
544
|
+
if (needScroll) {
|
|
545
|
+
if (userInteracted === false) forceScrollToBottomImmediate();
|
|
546
|
+
else scheduleScroll(true);
|
|
547
|
+
needScroll = false;
|
|
548
|
+
}
|
|
549
|
+
|
|
579
550
|
if (streamQ.length) {
|
|
580
551
|
streamRAF = requestAnimationFrame(drainStream);
|
|
581
552
|
}
|
|
582
553
|
}
|
|
583
|
-
// Public API: enqueue and process in the next animation frame
|
|
584
554
|
function appendStream(name_header, content, chunk, replace = false, is_code_block = false) {
|
|
585
555
|
enqueueStream(name_header, content, chunk, replace, is_code_block);
|
|
586
556
|
}
|
|
587
|
-
// Internal: performs actual DOM updates for a single merged chunk
|
|
588
557
|
function applyStream(name_header, content, chunk, replace = false, is_code_block = false) {
|
|
589
|
-
dropIfDetached();
|
|
558
|
+
dropIfDetached();
|
|
590
559
|
hideTips();
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const name = document.createElement('div');
|
|
604
|
-
name.classList.add('name-header');
|
|
605
|
-
name.classList.add('name-bot');
|
|
606
|
-
name.innerHTML = name_header;
|
|
607
|
-
box.appendChild(name);
|
|
560
|
+
const msg = getStreamMsg(true, name_header);
|
|
561
|
+
if (msg) {
|
|
562
|
+
if (replace) {
|
|
563
|
+
domLastCodeBlock = null;
|
|
564
|
+
domLastParagraphBlock = null;
|
|
565
|
+
msg.replaceChildren();
|
|
566
|
+
if (content) {
|
|
567
|
+
msg.insertAdjacentHTML('afterbegin', content);
|
|
568
|
+
}
|
|
569
|
+
let doMath = true;
|
|
570
|
+
if (is_code_block) {
|
|
571
|
+
doMath = false;
|
|
608
572
|
}
|
|
609
|
-
msg
|
|
610
|
-
msg.classList.add('msg');
|
|
611
|
-
box.appendChild(msg);
|
|
612
|
-
element.appendChild(box);
|
|
573
|
+
highlightCode(doMath, msg);
|
|
613
574
|
} else {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
msg.insertAdjacentHTML('afterbegin', content);
|
|
623
|
-
}
|
|
624
|
-
let doMath = true;
|
|
625
|
-
if (is_code_block) {
|
|
626
|
-
doMath = false;
|
|
627
|
-
}
|
|
628
|
-
highlightCode(doMath, msg);
|
|
629
|
-
} else {
|
|
630
|
-
if (is_code_block) {
|
|
631
|
-
// Try to reuse cached last code block; fallback to cheap lastElementChild check
|
|
632
|
-
let lastCodeBlock = domLastCodeBlock;
|
|
633
|
-
if (!lastCodeBlock || !msg.contains(lastCodeBlock)) {
|
|
634
|
-
const last = msg.lastElementChild;
|
|
635
|
-
if (last && last.tagName === 'PRE') {
|
|
636
|
-
const codeEl = last.querySelector('code');
|
|
637
|
-
if (codeEl) {
|
|
638
|
-
lastCodeBlock = codeEl;
|
|
639
|
-
}
|
|
640
|
-
} else {
|
|
641
|
-
// Fallback scan only when necessary
|
|
642
|
-
const codes = msg.querySelectorAll('pre code');
|
|
643
|
-
if (codes.length > 0) {
|
|
644
|
-
lastCodeBlock = codes[codes.length - 1];
|
|
645
|
-
}
|
|
575
|
+
if (is_code_block) {
|
|
576
|
+
let lastCodeBlock = domLastCodeBlock;
|
|
577
|
+
if (!lastCodeBlock || !msg.contains(lastCodeBlock)) {
|
|
578
|
+
const last = msg.lastElementChild;
|
|
579
|
+
if (last && last.tagName === 'PRE') {
|
|
580
|
+
const codeEl = last.querySelector('code');
|
|
581
|
+
if (codeEl) {
|
|
582
|
+
lastCodeBlock = codeEl;
|
|
646
583
|
}
|
|
647
|
-
}
|
|
648
|
-
if (lastCodeBlock) {
|
|
649
|
-
lastCodeBlock.insertAdjacentHTML('beforeend', chunk);
|
|
650
|
-
domLastCodeBlock = lastCodeBlock;
|
|
651
584
|
} else {
|
|
652
|
-
msg.
|
|
653
|
-
|
|
585
|
+
const codes = msg.querySelectorAll('pre code');
|
|
586
|
+
if (codes.length > 0) {
|
|
587
|
+
lastCodeBlock = codes[codes.length - 1];
|
|
588
|
+
}
|
|
654
589
|
}
|
|
590
|
+
}
|
|
591
|
+
if (lastCodeBlock) {
|
|
592
|
+
lastCodeBlock.insertAdjacentHTML('beforeend', chunk);
|
|
593
|
+
domLastCodeBlock = lastCodeBlock;
|
|
655
594
|
} else {
|
|
595
|
+
msg.insertAdjacentHTML('beforeend', chunk);
|
|
656
596
|
domLastCodeBlock = null;
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
domLastCodeBlock = null;
|
|
600
|
+
let p = (domLastParagraphBlock && msg.contains(domLastParagraphBlock))
|
|
601
|
+
? domLastParagraphBlock
|
|
602
|
+
: (msg.lastElementChild && msg.lastElementChild.tagName === 'P'
|
|
603
|
+
? msg.lastElementChild
|
|
604
|
+
: null);
|
|
605
|
+
if (p) {
|
|
606
|
+
p.insertAdjacentHTML('beforeend', chunk);
|
|
607
|
+
domLastParagraphBlock = p;
|
|
608
|
+
} else {
|
|
609
|
+
msg.insertAdjacentHTML('beforeend', chunk);
|
|
610
|
+
const last = msg.lastElementChild;
|
|
611
|
+
domLastParagraphBlock = (last && last.tagName === 'P') ? last : null;
|
|
670
612
|
}
|
|
671
613
|
}
|
|
672
614
|
}
|
|
673
615
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
616
|
+
if (batching) {
|
|
617
|
+
needScroll = true;
|
|
618
|
+
} else {
|
|
619
|
+
if (userInteracted === false) {
|
|
620
|
+
forceScrollToBottomImmediate();
|
|
621
|
+
} else {
|
|
622
|
+
scheduleScroll(true);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
fabFreezeUntil = performance.now() + FAB_TOGGLE_DEBOUNCE_MS;
|
|
626
|
+
scheduleScrollFabUpdate();
|
|
680
627
|
}
|
|
681
628
|
function nextStream() {
|
|
682
629
|
hideTips();
|
|
@@ -690,6 +637,7 @@ class Body:
|
|
|
690
637
|
elementBefore.appendChild(frag);
|
|
691
638
|
domLastCodeBlock = null;
|
|
692
639
|
domLastParagraphBlock = null;
|
|
640
|
+
domStreamMsg = null;
|
|
693
641
|
scheduleScroll();
|
|
694
642
|
}
|
|
695
643
|
}
|
|
@@ -798,7 +746,7 @@ class Body:
|
|
|
798
746
|
element.classList.add('visible');
|
|
799
747
|
}
|
|
800
748
|
element.innerHTML = content;
|
|
801
|
-
|
|
749
|
+
scheduleHighlight(element, true);
|
|
802
750
|
scheduleScroll();
|
|
803
751
|
}
|
|
804
752
|
}
|
|
@@ -828,7 +776,8 @@ class Body:
|
|
|
828
776
|
clearStreamBefore();
|
|
829
777
|
domLastCodeBlock = null;
|
|
830
778
|
domLastParagraphBlock = null;
|
|
831
|
-
|
|
779
|
+
domStreamMsg = null;
|
|
780
|
+
domOutputStream = null;
|
|
832
781
|
const element = els.appendOutput || document.getElementById('_append_output_');
|
|
833
782
|
if (element) {
|
|
834
783
|
element.replaceChildren();
|
|
@@ -906,7 +855,7 @@ class Body:
|
|
|
906
855
|
const source = wrapper.querySelector('code');
|
|
907
856
|
if (source && collapsed_idx.includes(index)) {
|
|
908
857
|
source.style.display = 'none';
|
|
909
|
-
const collapseBtn = wrapper.querySelector('code-header-collapse');
|
|
858
|
+
const collapseBtn = wrapper.querySelector('.code-header-collapse');
|
|
910
859
|
if (collapseBtn) {
|
|
911
860
|
const collapseSpan = collapseBtn.querySelector('span');
|
|
912
861
|
if (collapseSpan) {
|
|
@@ -974,112 +923,112 @@ class Body:
|
|
|
974
923
|
}
|
|
975
924
|
}
|
|
976
925
|
|
|
977
|
-
// ---------- scroll bottom ----------
|
|
978
926
|
function hasVerticalScroll() {
|
|
979
|
-
const el =
|
|
927
|
+
const el = SE;
|
|
980
928
|
return (el.scrollHeight - el.clientHeight) > 1;
|
|
981
929
|
}
|
|
982
930
|
function distanceToBottomPx() {
|
|
983
|
-
const el =
|
|
931
|
+
const el = SE;
|
|
984
932
|
return el.scrollHeight - el.clientHeight - el.scrollTop;
|
|
985
933
|
}
|
|
986
934
|
function isAtBottom(thresholdPx = 2) {
|
|
987
935
|
return distanceToBottomPx() <= thresholdPx;
|
|
988
936
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
937
|
+
|
|
938
|
+
function computeFabAction() {
|
|
939
|
+
const el = SE;
|
|
940
|
+
const hasScroll = (el.scrollHeight - el.clientHeight) > 1;
|
|
941
|
+
if (!hasScroll) return 'none';
|
|
942
|
+
const dist = el.scrollHeight - el.clientHeight - el.scrollTop;
|
|
943
|
+
if (dist <= 2) return 'up';
|
|
944
|
+
if (dist >= SHOW_DOWN_THRESHOLD_PX) return 'down';
|
|
945
|
+
return 'none';
|
|
993
946
|
}
|
|
994
|
-
function updateScrollFab() {
|
|
947
|
+
function updateScrollFab(force = false, actionOverride = null, bypassFreeze = false) {
|
|
995
948
|
const btn = els.scrollFab || document.getElementById('scrollFab');
|
|
996
949
|
const icon = els.scrollFabIcon || document.getElementById('scrollFabIcon');
|
|
997
950
|
if (!btn || !icon) return;
|
|
998
951
|
|
|
999
|
-
const
|
|
1000
|
-
if (!
|
|
1001
|
-
btn.classList.remove('visible');
|
|
1002
|
-
currentFabAction = 'none';
|
|
952
|
+
const action = actionOverride || computeFabAction();
|
|
953
|
+
if (!force && !bypassFreeze && performance.now() < fabFreezeUntil && action !== currentFabAction) {
|
|
1003
954
|
return;
|
|
1004
955
|
}
|
|
1005
|
-
|
|
1006
|
-
const atBottom = isAtBottom();
|
|
1007
|
-
const dist = distanceToBottomPx();
|
|
1008
|
-
const loaderHidden = isLoaderHidden();
|
|
1009
|
-
|
|
1010
|
-
// Determine desired action and visibility based on requirements:
|
|
1011
|
-
// - Show "down" only when at least SHOW_DOWN_THRESHOLD_PX away from bottom.
|
|
1012
|
-
// - Show "up" only when loader-global has the 'hidden' class.
|
|
1013
|
-
// - Otherwise hide the FAB to prevent overlap and noise.
|
|
1014
|
-
let action = 'none'; // 'up' | 'down' | 'none'
|
|
1015
|
-
if (atBottom) {
|
|
1016
|
-
action = 'up';
|
|
1017
|
-
} else {
|
|
1018
|
-
if (dist >= SHOW_DOWN_THRESHOLD_PX) {
|
|
1019
|
-
action = 'down';
|
|
1020
|
-
} else {
|
|
1021
|
-
action = 'none';
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
956
|
if (action === 'none') {
|
|
1026
|
-
|
|
1027
|
-
|
|
957
|
+
if (currentFabAction !== 'none' || force) {
|
|
958
|
+
btn.classList.remove('visible');
|
|
959
|
+
currentFabAction = 'none';
|
|
960
|
+
}
|
|
1028
961
|
return;
|
|
1029
962
|
}
|
|
1030
|
-
|
|
1031
|
-
// Update icon and semantics only if changed to avoid redundant 'load' events
|
|
1032
|
-
if (action !== currentFabAction) {
|
|
963
|
+
if (action !== currentFabAction || force) {
|
|
1033
964
|
if (action === 'up') {
|
|
1034
|
-
if (icon.
|
|
965
|
+
if (icon.dataset.dir !== 'up') {
|
|
966
|
+
icon.src = ICON_COLLAPSE;
|
|
967
|
+
icon.dataset.dir = 'up';
|
|
968
|
+
}
|
|
1035
969
|
btn.title = "Go to top";
|
|
1036
970
|
} else {
|
|
1037
|
-
if (icon.
|
|
971
|
+
if (icon.dataset.dir !== 'down') {
|
|
972
|
+
icon.src = ICON_EXPAND;
|
|
973
|
+
icon.dataset.dir = 'down';
|
|
974
|
+
}
|
|
1038
975
|
btn.title = "Go to bottom";
|
|
1039
976
|
}
|
|
1040
977
|
btn.setAttribute('aria-label', btn.title);
|
|
1041
978
|
currentFabAction = action;
|
|
979
|
+
btn.classList.add('visible');
|
|
980
|
+
} else if (!btn.classList.contains('visible')) {
|
|
981
|
+
btn.classList.add('visible');
|
|
1042
982
|
}
|
|
1043
|
-
|
|
1044
|
-
// Finally show
|
|
1045
|
-
btn.classList.add('visible');
|
|
1046
983
|
}
|
|
1047
984
|
function scheduleScrollFabUpdate() {
|
|
1048
985
|
if (scrollFabUpdateScheduled) return;
|
|
1049
986
|
scrollFabUpdateScheduled = true;
|
|
1050
987
|
requestAnimationFrame(function() {
|
|
1051
988
|
scrollFabUpdateScheduled = false;
|
|
1052
|
-
|
|
989
|
+
const action = computeFabAction();
|
|
990
|
+
if (action !== currentFabAction) {
|
|
991
|
+
updateScrollFab(false, action);
|
|
992
|
+
}
|
|
1053
993
|
});
|
|
1054
994
|
}
|
|
995
|
+
|
|
996
|
+
function maybeEnableAutoFollowByProximity() {
|
|
997
|
+
const el = SE;
|
|
998
|
+
if (!autoFollow) {
|
|
999
|
+
const distanceToBottom = el.scrollHeight - el.clientHeight - el.scrollTop;
|
|
1000
|
+
if (distanceToBottom <= AUTO_FOLLOW_REENABLE_PX) {
|
|
1001
|
+
autoFollow = true;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1055
1005
|
function scrollToTopUser() {
|
|
1056
|
-
// Explicit user-driven scroll disables auto-follow
|
|
1057
1006
|
userInteracted = true;
|
|
1058
1007
|
autoFollow = false;
|
|
1059
1008
|
try {
|
|
1060
|
-
const el =
|
|
1009
|
+
const el = SE;
|
|
1061
1010
|
el.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1011
|
+
lastScrollTop = el.scrollTop;
|
|
1062
1012
|
} catch (e) {
|
|
1063
|
-
|
|
1064
|
-
const el = document.scrollingElement || document.documentElement;
|
|
1013
|
+
const el = SE;
|
|
1065
1014
|
el.scrollTop = 0;
|
|
1015
|
+
lastScrollTop = 0;
|
|
1066
1016
|
}
|
|
1067
|
-
scheduleScrollFabUpdate();
|
|
1068
1017
|
}
|
|
1069
1018
|
function scrollToBottomUser() {
|
|
1070
|
-
// User action to go to bottom re-enables auto-follow
|
|
1071
1019
|
userInteracted = true;
|
|
1072
|
-
autoFollow =
|
|
1020
|
+
autoFollow = false;
|
|
1073
1021
|
try {
|
|
1074
|
-
const el =
|
|
1022
|
+
const el = SE;
|
|
1075
1023
|
el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' });
|
|
1024
|
+
lastScrollTop = el.scrollTop;
|
|
1076
1025
|
} catch (e) {
|
|
1077
|
-
const el =
|
|
1026
|
+
const el = SE;
|
|
1078
1027
|
el.scrollTop = el.scrollHeight;
|
|
1028
|
+
lastScrollTop = el.scrollTop;
|
|
1079
1029
|
}
|
|
1080
|
-
|
|
1030
|
+
maybeEnableAutoFollowByProximity();
|
|
1081
1031
|
}
|
|
1082
|
-
// ---------- end of scroll bottom ----------
|
|
1083
1032
|
|
|
1084
1033
|
document.addEventListener('DOMContentLoaded', function() {
|
|
1085
1034
|
new QWebChannel(qt.webChannelTransport, function (channel) {
|
|
@@ -1089,6 +1038,7 @@ class Body:
|
|
|
1089
1038
|
});
|
|
1090
1039
|
initDomRefs();
|
|
1091
1040
|
const container = els.container;
|
|
1041
|
+
|
|
1092
1042
|
function addClassToMsg(id, className) {
|
|
1093
1043
|
const msgElement = document.getElementById('msg-bot-' + id);
|
|
1094
1044
|
if (msgElement) {
|
|
@@ -1101,61 +1051,71 @@ class Body:
|
|
|
1101
1051
|
msgElement.classList.remove(className);
|
|
1102
1052
|
}
|
|
1103
1053
|
}
|
|
1104
|
-
|
|
1054
|
+
containerMouseOverHandler = function(event) {
|
|
1105
1055
|
if (event.target.classList.contains('action-img')) {
|
|
1106
1056
|
const id = event.target.getAttribute('data-id');
|
|
1107
1057
|
addClassToMsg(id, 'msg-highlight');
|
|
1108
1058
|
}
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1059
|
+
};
|
|
1060
|
+
containerMouseOutHandler = function(event) {
|
|
1111
1061
|
if (event.target.classList.contains('action-img')) {
|
|
1112
1062
|
const id = event.target.getAttribute('data-id');
|
|
1113
1063
|
removeClassFromMsg(id, 'msg-highlight');
|
|
1114
1064
|
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1065
|
+
};
|
|
1066
|
+
container.addEventListener('mouseover', containerMouseOverHandler, { passive: true });
|
|
1067
|
+
container.addEventListener('mouseout', containerMouseOutHandler, { passive: true });
|
|
1068
|
+
|
|
1069
|
+
wheelHandler = function(ev) {
|
|
1118
1070
|
userInteracted = true;
|
|
1119
1071
|
if (ev.deltaY < 0) {
|
|
1120
1072
|
autoFollow = false;
|
|
1073
|
+
} else {
|
|
1074
|
+
maybeEnableAutoFollowByProximity();
|
|
1121
1075
|
}
|
|
1122
|
-
}
|
|
1076
|
+
};
|
|
1077
|
+
document.addEventListener('wheel', wheelHandler, { passive: true });
|
|
1123
1078
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
const el = document.scrollingElement || document.documentElement;
|
|
1079
|
+
scrollHandler = function() {
|
|
1080
|
+
const el = SE;
|
|
1127
1081
|
const top = el.scrollTop;
|
|
1128
|
-
|
|
1129
|
-
// User scrolled up (ignore tiny jitter)
|
|
1130
1082
|
if (top + 1 < lastScrollTop) {
|
|
1131
1083
|
autoFollow = false;
|
|
1132
|
-
} else if (!autoFollow) {
|
|
1133
|
-
const distanceToBottom = el.scrollHeight - el.clientHeight - top;
|
|
1134
|
-
if (distanceToBottom <= AUTO_FOLLOW_REENABLE_PX) {
|
|
1135
|
-
autoFollow = true;
|
|
1136
|
-
}
|
|
1137
1084
|
}
|
|
1085
|
+
maybeEnableAutoFollowByProximity();
|
|
1138
1086
|
lastScrollTop = top;
|
|
1139
|
-
|
|
1087
|
+
const action = computeFabAction();
|
|
1088
|
+
if (action !== currentFabAction) {
|
|
1089
|
+
updateScrollFab(false, action, true);
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
window.addEventListener('scroll', scrollHandler, { passive: true });
|
|
1140
1093
|
|
|
1141
|
-
// Scroll-to-top/bottom FAB wiring
|
|
1142
1094
|
if (els.scrollFab) {
|
|
1143
|
-
|
|
1095
|
+
fabClickHandler = function(ev) {
|
|
1144
1096
|
ev.preventDefault();
|
|
1145
|
-
|
|
1097
|
+
ev.stopPropagation();
|
|
1098
|
+
const action = computeFabAction();
|
|
1099
|
+
if (action === 'up') {
|
|
1146
1100
|
scrollToTopUser();
|
|
1147
|
-
} else {
|
|
1101
|
+
} else if (action === 'down') {
|
|
1148
1102
|
scrollToBottomUser();
|
|
1149
1103
|
}
|
|
1150
|
-
|
|
1104
|
+
fabFreezeUntil = performance.now() + FAB_TOGGLE_DEBOUNCE_MS;
|
|
1105
|
+
updateScrollFab(true);
|
|
1106
|
+
};
|
|
1107
|
+
els.scrollFab.addEventListener('click', fabClickHandler, { passive: false });
|
|
1151
1108
|
}
|
|
1152
|
-
window.addEventListener('scroll', scheduleScrollFabUpdate, { passive: true });
|
|
1153
|
-
window.addEventListener('resize', scheduleScrollFabUpdate, { passive: true });
|
|
1154
1109
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1110
|
+
resizeHandler = function() {
|
|
1111
|
+
maybeEnableAutoFollowByProximity();
|
|
1112
|
+
scheduleScrollFabUpdate();
|
|
1113
|
+
};
|
|
1114
|
+
window.addEventListener('resize', resizeHandler, { passive: true });
|
|
1115
|
+
|
|
1116
|
+
updateScrollFab(true);
|
|
1157
1117
|
|
|
1158
|
-
|
|
1118
|
+
containerClickHandler = function(event) {
|
|
1159
1119
|
const copyButton = event.target.closest('.code-header-copy');
|
|
1160
1120
|
if (copyButton) {
|
|
1161
1121
|
event.preventDefault();
|
|
@@ -1228,13 +1188,13 @@ class Body:
|
|
|
1228
1188
|
}
|
|
1229
1189
|
}
|
|
1230
1190
|
}
|
|
1231
|
-
}
|
|
1191
|
+
};
|
|
1192
|
+
container.addEventListener('click', containerClickHandler, { passive: false });
|
|
1232
1193
|
|
|
1233
|
-
//
|
|
1234
|
-
window.addEventListener('
|
|
1235
|
-
window.addEventListener('beforeunload', teardown, { passive: true });
|
|
1194
|
+
// window.addEventListener('pagehide', cleanup, { once: true });
|
|
1195
|
+
// window.addEventListener('beforeunload', cleanup, { once: true });
|
|
1236
1196
|
});
|
|
1237
|
-
setTimeout(cycleTips, 10000);
|
|
1197
|
+
setTimeout(cycleTips, 10000);
|
|
1238
1198
|
</script>
|
|
1239
1199
|
</head>
|
|
1240
1200
|
<body """
|
|
@@ -1304,7 +1264,6 @@ class Body:
|
|
|
1304
1264
|
}
|
|
1305
1265
|
"""
|
|
1306
1266
|
|
|
1307
|
-
# CSS for the scroll-to-top/bottom
|
|
1308
1267
|
_SCROLL_FAB_CSS = """
|
|
1309
1268
|
#scrollFab.scroll-fab {
|
|
1310
1269
|
position: fixed;
|
|
@@ -1371,11 +1330,6 @@ class Body:
|
|
|
1371
1330
|
"""
|
|
1372
1331
|
|
|
1373
1332
|
def __init__(self, window=None):
|
|
1374
|
-
"""
|
|
1375
|
-
HTML Body
|
|
1376
|
-
|
|
1377
|
-
:param window: Window instance
|
|
1378
|
-
"""
|
|
1379
1333
|
self.window = window
|
|
1380
1334
|
self.highlight = SyntaxHighlight(window)
|
|
1381
1335
|
self._tip_keys = tuple(f"output.tips.{i}" for i in range(1, self.NUM_TIPS + 1))
|
|
@@ -1399,21 +1353,10 @@ class Body:
|
|
|
1399
1353
|
"zenburn",
|
|
1400
1354
|
)
|
|
1401
1355
|
|
|
1402
|
-
|
|
1403
1356
|
def is_timestamp_enabled(self) -> bool:
|
|
1404
|
-
"""
|
|
1405
|
-
Check if timestamp is enabled
|
|
1406
|
-
|
|
1407
|
-
:return: True if timestamp is enabled
|
|
1408
|
-
"""
|
|
1409
1357
|
return self.window.core.config.get('output_timestamp')
|
|
1410
1358
|
|
|
1411
1359
|
def prepare_styles(self) -> str:
|
|
1412
|
-
"""
|
|
1413
|
-
Prepare CSS styles
|
|
1414
|
-
|
|
1415
|
-
:return: CSS styles
|
|
1416
|
-
"""
|
|
1417
1360
|
cfg = self.window.core.config
|
|
1418
1361
|
fonts_path = os.path.join(cfg.get_app_path(), "data", "fonts").replace("\\", "/")
|
|
1419
1362
|
syntax_style = self.window.core.config.get("render.code_syntax") or "default"
|
|
@@ -1424,30 +1367,17 @@ class Body:
|
|
|
1424
1367
|
theme_css,
|
|
1425
1368
|
"pre { color: #fff; }" if syntax_style in self._syntax_dark else "pre { color: #000; }",
|
|
1426
1369
|
self.highlight.get_style_defs(),
|
|
1427
|
-
self._PERFORMANCE_CSS
|
|
1370
|
+
self._PERFORMANCE_CSS
|
|
1428
1371
|
]
|
|
1429
1372
|
return "\n".join(parts)
|
|
1430
1373
|
|
|
1431
1374
|
def prepare_action_icons(self, ctx: CtxItem) -> str:
|
|
1432
|
-
"""
|
|
1433
|
-
Append action icons
|
|
1434
|
-
|
|
1435
|
-
:param ctx: context item
|
|
1436
|
-
:return: HTML code
|
|
1437
|
-
"""
|
|
1438
1375
|
icons_html = "".join(self.get_action_icons(ctx, all=True))
|
|
1439
1376
|
if icons_html:
|
|
1440
1377
|
return f'<div class="action-icons" data-id="{ctx.id}">{icons_html}</div>'
|
|
1441
1378
|
return ""
|
|
1442
1379
|
|
|
1443
1380
|
def get_action_icons(self, ctx: CtxItem, all: bool = False) -> List[str]:
|
|
1444
|
-
"""
|
|
1445
|
-
Get action icons for context item
|
|
1446
|
-
|
|
1447
|
-
:param ctx: context item
|
|
1448
|
-
:param all: True to show all icons
|
|
1449
|
-
:return: list of icons
|
|
1450
|
-
"""
|
|
1451
1381
|
icons: List[str] = []
|
|
1452
1382
|
if ctx.output:
|
|
1453
1383
|
cid = ctx.id
|
|
@@ -1467,46 +1397,17 @@ class Body:
|
|
|
1467
1397
|
f'<a href="extra-join:{cid}" class="action-icon edit-icon" data-id="{cid}" role="button"><span class="cmd">{self.get_icon("playlist_add", t("ctx.extra.join"), ctx)}</span></a>')
|
|
1468
1398
|
return icons
|
|
1469
1399
|
|
|
1470
|
-
def get_icon(
|
|
1471
|
-
self,
|
|
1472
|
-
icon: str,
|
|
1473
|
-
title: Optional[str] = None,
|
|
1474
|
-
item: Optional[CtxItem] = None
|
|
1475
|
-
) -> str:
|
|
1476
|
-
"""
|
|
1477
|
-
Get icon
|
|
1478
|
-
|
|
1479
|
-
:param icon: icon name
|
|
1480
|
-
:param title: icon title
|
|
1481
|
-
:param item: context item
|
|
1482
|
-
:return: icon HTML
|
|
1483
|
-
"""
|
|
1400
|
+
def get_icon(self, icon: str, title: Optional[str] = None, item: Optional[CtxItem] = None) -> str:
|
|
1484
1401
|
app_path = self.window.core.config.get_app_path()
|
|
1485
1402
|
icon_path = os.path.join(app_path, "data", "icons", f"{icon}.svg")
|
|
1486
1403
|
return f'<img src="file://{icon_path}" class="action-img" title="{title}" alt="{title}" data-id="{item.id}">'
|
|
1487
1404
|
|
|
1488
|
-
def get_image_html(
|
|
1489
|
-
self,
|
|
1490
|
-
url: str,
|
|
1491
|
-
num: Optional[int] = None,
|
|
1492
|
-
num_all: Optional[int] = None
|
|
1493
|
-
) -> str:
|
|
1494
|
-
"""
|
|
1495
|
-
Get media image/video/audio HTML
|
|
1496
|
-
|
|
1497
|
-
:param url: URL to image
|
|
1498
|
-
:param num: number of image
|
|
1499
|
-
:param num_all: number of all images
|
|
1500
|
-
:return: HTML code
|
|
1501
|
-
"""
|
|
1405
|
+
def get_image_html(self, url: str, num: Optional[int] = None, num_all: Optional[int] = None) -> str:
|
|
1502
1406
|
url, path = self.window.core.filesystem.extract_local_url(url)
|
|
1503
1407
|
basename = os.path.basename(path)
|
|
1504
|
-
|
|
1505
|
-
# if video file then embed video player
|
|
1506
1408
|
ext = os.path.splitext(basename)[1].lower()
|
|
1507
1409
|
video_exts = (".mp4", ".webm", ".ogg", ".mov", ".avi", ".mkv")
|
|
1508
1410
|
if ext in video_exts:
|
|
1509
|
-
# check if .webm file exists for better compatibility
|
|
1510
1411
|
if ext != ".webm":
|
|
1511
1412
|
webm_path = os.path.splitext(path)[0] + ".webm"
|
|
1512
1413
|
if os.path.exists(webm_path):
|
|
@@ -1522,20 +1423,7 @@ class Body:
|
|
|
1522
1423
|
'''
|
|
1523
1424
|
return f'<div class="extra-src-img-box" title="{url}"><div class="img-outer"><div class="img-wrapper"><a href="{url}"><img src="{path}" class="image"></a></div><a href="{url}" class="title">{elide_filename(basename)}</a></div></div><br/>'
|
|
1524
1425
|
|
|
1525
|
-
def get_url_html(
|
|
1526
|
-
self,
|
|
1527
|
-
url: str,
|
|
1528
|
-
num: Optional[int] = None,
|
|
1529
|
-
num_all: Optional[int] = None
|
|
1530
|
-
) -> str:
|
|
1531
|
-
"""
|
|
1532
|
-
Get URL HTML
|
|
1533
|
-
|
|
1534
|
-
:param url: external URL
|
|
1535
|
-
:param num: number of URL
|
|
1536
|
-
:param num_all: number of all URLs
|
|
1537
|
-
:return: HTML code
|
|
1538
|
-
"""
|
|
1426
|
+
def get_url_html(self, url: str, num: Optional[int] = None, num_all: Optional[int] = None) -> str:
|
|
1539
1427
|
app_path = self.window.core.config.get_app_path()
|
|
1540
1428
|
icon_path = os.path.join(app_path, "data", "icons", "language.svg").replace("\\", "/")
|
|
1541
1429
|
icon = f'<img src="file://{icon_path}" class="extra-src-icon">'
|
|
@@ -1543,12 +1431,6 @@ class Body:
|
|
|
1543
1431
|
return f'{icon}<a href="{url}" title="{url}">{url}</a> <small>{num_str}</small>'
|
|
1544
1432
|
|
|
1545
1433
|
def get_docs_html(self, docs: List[Dict]) -> str:
|
|
1546
|
-
"""
|
|
1547
|
-
Get Llama-index doc metadata HTML
|
|
1548
|
-
|
|
1549
|
-
:param docs: list of document metadata
|
|
1550
|
-
:return: HTML code
|
|
1551
|
-
"""
|
|
1552
1434
|
html_parts: List[str] = []
|
|
1553
1435
|
src_parts: List[str] = []
|
|
1554
1436
|
num = 1
|
|
@@ -1578,20 +1460,7 @@ class Body:
|
|
|
1578
1460
|
|
|
1579
1461
|
return "".join(html_parts)
|
|
1580
1462
|
|
|
1581
|
-
def get_file_html(
|
|
1582
|
-
self,
|
|
1583
|
-
url: str,
|
|
1584
|
-
num: Optional[int] = None,
|
|
1585
|
-
num_all: Optional[int] = None
|
|
1586
|
-
) -> str:
|
|
1587
|
-
"""
|
|
1588
|
-
Get file HTML
|
|
1589
|
-
|
|
1590
|
-
:param url: URL to file
|
|
1591
|
-
:param num: number of file
|
|
1592
|
-
:param num_all: number of all files
|
|
1593
|
-
:return: HTML code
|
|
1594
|
-
"""
|
|
1463
|
+
def get_file_html(self, url: str, num: Optional[int] = None, num_all: Optional[int] = None) -> str:
|
|
1595
1464
|
app_path = self.window.core.config.get_app_path()
|
|
1596
1465
|
icon_path = os.path.join(app_path, "data", "icons", "attachments.svg").replace("\\", "/")
|
|
1597
1466
|
icon = f'<img src="file://{icon_path}" class="extra-src-icon">'
|
|
@@ -1600,12 +1469,6 @@ class Body:
|
|
|
1600
1469
|
return f'{icon} <b>{num_str}</b> <a href="{url}">{path}</a>'
|
|
1601
1470
|
|
|
1602
1471
|
def prepare_tool_extra(self, ctx: CtxItem) -> str:
|
|
1603
|
-
"""
|
|
1604
|
-
Prepare footer extra
|
|
1605
|
-
|
|
1606
|
-
:param ctx: context item
|
|
1607
|
-
:return: HTML code
|
|
1608
|
-
"""
|
|
1609
1472
|
extra = ctx.extra
|
|
1610
1473
|
if not extra:
|
|
1611
1474
|
return ""
|
|
@@ -1643,11 +1506,6 @@ class Body:
|
|
|
1643
1506
|
return "".join(parts)
|
|
1644
1507
|
|
|
1645
1508
|
def get_all_tips(self) -> str:
|
|
1646
|
-
"""
|
|
1647
|
-
Get all tips for the output view
|
|
1648
|
-
|
|
1649
|
-
:return: JSON string of tips
|
|
1650
|
-
"""
|
|
1651
1509
|
if not self.window.core.config.get("layout.tooltips", False):
|
|
1652
1510
|
return "[]"
|
|
1653
1511
|
|
|
@@ -1661,12 +1519,6 @@ class Body:
|
|
|
1661
1519
|
return _json_dumps(tips)
|
|
1662
1520
|
|
|
1663
1521
|
def get_html(self, pid: int) -> str:
|
|
1664
|
-
"""
|
|
1665
|
-
Build webview HTML code (fast path, minimal allocations)
|
|
1666
|
-
|
|
1667
|
-
:param pid: process ID
|
|
1668
|
-
:return: HTML code
|
|
1669
|
-
"""
|
|
1670
1522
|
cfg_get = self.window.core.config.get
|
|
1671
1523
|
style = cfg_get("theme.style", "blocks")
|
|
1672
1524
|
classes = ["theme-" + style]
|
|
@@ -1680,7 +1532,6 @@ class Body:
|
|
|
1680
1532
|
styles_css = self.prepare_styles()
|
|
1681
1533
|
tips_json = self.get_all_tips()
|
|
1682
1534
|
|
|
1683
|
-
# Build file:// paths for FAB icons
|
|
1684
1535
|
app_path = self.window.core.config.get_app_path().replace("\\", "/")
|
|
1685
1536
|
expand_path = os.path.join(app_path, "data", "icons", "expand.svg").replace("\\", "/")
|
|
1686
1537
|
collapse_path = os.path.join(app_path, "data", "icons", "collapse.svg").replace("\\", "/")
|