pygpt-net 2.6.37__py3-none-any.whl → 2.6.39__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 +12 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/chat/handler/anthropic_stream.py +0 -2
- pygpt_net/controller/chat/handler/worker.py +6 -2
- pygpt_net/controller/debug/debug.py +6 -6
- pygpt_net/controller/model/editor.py +20 -42
- pygpt_net/controller/model/importer.py +9 -2
- pygpt_net/controller/painter/common.py +0 -8
- pygpt_net/controller/plugins/plugins.py +11 -3
- pygpt_net/controller/presets/presets.py +2 -2
- pygpt_net/core/ctx/bag.py +7 -2
- pygpt_net/core/ctx/reply.py +17 -2
- pygpt_net/core/db/viewer.py +19 -34
- pygpt_net/core/render/plain/pid.py +12 -1
- pygpt_net/core/render/web/body.py +292 -445
- pygpt_net/core/tabs/tab.py +24 -1
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/item/assistant.py +51 -2
- pygpt_net/item/attachment.py +21 -20
- pygpt_net/item/calendar_note.py +19 -2
- pygpt_net/item/ctx.py +115 -2
- pygpt_net/item/index.py +9 -2
- pygpt_net/item/mode.py +9 -6
- pygpt_net/item/model.py +20 -3
- pygpt_net/item/notepad.py +14 -2
- pygpt_net/item/preset.py +42 -2
- pygpt_net/item/prompt.py +8 -2
- pygpt_net/plugin/cmd_files/plugin.py +2 -2
- pygpt_net/provider/api/anthropic/tools.py +1 -1
- pygpt_net/provider/api/google/realtime/client.py +2 -2
- pygpt_net/provider/core/attachment/json_file.py +2 -2
- pygpt_net/tools/text_editor/tool.py +4 -1
- pygpt_net/tools/text_editor/ui/dialogs.py +1 -1
- pygpt_net/ui/dialog/db.py +177 -59
- pygpt_net/ui/dialog/dictionary.py +57 -59
- pygpt_net/ui/dialog/editor.py +3 -2
- pygpt_net/ui/dialog/image.py +1 -1
- pygpt_net/ui/dialog/logger.py +3 -2
- pygpt_net/ui/dialog/models.py +171 -21
- pygpt_net/ui/dialog/plugins.py +26 -20
- pygpt_net/ui/layout/ctx/ctx_list.py +3 -4
- pygpt_net/ui/layout/toolbox/__init__.py +2 -2
- pygpt_net/ui/layout/toolbox/assistants.py +8 -9
- pygpt_net/ui/layout/toolbox/presets.py +2 -2
- pygpt_net/ui/main.py +9 -4
- pygpt_net/ui/widget/element/labels.py +2 -2
- pygpt_net/ui/widget/textarea/editor.py +0 -4
- pygpt_net/utils.py +12 -13
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.dist-info}/METADATA +14 -2
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.dist-info}/RECORD +54 -54
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.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,116 +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
|
-
if (loaderHidden) {
|
|
1017
|
-
action = 'up';
|
|
1018
|
-
} else {
|
|
1019
|
-
action = 'none';
|
|
1020
|
-
}
|
|
1021
|
-
} else {
|
|
1022
|
-
if (dist >= SHOW_DOWN_THRESHOLD_PX) {
|
|
1023
|
-
action = 'down';
|
|
1024
|
-
} else {
|
|
1025
|
-
action = 'none';
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
956
|
if (action === 'none') {
|
|
1030
|
-
|
|
1031
|
-
|
|
957
|
+
if (currentFabAction !== 'none' || force) {
|
|
958
|
+
btn.classList.remove('visible');
|
|
959
|
+
currentFabAction = 'none';
|
|
960
|
+
}
|
|
1032
961
|
return;
|
|
1033
962
|
}
|
|
1034
|
-
|
|
1035
|
-
// Update icon and semantics only if changed to avoid redundant 'load' events
|
|
1036
|
-
if (action !== currentFabAction) {
|
|
963
|
+
if (action !== currentFabAction || force) {
|
|
1037
964
|
if (action === 'up') {
|
|
1038
|
-
if (icon.
|
|
965
|
+
if (icon.dataset.dir !== 'up') {
|
|
966
|
+
icon.src = ICON_COLLAPSE;
|
|
967
|
+
icon.dataset.dir = 'up';
|
|
968
|
+
}
|
|
1039
969
|
btn.title = "Go to top";
|
|
1040
970
|
} else {
|
|
1041
|
-
if (icon.
|
|
971
|
+
if (icon.dataset.dir !== 'down') {
|
|
972
|
+
icon.src = ICON_EXPAND;
|
|
973
|
+
icon.dataset.dir = 'down';
|
|
974
|
+
}
|
|
1042
975
|
btn.title = "Go to bottom";
|
|
1043
976
|
}
|
|
1044
977
|
btn.setAttribute('aria-label', btn.title);
|
|
1045
978
|
currentFabAction = action;
|
|
979
|
+
btn.classList.add('visible');
|
|
980
|
+
} else if (!btn.classList.contains('visible')) {
|
|
981
|
+
btn.classList.add('visible');
|
|
1046
982
|
}
|
|
1047
|
-
|
|
1048
|
-
// Finally show
|
|
1049
|
-
btn.classList.add('visible');
|
|
1050
983
|
}
|
|
1051
984
|
function scheduleScrollFabUpdate() {
|
|
1052
985
|
if (scrollFabUpdateScheduled) return;
|
|
1053
986
|
scrollFabUpdateScheduled = true;
|
|
1054
987
|
requestAnimationFrame(function() {
|
|
1055
988
|
scrollFabUpdateScheduled = false;
|
|
1056
|
-
|
|
989
|
+
const action = computeFabAction();
|
|
990
|
+
if (action !== currentFabAction) {
|
|
991
|
+
updateScrollFab(false, action);
|
|
992
|
+
}
|
|
1057
993
|
});
|
|
1058
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
|
+
}
|
|
1059
1005
|
function scrollToTopUser() {
|
|
1060
|
-
// Explicit user-driven scroll disables auto-follow
|
|
1061
1006
|
userInteracted = true;
|
|
1062
1007
|
autoFollow = false;
|
|
1063
1008
|
try {
|
|
1064
|
-
const el =
|
|
1009
|
+
const el = SE;
|
|
1065
1010
|
el.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1011
|
+
lastScrollTop = el.scrollTop;
|
|
1066
1012
|
} catch (e) {
|
|
1067
|
-
|
|
1068
|
-
const el = document.scrollingElement || document.documentElement;
|
|
1013
|
+
const el = SE;
|
|
1069
1014
|
el.scrollTop = 0;
|
|
1015
|
+
lastScrollTop = 0;
|
|
1070
1016
|
}
|
|
1071
|
-
scheduleScrollFabUpdate();
|
|
1072
1017
|
}
|
|
1073
1018
|
function scrollToBottomUser() {
|
|
1074
|
-
// User action to go to bottom re-enables auto-follow
|
|
1075
1019
|
userInteracted = true;
|
|
1076
|
-
autoFollow =
|
|
1020
|
+
autoFollow = false;
|
|
1077
1021
|
try {
|
|
1078
|
-
const el =
|
|
1022
|
+
const el = SE;
|
|
1079
1023
|
el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' });
|
|
1024
|
+
lastScrollTop = el.scrollTop;
|
|
1080
1025
|
} catch (e) {
|
|
1081
|
-
const el =
|
|
1026
|
+
const el = SE;
|
|
1082
1027
|
el.scrollTop = el.scrollHeight;
|
|
1028
|
+
lastScrollTop = el.scrollTop;
|
|
1083
1029
|
}
|
|
1084
|
-
|
|
1030
|
+
maybeEnableAutoFollowByProximity();
|
|
1085
1031
|
}
|
|
1086
|
-
// ---------- end of scroll bottom ----------
|
|
1087
1032
|
|
|
1088
1033
|
document.addEventListener('DOMContentLoaded', function() {
|
|
1089
1034
|
new QWebChannel(qt.webChannelTransport, function (channel) {
|
|
@@ -1093,6 +1038,7 @@ class Body:
|
|
|
1093
1038
|
});
|
|
1094
1039
|
initDomRefs();
|
|
1095
1040
|
const container = els.container;
|
|
1041
|
+
|
|
1096
1042
|
function addClassToMsg(id, className) {
|
|
1097
1043
|
const msgElement = document.getElementById('msg-bot-' + id);
|
|
1098
1044
|
if (msgElement) {
|
|
@@ -1105,61 +1051,71 @@ class Body:
|
|
|
1105
1051
|
msgElement.classList.remove(className);
|
|
1106
1052
|
}
|
|
1107
1053
|
}
|
|
1108
|
-
|
|
1054
|
+
containerMouseOverHandler = function(event) {
|
|
1109
1055
|
if (event.target.classList.contains('action-img')) {
|
|
1110
1056
|
const id = event.target.getAttribute('data-id');
|
|
1111
1057
|
addClassToMsg(id, 'msg-highlight');
|
|
1112
1058
|
}
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1059
|
+
};
|
|
1060
|
+
containerMouseOutHandler = function(event) {
|
|
1115
1061
|
if (event.target.classList.contains('action-img')) {
|
|
1116
1062
|
const id = event.target.getAttribute('data-id');
|
|
1117
1063
|
removeClassFromMsg(id, 'msg-highlight');
|
|
1118
1064
|
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1065
|
+
};
|
|
1066
|
+
container.addEventListener('mouseover', containerMouseOverHandler, { passive: true });
|
|
1067
|
+
container.addEventListener('mouseout', containerMouseOutHandler, { passive: true });
|
|
1068
|
+
|
|
1069
|
+
wheelHandler = function(ev) {
|
|
1122
1070
|
userInteracted = true;
|
|
1123
1071
|
if (ev.deltaY < 0) {
|
|
1124
1072
|
autoFollow = false;
|
|
1073
|
+
} else {
|
|
1074
|
+
maybeEnableAutoFollowByProximity();
|
|
1125
1075
|
}
|
|
1126
|
-
}
|
|
1076
|
+
};
|
|
1077
|
+
document.addEventListener('wheel', wheelHandler, { passive: true });
|
|
1127
1078
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
const el = document.scrollingElement || document.documentElement;
|
|
1079
|
+
scrollHandler = function() {
|
|
1080
|
+
const el = SE;
|
|
1131
1081
|
const top = el.scrollTop;
|
|
1132
|
-
|
|
1133
|
-
// User scrolled up (ignore tiny jitter)
|
|
1134
1082
|
if (top + 1 < lastScrollTop) {
|
|
1135
1083
|
autoFollow = false;
|
|
1136
|
-
} else if (!autoFollow) {
|
|
1137
|
-
const distanceToBottom = el.scrollHeight - el.clientHeight - top;
|
|
1138
|
-
if (distanceToBottom <= AUTO_FOLLOW_REENABLE_PX) {
|
|
1139
|
-
autoFollow = true;
|
|
1140
|
-
}
|
|
1141
1084
|
}
|
|
1085
|
+
maybeEnableAutoFollowByProximity();
|
|
1142
1086
|
lastScrollTop = top;
|
|
1143
|
-
|
|
1087
|
+
const action = computeFabAction();
|
|
1088
|
+
if (action !== currentFabAction) {
|
|
1089
|
+
updateScrollFab(false, action, true);
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
window.addEventListener('scroll', scrollHandler, { passive: true });
|
|
1144
1093
|
|
|
1145
|
-
// Scroll-to-top/bottom FAB wiring
|
|
1146
1094
|
if (els.scrollFab) {
|
|
1147
|
-
|
|
1095
|
+
fabClickHandler = function(ev) {
|
|
1148
1096
|
ev.preventDefault();
|
|
1149
|
-
|
|
1097
|
+
ev.stopPropagation();
|
|
1098
|
+
const action = computeFabAction();
|
|
1099
|
+
if (action === 'up') {
|
|
1150
1100
|
scrollToTopUser();
|
|
1151
|
-
} else {
|
|
1101
|
+
} else if (action === 'down') {
|
|
1152
1102
|
scrollToBottomUser();
|
|
1153
1103
|
}
|
|
1154
|
-
|
|
1104
|
+
fabFreezeUntil = performance.now() + FAB_TOGGLE_DEBOUNCE_MS;
|
|
1105
|
+
updateScrollFab(true);
|
|
1106
|
+
};
|
|
1107
|
+
els.scrollFab.addEventListener('click', fabClickHandler, { passive: false });
|
|
1155
1108
|
}
|
|
1156
|
-
window.addEventListener('scroll', scheduleScrollFabUpdate, { passive: true });
|
|
1157
|
-
window.addEventListener('resize', scheduleScrollFabUpdate, { passive: true });
|
|
1158
1109
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1110
|
+
resizeHandler = function() {
|
|
1111
|
+
maybeEnableAutoFollowByProximity();
|
|
1112
|
+
scheduleScrollFabUpdate();
|
|
1113
|
+
};
|
|
1114
|
+
window.addEventListener('resize', resizeHandler, { passive: true });
|
|
1115
|
+
|
|
1116
|
+
updateScrollFab(true);
|
|
1161
1117
|
|
|
1162
|
-
|
|
1118
|
+
containerClickHandler = function(event) {
|
|
1163
1119
|
const copyButton = event.target.closest('.code-header-copy');
|
|
1164
1120
|
if (copyButton) {
|
|
1165
1121
|
event.preventDefault();
|
|
@@ -1232,13 +1188,13 @@ class Body:
|
|
|
1232
1188
|
}
|
|
1233
1189
|
}
|
|
1234
1190
|
}
|
|
1235
|
-
}
|
|
1191
|
+
};
|
|
1192
|
+
container.addEventListener('click', containerClickHandler, { passive: false });
|
|
1236
1193
|
|
|
1237
|
-
//
|
|
1238
|
-
window.addEventListener('
|
|
1239
|
-
window.addEventListener('beforeunload', teardown, { passive: true });
|
|
1194
|
+
// window.addEventListener('pagehide', cleanup, { once: true });
|
|
1195
|
+
// window.addEventListener('beforeunload', cleanup, { once: true });
|
|
1240
1196
|
});
|
|
1241
|
-
setTimeout(cycleTips, 10000);
|
|
1197
|
+
setTimeout(cycleTips, 10000);
|
|
1242
1198
|
</script>
|
|
1243
1199
|
</head>
|
|
1244
1200
|
<body """
|
|
@@ -1308,7 +1264,6 @@ class Body:
|
|
|
1308
1264
|
}
|
|
1309
1265
|
"""
|
|
1310
1266
|
|
|
1311
|
-
# CSS for the scroll-to-top/bottom
|
|
1312
1267
|
_SCROLL_FAB_CSS = """
|
|
1313
1268
|
#scrollFab.scroll-fab {
|
|
1314
1269
|
position: fixed;
|
|
@@ -1375,11 +1330,6 @@ class Body:
|
|
|
1375
1330
|
"""
|
|
1376
1331
|
|
|
1377
1332
|
def __init__(self, window=None):
|
|
1378
|
-
"""
|
|
1379
|
-
HTML Body
|
|
1380
|
-
|
|
1381
|
-
:param window: Window instance
|
|
1382
|
-
"""
|
|
1383
1333
|
self.window = window
|
|
1384
1334
|
self.highlight = SyntaxHighlight(window)
|
|
1385
1335
|
self._tip_keys = tuple(f"output.tips.{i}" for i in range(1, self.NUM_TIPS + 1))
|
|
@@ -1403,21 +1353,10 @@ class Body:
|
|
|
1403
1353
|
"zenburn",
|
|
1404
1354
|
)
|
|
1405
1355
|
|
|
1406
|
-
|
|
1407
1356
|
def is_timestamp_enabled(self) -> bool:
|
|
1408
|
-
"""
|
|
1409
|
-
Check if timestamp is enabled
|
|
1410
|
-
|
|
1411
|
-
:return: True if timestamp is enabled
|
|
1412
|
-
"""
|
|
1413
1357
|
return self.window.core.config.get('output_timestamp')
|
|
1414
1358
|
|
|
1415
1359
|
def prepare_styles(self) -> str:
|
|
1416
|
-
"""
|
|
1417
|
-
Prepare CSS styles
|
|
1418
|
-
|
|
1419
|
-
:return: CSS styles
|
|
1420
|
-
"""
|
|
1421
1360
|
cfg = self.window.core.config
|
|
1422
1361
|
fonts_path = os.path.join(cfg.get_app_path(), "data", "fonts").replace("\\", "/")
|
|
1423
1362
|
syntax_style = self.window.core.config.get("render.code_syntax") or "default"
|
|
@@ -1428,30 +1367,17 @@ class Body:
|
|
|
1428
1367
|
theme_css,
|
|
1429
1368
|
"pre { color: #fff; }" if syntax_style in self._syntax_dark else "pre { color: #000; }",
|
|
1430
1369
|
self.highlight.get_style_defs(),
|
|
1431
|
-
self._PERFORMANCE_CSS
|
|
1370
|
+
self._PERFORMANCE_CSS
|
|
1432
1371
|
]
|
|
1433
1372
|
return "\n".join(parts)
|
|
1434
1373
|
|
|
1435
1374
|
def prepare_action_icons(self, ctx: CtxItem) -> str:
|
|
1436
|
-
"""
|
|
1437
|
-
Append action icons
|
|
1438
|
-
|
|
1439
|
-
:param ctx: context item
|
|
1440
|
-
:return: HTML code
|
|
1441
|
-
"""
|
|
1442
1375
|
icons_html = "".join(self.get_action_icons(ctx, all=True))
|
|
1443
1376
|
if icons_html:
|
|
1444
1377
|
return f'<div class="action-icons" data-id="{ctx.id}">{icons_html}</div>'
|
|
1445
1378
|
return ""
|
|
1446
1379
|
|
|
1447
1380
|
def get_action_icons(self, ctx: CtxItem, all: bool = False) -> List[str]:
|
|
1448
|
-
"""
|
|
1449
|
-
Get action icons for context item
|
|
1450
|
-
|
|
1451
|
-
:param ctx: context item
|
|
1452
|
-
:param all: True to show all icons
|
|
1453
|
-
:return: list of icons
|
|
1454
|
-
"""
|
|
1455
1381
|
icons: List[str] = []
|
|
1456
1382
|
if ctx.output:
|
|
1457
1383
|
cid = ctx.id
|
|
@@ -1471,46 +1397,17 @@ class Body:
|
|
|
1471
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>')
|
|
1472
1398
|
return icons
|
|
1473
1399
|
|
|
1474
|
-
def get_icon(
|
|
1475
|
-
self,
|
|
1476
|
-
icon: str,
|
|
1477
|
-
title: Optional[str] = None,
|
|
1478
|
-
item: Optional[CtxItem] = None
|
|
1479
|
-
) -> str:
|
|
1480
|
-
"""
|
|
1481
|
-
Get icon
|
|
1482
|
-
|
|
1483
|
-
:param icon: icon name
|
|
1484
|
-
:param title: icon title
|
|
1485
|
-
:param item: context item
|
|
1486
|
-
:return: icon HTML
|
|
1487
|
-
"""
|
|
1400
|
+
def get_icon(self, icon: str, title: Optional[str] = None, item: Optional[CtxItem] = None) -> str:
|
|
1488
1401
|
app_path = self.window.core.config.get_app_path()
|
|
1489
1402
|
icon_path = os.path.join(app_path, "data", "icons", f"{icon}.svg")
|
|
1490
1403
|
return f'<img src="file://{icon_path}" class="action-img" title="{title}" alt="{title}" data-id="{item.id}">'
|
|
1491
1404
|
|
|
1492
|
-
def get_image_html(
|
|
1493
|
-
self,
|
|
1494
|
-
url: str,
|
|
1495
|
-
num: Optional[int] = None,
|
|
1496
|
-
num_all: Optional[int] = None
|
|
1497
|
-
) -> str:
|
|
1498
|
-
"""
|
|
1499
|
-
Get media image/video/audio HTML
|
|
1500
|
-
|
|
1501
|
-
:param url: URL to image
|
|
1502
|
-
:param num: number of image
|
|
1503
|
-
:param num_all: number of all images
|
|
1504
|
-
:return: HTML code
|
|
1505
|
-
"""
|
|
1405
|
+
def get_image_html(self, url: str, num: Optional[int] = None, num_all: Optional[int] = None) -> str:
|
|
1506
1406
|
url, path = self.window.core.filesystem.extract_local_url(url)
|
|
1507
1407
|
basename = os.path.basename(path)
|
|
1508
|
-
|
|
1509
|
-
# if video file then embed video player
|
|
1510
1408
|
ext = os.path.splitext(basename)[1].lower()
|
|
1511
1409
|
video_exts = (".mp4", ".webm", ".ogg", ".mov", ".avi", ".mkv")
|
|
1512
1410
|
if ext in video_exts:
|
|
1513
|
-
# check if .webm file exists for better compatibility
|
|
1514
1411
|
if ext != ".webm":
|
|
1515
1412
|
webm_path = os.path.splitext(path)[0] + ".webm"
|
|
1516
1413
|
if os.path.exists(webm_path):
|
|
@@ -1526,20 +1423,7 @@ class Body:
|
|
|
1526
1423
|
'''
|
|
1527
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/>'
|
|
1528
1425
|
|
|
1529
|
-
def get_url_html(
|
|
1530
|
-
self,
|
|
1531
|
-
url: str,
|
|
1532
|
-
num: Optional[int] = None,
|
|
1533
|
-
num_all: Optional[int] = None
|
|
1534
|
-
) -> str:
|
|
1535
|
-
"""
|
|
1536
|
-
Get URL HTML
|
|
1537
|
-
|
|
1538
|
-
:param url: external URL
|
|
1539
|
-
:param num: number of URL
|
|
1540
|
-
:param num_all: number of all URLs
|
|
1541
|
-
:return: HTML code
|
|
1542
|
-
"""
|
|
1426
|
+
def get_url_html(self, url: str, num: Optional[int] = None, num_all: Optional[int] = None) -> str:
|
|
1543
1427
|
app_path = self.window.core.config.get_app_path()
|
|
1544
1428
|
icon_path = os.path.join(app_path, "data", "icons", "language.svg").replace("\\", "/")
|
|
1545
1429
|
icon = f'<img src="file://{icon_path}" class="extra-src-icon">'
|
|
@@ -1547,12 +1431,6 @@ class Body:
|
|
|
1547
1431
|
return f'{icon}<a href="{url}" title="{url}">{url}</a> <small>{num_str}</small>'
|
|
1548
1432
|
|
|
1549
1433
|
def get_docs_html(self, docs: List[Dict]) -> str:
|
|
1550
|
-
"""
|
|
1551
|
-
Get Llama-index doc metadata HTML
|
|
1552
|
-
|
|
1553
|
-
:param docs: list of document metadata
|
|
1554
|
-
:return: HTML code
|
|
1555
|
-
"""
|
|
1556
1434
|
html_parts: List[str] = []
|
|
1557
1435
|
src_parts: List[str] = []
|
|
1558
1436
|
num = 1
|
|
@@ -1582,20 +1460,7 @@ class Body:
|
|
|
1582
1460
|
|
|
1583
1461
|
return "".join(html_parts)
|
|
1584
1462
|
|
|
1585
|
-
def get_file_html(
|
|
1586
|
-
self,
|
|
1587
|
-
url: str,
|
|
1588
|
-
num: Optional[int] = None,
|
|
1589
|
-
num_all: Optional[int] = None
|
|
1590
|
-
) -> str:
|
|
1591
|
-
"""
|
|
1592
|
-
Get file HTML
|
|
1593
|
-
|
|
1594
|
-
:param url: URL to file
|
|
1595
|
-
:param num: number of file
|
|
1596
|
-
:param num_all: number of all files
|
|
1597
|
-
:return: HTML code
|
|
1598
|
-
"""
|
|
1463
|
+
def get_file_html(self, url: str, num: Optional[int] = None, num_all: Optional[int] = None) -> str:
|
|
1599
1464
|
app_path = self.window.core.config.get_app_path()
|
|
1600
1465
|
icon_path = os.path.join(app_path, "data", "icons", "attachments.svg").replace("\\", "/")
|
|
1601
1466
|
icon = f'<img src="file://{icon_path}" class="extra-src-icon">'
|
|
@@ -1604,12 +1469,6 @@ class Body:
|
|
|
1604
1469
|
return f'{icon} <b>{num_str}</b> <a href="{url}">{path}</a>'
|
|
1605
1470
|
|
|
1606
1471
|
def prepare_tool_extra(self, ctx: CtxItem) -> str:
|
|
1607
|
-
"""
|
|
1608
|
-
Prepare footer extra
|
|
1609
|
-
|
|
1610
|
-
:param ctx: context item
|
|
1611
|
-
:return: HTML code
|
|
1612
|
-
"""
|
|
1613
1472
|
extra = ctx.extra
|
|
1614
1473
|
if not extra:
|
|
1615
1474
|
return ""
|
|
@@ -1647,11 +1506,6 @@ class Body:
|
|
|
1647
1506
|
return "".join(parts)
|
|
1648
1507
|
|
|
1649
1508
|
def get_all_tips(self) -> str:
|
|
1650
|
-
"""
|
|
1651
|
-
Get all tips for the output view
|
|
1652
|
-
|
|
1653
|
-
:return: JSON string of tips
|
|
1654
|
-
"""
|
|
1655
1509
|
if not self.window.core.config.get("layout.tooltips", False):
|
|
1656
1510
|
return "[]"
|
|
1657
1511
|
|
|
@@ -1665,12 +1519,6 @@ class Body:
|
|
|
1665
1519
|
return _json_dumps(tips)
|
|
1666
1520
|
|
|
1667
1521
|
def get_html(self, pid: int) -> str:
|
|
1668
|
-
"""
|
|
1669
|
-
Build webview HTML code (fast path, minimal allocations)
|
|
1670
|
-
|
|
1671
|
-
:param pid: process ID
|
|
1672
|
-
:return: HTML code
|
|
1673
|
-
"""
|
|
1674
1522
|
cfg_get = self.window.core.config.get
|
|
1675
1523
|
style = cfg_get("theme.style", "blocks")
|
|
1676
1524
|
classes = ["theme-" + style]
|
|
@@ -1684,7 +1532,6 @@ class Body:
|
|
|
1684
1532
|
styles_css = self.prepare_styles()
|
|
1685
1533
|
tips_json = self.get_all_tips()
|
|
1686
1534
|
|
|
1687
|
-
# Build file:// paths for FAB icons
|
|
1688
1535
|
app_path = self.window.core.config.get_app_path().replace("\\", "/")
|
|
1689
1536
|
expand_path = os.path.join(app_path, "data", "icons", "expand.svg").replace("\\", "/")
|
|
1690
1537
|
collapse_path = os.path.join(app_path, "data", "icons", "collapse.svg").replace("\\", "/")
|