pygpt-net 2.6.1__py3-none-any.whl → 2.6.6__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.
Files changed (131) hide show
  1. pygpt_net/CHANGELOG.txt +23 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +20 -1
  4. pygpt_net/config.py +55 -65
  5. pygpt_net/controller/__init__.py +5 -2
  6. pygpt_net/controller/calendar/note.py +101 -126
  7. pygpt_net/controller/chat/chat.py +38 -35
  8. pygpt_net/controller/chat/render.py +154 -214
  9. pygpt_net/controller/chat/response.py +5 -3
  10. pygpt_net/controller/chat/stream.py +92 -27
  11. pygpt_net/controller/config/config.py +39 -42
  12. pygpt_net/controller/config/field/checkbox.py +16 -12
  13. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  14. pygpt_net/controller/config/field/cmd.py +51 -57
  15. pygpt_net/controller/config/field/combo.py +33 -16
  16. pygpt_net/controller/config/field/dictionary.py +48 -55
  17. pygpt_net/controller/config/field/input.py +50 -32
  18. pygpt_net/controller/config/field/slider.py +40 -45
  19. pygpt_net/controller/config/field/textarea.py +20 -6
  20. pygpt_net/controller/config/placeholder.py +110 -231
  21. pygpt_net/controller/ctx/common.py +48 -48
  22. pygpt_net/controller/ctx/ctx.py +91 -132
  23. pygpt_net/controller/lang/mapping.py +57 -95
  24. pygpt_net/controller/lang/plugins.py +64 -55
  25. pygpt_net/controller/lang/settings.py +39 -38
  26. pygpt_net/controller/layout/layout.py +176 -109
  27. pygpt_net/controller/mode/mode.py +88 -85
  28. pygpt_net/controller/model/model.py +73 -73
  29. pygpt_net/controller/plugins/plugins.py +209 -223
  30. pygpt_net/controller/plugins/presets.py +54 -55
  31. pygpt_net/controller/plugins/settings.py +54 -69
  32. pygpt_net/controller/presets/editor.py +33 -88
  33. pygpt_net/controller/presets/experts.py +20 -1
  34. pygpt_net/controller/presets/presets.py +293 -298
  35. pygpt_net/controller/settings/profile.py +16 -4
  36. pygpt_net/controller/theme/theme.py +72 -81
  37. pygpt_net/controller/ui/mode.py +118 -186
  38. pygpt_net/controller/ui/tabs.py +69 -90
  39. pygpt_net/controller/ui/ui.py +47 -56
  40. pygpt_net/controller/ui/vision.py +24 -23
  41. pygpt_net/core/agents/runner.py +15 -7
  42. pygpt_net/core/bridge/bridge.py +5 -5
  43. pygpt_net/core/command/command.py +149 -219
  44. pygpt_net/core/ctx/ctx.py +94 -146
  45. pygpt_net/core/debug/debug.py +48 -58
  46. pygpt_net/core/experts/experts.py +3 -3
  47. pygpt_net/core/models/models.py +74 -112
  48. pygpt_net/core/modes/modes.py +13 -21
  49. pygpt_net/core/plugins/plugins.py +154 -177
  50. pygpt_net/core/presets/presets.py +103 -176
  51. pygpt_net/core/render/web/body.py +217 -215
  52. pygpt_net/core/render/web/renderer.py +330 -474
  53. pygpt_net/core/text/utils.py +28 -44
  54. pygpt_net/core/tokens/tokens.py +104 -203
  55. pygpt_net/data/config/config.json +3 -3
  56. pygpt_net/data/config/models.json +3 -3
  57. pygpt_net/data/locale/locale.de.ini +2 -0
  58. pygpt_net/data/locale/locale.en.ini +2 -0
  59. pygpt_net/data/locale/locale.es.ini +2 -0
  60. pygpt_net/data/locale/locale.fr.ini +2 -0
  61. pygpt_net/data/locale/locale.it.ini +2 -0
  62. pygpt_net/data/locale/locale.pl.ini +3 -1
  63. pygpt_net/data/locale/locale.uk.ini +2 -0
  64. pygpt_net/data/locale/locale.zh.ini +2 -0
  65. pygpt_net/item/ctx.py +141 -139
  66. pygpt_net/plugin/agent/plugin.py +2 -1
  67. pygpt_net/plugin/audio_output/plugin.py +5 -2
  68. pygpt_net/plugin/base/plugin.py +101 -85
  69. pygpt_net/plugin/bitbucket/__init__.py +12 -0
  70. pygpt_net/plugin/bitbucket/config.py +267 -0
  71. pygpt_net/plugin/bitbucket/plugin.py +126 -0
  72. pygpt_net/plugin/bitbucket/worker.py +569 -0
  73. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  74. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  75. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  76. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  77. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  78. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  79. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  80. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  81. pygpt_net/plugin/experts/plugin.py +2 -2
  82. pygpt_net/plugin/facebook/__init__.py +12 -0
  83. pygpt_net/plugin/facebook/config.py +359 -0
  84. pygpt_net/plugin/facebook/plugin.py +113 -0
  85. pygpt_net/plugin/facebook/worker.py +698 -0
  86. pygpt_net/plugin/github/__init__.py +12 -0
  87. pygpt_net/plugin/github/config.py +441 -0
  88. pygpt_net/plugin/github/plugin.py +126 -0
  89. pygpt_net/plugin/github/worker.py +674 -0
  90. pygpt_net/plugin/google/__init__.py +12 -0
  91. pygpt_net/plugin/google/config.py +367 -0
  92. pygpt_net/plugin/google/plugin.py +126 -0
  93. pygpt_net/plugin/google/worker.py +826 -0
  94. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  95. pygpt_net/plugin/mailer/plugin.py +3 -5
  96. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  97. pygpt_net/plugin/real_time/plugin.py +52 -60
  98. pygpt_net/plugin/slack/__init__.py +12 -0
  99. pygpt_net/plugin/slack/config.py +349 -0
  100. pygpt_net/plugin/slack/plugin.py +115 -0
  101. pygpt_net/plugin/slack/worker.py +639 -0
  102. pygpt_net/plugin/telegram/__init__.py +12 -0
  103. pygpt_net/plugin/telegram/config.py +308 -0
  104. pygpt_net/plugin/telegram/plugin.py +117 -0
  105. pygpt_net/plugin/telegram/worker.py +563 -0
  106. pygpt_net/plugin/twitter/__init__.py +12 -0
  107. pygpt_net/plugin/twitter/config.py +491 -0
  108. pygpt_net/plugin/twitter/plugin.py +125 -0
  109. pygpt_net/plugin/twitter/worker.py +837 -0
  110. pygpt_net/provider/agents/llama_index/legacy/openai_assistant.py +35 -3
  111. pygpt_net/tools/code_interpreter/tool.py +0 -1
  112. pygpt_net/tools/translator/tool.py +1 -1
  113. pygpt_net/ui/base/config_dialog.py +86 -100
  114. pygpt_net/ui/base/context_menu.py +48 -46
  115. pygpt_net/ui/dialog/preset.py +34 -77
  116. pygpt_net/ui/layout/ctx/ctx_list.py +10 -6
  117. pygpt_net/ui/layout/toolbox/presets.py +41 -41
  118. pygpt_net/ui/main.py +49 -31
  119. pygpt_net/ui/tray.py +61 -60
  120. pygpt_net/ui/widget/calendar/select.py +86 -70
  121. pygpt_net/ui/widget/lists/attachment.py +86 -44
  122. pygpt_net/ui/widget/lists/base_list_combo.py +85 -33
  123. pygpt_net/ui/widget/lists/context.py +135 -188
  124. pygpt_net/ui/widget/lists/preset.py +59 -61
  125. pygpt_net/ui/widget/textarea/web.py +161 -48
  126. pygpt_net/utils.py +8 -1
  127. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/METADATA +164 -2
  128. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/RECORD +131 -103
  129. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/LICENSE +0 -0
  130. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/WHEEL +0 -0
  131. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.13 16:00:00 #
9
+ # Updated Date: 2025.08.16 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -47,11 +47,10 @@ class Body:
47
47
  let scrollTimeout = null;
48
48
  let prevScroll = 0;
49
49
  let bridge;
50
+ let streamQ = [];
51
+ let streamRAF = 0;
50
52
  let pid = """
51
- _HTML_P2 = """
52
- new QWebChannel(qt.webChannelTransport, function (channel) {
53
- bridge = channel.objects.bridge;
54
- });
53
+ _HTML_P2 = """
55
54
  let collapsed_idx = [];
56
55
  let domOutputStream = document.getElementById('_append_output_');
57
56
  let domOutput = document.getElementById('_output_');
@@ -63,14 +62,21 @@ class Body:
63
62
  _HTML_P3 = """;
64
63
  let tips_hidden = false;
65
64
 
65
+ let els = {};
66
+ let highlightScheduled = false;
67
+ let pendingHighlightRoot = null;
68
+ let pendingHighlightMath = false;
69
+ let scrollScheduled = false;
70
+ const AMP_LT_GT = /&(lt|gt);/g;
71
+
66
72
  history.scrollRestoration = "manual";
67
73
  document.addEventListener('keydown', function(event) {
68
74
  if (event.ctrlKey && event.key === 'f') {
69
- window.location.href = 'bridge://open_find:' + pid; // send to bridge
75
+ window.location.href = 'bridge://open_find:' + pid;
70
76
  event.preventDefault();
71
77
  }
72
78
  if (event.key === 'Escape') {
73
- window.location.href = 'bridge://escape'; // send to bridge
79
+ window.location.href = 'bridge://escape';
74
80
  event.preventDefault();
75
81
  }
76
82
  });
@@ -84,42 +90,71 @@ class Body:
84
90
  bridge.log(text);
85
91
  }
86
92
  }
87
- function prepare() {
88
- collapsed_idx = []; // clear collapsed code
89
- hideTips();
93
+ function initDomRefs() {
94
+ els.container = document.getElementById('container');
95
+ els.nodes = document.getElementById('_nodes_');
96
+ els.appendInput = document.getElementById('_append_input_');
97
+ els.appendOutputBefore = document.getElementById('_append_output_before_');
98
+ els.appendOutput = document.getElementById('_append_output_');
99
+ els.appendLive = document.getElementById('_append_live_');
100
+ els.footer = document.getElementById('_footer_');
101
+ els.loader = document.getElementById('_loader_');
102
+ els.tips = document.getElementById('tips');
90
103
  }
91
- function sanitize(content) {
92
- return content.replace(/</g, '<').replace(/>/g, '>');
104
+ function scheduleHighlight(root, withMath = true) {
105
+ const scope = root && root.nodeType === 1 ? root : document;
106
+ if (!pendingHighlightRoot || pendingHighlightRoot === document) {
107
+ pendingHighlightRoot = scope;
108
+ } else if (!pendingHighlightRoot.contains(scope)) {
109
+ pendingHighlightRoot = document;
110
+ }
111
+ if (withMath) pendingHighlightMath = true;
112
+ if (highlightScheduled) return;
113
+ highlightScheduled = true;
114
+ requestAnimationFrame(function() {
115
+ try {
116
+ highlightCodeInternal(pendingHighlightRoot || document, pendingHighlightMath);
117
+ } finally {
118
+ highlightScheduled = false;
119
+ pendingHighlightRoot = null;
120
+ pendingHighlightMath = false;
121
+ }
122
+ });
93
123
  }
94
- function highlightCode(withMath = true) {
95
- document.querySelectorAll('pre code').forEach(el => {
96
- if (!el.classList.contains('hljs')) hljs.highlightElement(el);
124
+ function highlightCodeInternal(root, withMath) {
125
+ (root || document).querySelectorAll('pre code:not(.hljs)').forEach(el => {
126
+ hljs.highlightElement(el);
97
127
  });
98
128
  if (withMath) {
99
- renderMath();
100
- }
101
- restoreCollapsedCode();
129
+ renderMath(root);
130
+ }
131
+ restoreCollapsedCode(root);
132
+ }
133
+ function highlightCode(withMath = true, root = null) {
134
+ scheduleHighlight(root || document, withMath);
102
135
  }
103
136
  function hideTips() {
104
137
  if (tips_hidden) return;
105
- document.getElementById('tips').style.display = 'none';
138
+ const t = els.tips || document.getElementById('tips');
139
+ if (t) t.style.display = 'none';
106
140
  tips_hidden = true;
107
141
  }
108
142
  function showTips() {
109
143
  if (tips_hidden) return;
110
144
  if (tips.length === 0) return;
111
- document.getElementById('tips').style.display = 'block';
145
+ const t = els.tips || document.getElementById('tips');
146
+ if (t) t.style.display = 'block';
112
147
  tips_hidden = false;
113
- }
148
+ }
114
149
  function cycleTips() {
115
150
  if (tips_hidden) return;
116
151
  if (tips.length === 0) return;
117
- let tipContainer = document.getElementById('tips');
152
+ let tipContainer = els.tips || document.getElementById('tips');
118
153
  let currentTip = 0;
119
154
  function showNextTip() {
120
155
  if (tips_hidden) return;
121
156
  tipContainer.innerHTML = tips[currentTip];
122
- tipContainer.classList.add('visible');
157
+ tipContainer.classList.add('visible');
123
158
  setTimeout(function() {
124
159
  if (tips_hidden) return;
125
160
  tipContainer.classList.remove('visible');
@@ -131,9 +166,10 @@ class Body:
131
166
  }
132
167
  showNextTip();
133
168
  }
134
- function renderMath() {
135
- const scripts = document.querySelectorAll('script[type^="math/tex"]');
136
- scripts.forEach(function(script) {
169
+ function renderMath(root) {
170
+ const scope = root || document;
171
+ const scripts = scope.querySelectorAll('script[type^="math/tex"]');
172
+ scripts.forEach(function(script) {
137
173
  const displayMode = script.type.indexOf('mode=display') > -1;
138
174
  const mathContent = script.textContent || script.innerText;
139
175
  const element = document.createElement(displayMode ? 'div' : 'span');
@@ -145,7 +181,8 @@ class Body:
145
181
  } catch (err) {
146
182
  element.textContent = mathContent;
147
183
  }
148
- script.parentNode.replaceChild(element, script);
184
+ const parent = script.parentNode;
185
+ if (parent) parent.replaceChild(element, script);
149
186
  });
150
187
  }
151
188
  function isNearBottom(marginPx = 100) {
@@ -153,32 +190,53 @@ class Body:
153
190
  const distanceToBottom = el.scrollHeight - el.clientHeight - el.scrollTop;
154
191
  return distanceToBottom <= marginPx;
155
192
  }
193
+ function scheduleScroll(live = false) {
194
+ if (scrollScheduled) return;
195
+ scrollScheduled = true;
196
+ requestAnimationFrame(function() {
197
+ scrollScheduled = false;
198
+ scrollToBottom(live);
199
+ });
200
+ }
156
201
  function scrollToBottom(live = false) {
157
202
  const el = document.scrollingElement || document.documentElement;
158
- const marginPx = 300;
203
+ const marginPx = 450;
159
204
  let behavior = 'instant';
160
205
  if (live == true) {
161
- behavior = 'instant'; // no smooth scroll for live updates
206
+ behavior = 'instant';
162
207
  } else {
163
- behavior = 'smooth'; // smooth scroll for normal updates, TODO: implement in Chromium
208
+ behavior = 'smooth';
164
209
  }
165
210
  if (isNearBottom(marginPx) || live == false) {
166
211
  el.scrollTo({ top: el.scrollHeight, behavior });
167
212
  }
168
213
  prevScroll = el.scrollHeight;
169
- getScrollPosition(); // store using bridge
214
+ }
215
+ function sanitize(content) {
216
+ if (content.indexOf('&amp;') === -1) return content;
217
+ return content.replace(AMP_LT_GT, '&$1;'); // &amp;lt; -> &lt;, &amp;gt; -> &gt;
170
218
  }
171
219
  function appendToInput(content) {
172
- const element = document.getElementById('_append_input_');
220
+ const element = els.appendInput || document.getElementById('_append_input_');
173
221
  if (element) {
174
222
  element.insertAdjacentHTML('beforeend', sanitize(content));
223
+ highlightCode(true, element);
224
+ scheduleScroll();
175
225
  }
176
- highlightCode();
177
- scrollToBottom();
226
+ }
227
+ function getStreamContainer() {
228
+ if (domOutputStream && document.body.contains(domOutputStream)) {
229
+ return domOutputStream;
230
+ }
231
+ let element = els.appendOutput || document.getElementById('_append_output_');
232
+ if (element) {
233
+ domOutputStream = element;
234
+ }
235
+ return element;
178
236
  }
179
237
  function appendToOutput(bot_name, content) {
180
238
  hideTips();
181
- const element = document.getElementById('_append_output_');
239
+ const element = getStreamContainer();
182
240
  if (element) {
183
241
  let box = element.querySelector('.msg-box');
184
242
  let msg;
@@ -200,21 +258,21 @@ class Body:
200
258
  }
201
259
  if (msg) {
202
260
  msg.insertAdjacentHTML('beforeend', sanitize(content));
261
+ highlightCode(true, msg);
262
+ scheduleScroll();
203
263
  }
204
264
  }
205
- highlightCode();
206
- scrollToBottom();
207
265
  }
208
266
  function appendNode(content) {
209
267
  clearStreamBefore();
210
268
  prevScroll = 0;
211
- const element = document.getElementById('_nodes_');
269
+ const element = els.nodes || document.getElementById('_nodes_');
212
270
  if (element) {
213
271
  element.classList.remove('empty_list');
214
272
  element.insertAdjacentHTML('beforeend', sanitize(content));
273
+ highlightCode(true, element);
274
+ scheduleScroll();
215
275
  }
216
- highlightCode();
217
- scrollToBottom();
218
276
  }
219
277
  function appendExtra(id, content) {
220
278
  hideTips();
@@ -224,10 +282,10 @@ class Body:
224
282
  const extra = element.querySelector('.msg-extra');
225
283
  if (extra) {
226
284
  extra.insertAdjacentHTML('beforeend', sanitize(content));
285
+ highlightCode(true, extra);
286
+ scheduleScroll();
227
287
  }
228
288
  }
229
- highlightCode();
230
- scrollToBottom();
231
289
  }
232
290
  function removeNode(id) {
233
291
  prevScroll = 0;
@@ -240,14 +298,14 @@ class Body:
240
298
  element.remove();
241
299
  }
242
300
  highlightCode();
243
- scrollToBottom();
301
+ scheduleScroll();
244
302
  }
245
303
  function removeNodesFromId(id) {
246
304
  prevScroll = 0;
247
- const container = document.getElementById('_nodes_');
305
+ const container = els.nodes || document.getElementById('_nodes_');
248
306
  if (container) {
249
307
  const elements = container.querySelectorAll('.msg-box');
250
- remove = false;
308
+ let remove = false;
251
309
  elements.forEach(function(element) {
252
310
  if (element.id.endsWith('-' + id)) {
253
311
  remove = true;
@@ -256,29 +314,16 @@ class Body:
256
314
  element.remove();
257
315
  }
258
316
  });
317
+ highlightCode(true, container);
318
+ scheduleScroll();
259
319
  }
260
- highlightCode();
261
- scrollToBottom();
262
320
  }
263
- function getStreamContainer() {
264
- let element;
265
- if (domOutputStream) {
266
- element = domOutputStream;
267
- } else {
268
- element = document.getElementById('_append_output_');
269
- if (element) {
270
- domOutputStream = element;
271
- }
272
- }
273
- return element;
274
- }
275
321
  function clearStream() {
276
322
  hideTips();
277
323
  domLastParagraphBlock = null;
278
324
  domLastCodeBlock = null;
279
325
  domOutputStream = null;
280
326
  const element = getStreamContainer();
281
- let msg;
282
327
  if (element) {
283
328
  let box = element.querySelector('.msg-box');
284
329
  let msg;
@@ -286,46 +331,53 @@ class Body:
286
331
  box = document.createElement('div');
287
332
  box.classList.add('msg-box');
288
333
  box.classList.add('msg-bot');
289
- const name = document.createElement('div');
290
- name.classList.add('name-header');
291
- name.classList.add('name-bot');
292
- name.textContent = bot_name;
293
334
  msg = document.createElement('div');
294
335
  msg.classList.add('msg');
295
- box.appendChild(name);
296
336
  box.appendChild(msg);
297
337
  element.appendChild(box);
298
338
  } else {
299
339
  msg = box.querySelector('.msg');
300
340
  }
301
341
  if (msg) {
302
- msg.innerHTML = ''; // clear previous content
342
+ msg.replaceChildren();
303
343
  }
304
344
  }
305
345
  }
306
346
  function beginStream() {
307
347
  hideTips();
308
348
  clearOutput();
309
- scrollToBottom();
349
+ scheduleScroll();
310
350
  }
311
351
  function endStream() {
312
352
  clearOutput();
313
353
  }
354
+ function enqueueStream(name_header, content, chunk, replace = false, is_code_block = false) {
355
+ streamQ.push({name_header, content, chunk, replace, is_code_block});
356
+ if (!streamRAF) {
357
+ streamRAF = requestAnimationFrame(drainStream);
358
+ }
359
+ }
360
+ function drainStream() {
361
+ streamRAF = 0;
362
+ while (streamQ.length) {
363
+ const {name_header, content, chunk, replace, is_code_block} = streamQ.shift();
364
+ appendStream(name_header, content, chunk, replace, is_code_block);
365
+ }
366
+ }
314
367
  function appendStream(name_header, content, chunk, replace = false, is_code_block = false) {
315
368
  hideTips();
316
369
  const element = getStreamContainer();
317
- doHighlight = true;
318
- doMath = true;
319
370
  let msg;
320
371
  if (element) {
321
372
  let box = element.querySelector('.msg-box');
322
- let msg;
323
373
  if (!box) {
324
374
  box = document.createElement('div');
325
375
  box.classList.add('msg-box');
326
376
  box.classList.add('msg-bot');
327
377
  if (name_header != '') {
328
378
  const name = document.createElement('div');
379
+ name.classList.add('name-header');
380
+ name.classList.add('name-bot');
329
381
  name.innerHTML = name_header;
330
382
  box.appendChild(name);
331
383
  }
@@ -338,66 +390,55 @@ class Body:
338
390
  }
339
391
  if (msg) {
340
392
  if (replace) {
341
- msg.innerHTML = sanitize(content);
342
- domLastCodeBlock = null; // reset last code block
343
- domLastParagraphBlock = null; // reset last paragraph block
393
+ msg.replaceChildren();
394
+ if (content) {
395
+ msg.insertAdjacentHTML('afterbegin', content);
396
+ }
397
+ let doMath = true;
398
+ if (!is_code_block) {
399
+ doMath = false;
400
+ }
401
+ highlightCode(doMath, msg);
402
+ domLastCodeBlock = null;
403
+ domLastParagraphBlock = null;
344
404
  } else {
345
405
  if (is_code_block) {
346
406
  let lastCodeBlock;
347
407
  if (domLastCodeBlock) {
348
408
  lastCodeBlock = domLastCodeBlock;
349
409
  } else {
350
- // find last code block in the message
351
410
  const msgBlocks = msg.querySelectorAll('pre');
352
411
  if (msgBlocks.length > 0) {
353
412
  lastCodeBlock = msgBlocks[msgBlocks.length - 1].querySelector('code');
354
413
  }
355
414
  }
356
415
  if (lastCodeBlock) {
357
- // append to last code block
358
416
  lastCodeBlock.insertAdjacentHTML('beforeend', chunk);
359
417
  domLastCodeBlock = lastCodeBlock;
360
- doHighlight = false;
361
418
  } else {
362
- // if no code block, append chunk as normal text
363
- msg.insertAdjacentHTML('beforeend', chunk); // append chunk
364
- domLastCodeBlock = null; // reset last code block
419
+ msg.insertAdjacentHTML('beforeend', chunk);
420
+ domLastCodeBlock = null;
365
421
  }
366
- doMath = false; // disable math rendering for code blocks
367
422
  } else {
368
- domLastCodeBlock = null; // reset last code block
369
- if (msg.innerHTML.trim().endsWith('</p>')) {
370
- let lastParagraphBlock;
371
- if (domLastParagraphBlock) {
372
- lastParagraphBlock = domLastParagraphBlock;
373
- } else {
374
- const blocksParagraph = msg.querySelectorAll('p');
375
- if (blocksParagraph.length > 0) {
376
- lastParagraphBlock = blocksParagraph[blocksParagraph.length - 1];
377
- }
378
- }
379
- if (lastParagraphBlock) {
380
- domLastParagraphBlock = lastParagraphBlock; // store last paragraph block
381
- lastParagraphBlock.insertAdjacentHTML('beforeend', chunk); // append to last paragraph
382
- } else {
383
- domLastParagraphBlock = null; // reset last paragraph block
384
- msg.insertAdjacentHTML('beforeend', chunk); // append chunk
385
- }
423
+ domLastCodeBlock = null;
424
+ let p = (domLastParagraphBlock && msg.contains(domLastParagraphBlock))
425
+ ? domLastParagraphBlock
426
+ : (msg.lastElementChild && msg.lastElementChild.tagName === 'P'
427
+ ? msg.lastElementChild
428
+ : null);
429
+ if (p) {
430
+ p.insertAdjacentHTML('beforeend', sanitize(chunk));
431
+ domLastParagraphBlock = p;
386
432
  } else {
387
- domLastParagraphBlock = null; // reset last paragraph block
388
- msg.insertAdjacentHTML('beforeend', chunk); // append chunk
433
+ msg.insertAdjacentHTML('beforeend', sanitize(chunk));
434
+ const last = msg.lastElementChild;
435
+ domLastParagraphBlock = (last && last.tagName === 'P') ? last : null;
389
436
  }
390
- doHighlight = false;
391
437
  }
392
438
  }
393
439
  }
394
440
  }
395
- if (replace) {
396
- if (doHighlight) {
397
- highlightCode(doMath); // with or without math
398
- }
399
- }
400
- scrollToBottom(true);
441
+ scheduleScroll(true);
401
442
  }
402
443
  function replaceOutput(name_header, content) {
403
444
  hideTips();
@@ -411,6 +452,8 @@ class Body:
411
452
  box.classList.add('msg-bot');
412
453
  if (name_header != '') {
413
454
  const name = document.createElement('div');
455
+ name.classList.add('name-header');
456
+ name.classList.add('name-bot');
414
457
  name.innerHTML = name_header;
415
458
  box.appendChild(name);
416
459
  }
@@ -422,63 +465,35 @@ class Body:
422
465
  msg = box.querySelector('.msg');
423
466
  }
424
467
  if (msg) {
425
- msg.innerHTML = sanitize(content);
468
+ msg.replaceChildren();
469
+ msg.insertAdjacentHTML('afterbegin', sanitize(content));
470
+ highlightCode(true, msg);
471
+ scheduleScroll();
426
472
  }
427
473
  }
428
- highlightCode();
429
- scrollToBottom();
430
474
  }
431
475
  function nextStream() {
432
476
  hideTips();
433
- // Clear the current stream output and copy it to the before output
434
- // 1. copy current output from _append_output_ to _append_output_before_
435
- // 2. clear _append_output_
436
- const element = document.getElementById('_append_output_');
437
- const elementBefore = document.getElementById('_append_output_before_');
477
+ const element = els.appendOutput || document.getElementById('_append_output_');
478
+ const elementBefore = els.appendOutputBefore || document.getElementById('_append_output_before_');
438
479
  if (element && elementBefore) {
439
- elementBefore.insertAdjacentHTML('beforeend', element.innerHTML);
440
- element.innerHTML = ''; // clear current output
441
- domLastCodeBlock = null; // reset last code block
442
- domLastParagraphBlock = null; // reset last paragraph block
443
- scrollToBottom();
480
+ const frag = document.createDocumentFragment();
481
+ while (element.firstChild) {
482
+ frag.appendChild(element.firstChild);
483
+ }
484
+ elementBefore.appendChild(frag);
485
+ domLastCodeBlock = null;
486
+ domLastParagraphBlock = null;
487
+ scheduleScroll();
444
488
  }
445
489
  }
446
490
  function clearStreamBefore() {
447
491
  hideTips();
448
- const element = document.getElementById('_append_output_before_');
492
+ const element = els.appendOutputBefore || document.getElementById('_append_output_before_');
449
493
  if (element) {
450
494
  element.replaceChildren();
451
495
  }
452
496
  }
453
- function replaceOutput(bot_name, content) {
454
- hideTips();
455
- const element = getStreamContainer();
456
- if (element) {
457
- let box = element.querySelector('.msg-box');
458
- let msg;
459
- if (!box) {
460
- box = document.createElement('div');
461
- box.classList.add('msg-box');
462
- box.classList.add('msg-bot');
463
- const name = document.createElement('div');
464
- name.classList.add('name-header');
465
- name.classList.add('name-bot');
466
- name.textContent = bot_name;
467
- msg = document.createElement('div');
468
- msg.classList.add('msg');
469
- box.appendChild(name);
470
- box.appendChild(msg);
471
- element.appendChild(box);
472
- } else {
473
- msg = box.querySelector('.msg');
474
- }
475
- if (msg) {
476
- msg.innerHTML = sanitize(content);
477
- }
478
- }
479
- highlightCode();
480
- scrollToBottom();
481
- }
482
497
  function appendToolOutput(content) {
483
498
  hideToolOutputLoader();
484
499
  enableToolOutput();
@@ -511,24 +526,15 @@ class Body:
511
526
  const last = elements[elements.length - 1];
512
527
  const contentEl = last.querySelector('.content');
513
528
  if (contentEl) {
514
- contentEl.innerHTML = '';
529
+ contentEl.replaceChildren();
515
530
  }
516
531
  }
517
532
  }
518
533
  function showToolOutputLoader() {
519
- return; // disabled
520
- const elements = document.querySelectorAll('.msg-bot');
521
- if (elements.length > 0) {
522
- const last = elements[elements.length - 1];
523
- const contentEl = last.querySelector('.spinner');
524
- if (contentEl) {
525
- contentEl.style.display = 'inline-block';
526
- }
527
- }
534
+ return;
528
535
  }
529
536
  function hideToolOutputLoader() {
530
537
  const elements = document.querySelectorAll('.msg-bot');
531
- // hide all loaders
532
538
  if (elements.length > 0) {
533
539
  elements.forEach(function(element) {
534
540
  const contentEl = element.querySelector('.spinner');
@@ -577,19 +583,19 @@ class Body:
577
583
  }
578
584
  }
579
585
  function replaceLive(content) {
580
- const element = document.getElementById('_append_live_');
586
+ const element = els.appendLive || document.getElementById('_append_live_');
581
587
  if (element) {
582
588
  if (element.classList.contains('hidden')) {
583
589
  element.classList.remove('hidden');
584
590
  element.classList.add('visible');
585
591
  }
586
592
  element.innerHTML = sanitize(content);
593
+ highlightCode(true, element);
594
+ scheduleScroll();
587
595
  }
588
- highlightCode();
589
- scrollToBottom();
590
596
  }
591
597
  function updateFooter(content) {
592
- const element = document.getElementById('_footer_');
598
+ const element = els.footer || document.getElementById('_footer_');
593
599
  if (element) {
594
600
  element.innerHTML = content;
595
601
  }
@@ -597,39 +603,38 @@ class Body:
597
603
  function clearNodes() {
598
604
  prevScroll = 0;
599
605
  clearStreamBefore();
600
- const element = document.getElementById('_nodes_');
606
+ const element = els.nodes || document.getElementById('_nodes_');
601
607
  if (element) {
602
- element.innerHTML = '';
608
+ element.replaceChildren();
603
609
  element.classList.add('empty_list');
604
610
  }
605
611
  }
606
612
  function clearInput() {
607
- const element = document.getElementById('_append_input_');
613
+ const element = els.appendInput || document.getElementById('_append_input_');
608
614
  if (element) {
609
- element.innerHTML = '';
615
+ element.replaceChildren();
610
616
  }
611
617
  }
612
618
  function clearOutput() {
613
619
  clearStreamBefore();
614
620
  domLastCodeBlock = null;
615
621
  domLastParagraphBlock = null;
616
- const element = document.getElementById('_append_output_');
622
+ const element = els.appendOutput || document.getElementById('_append_output_');
617
623
  if (element) {
618
624
  element.replaceChildren();
619
625
  }
620
626
  }
621
627
  function clearLive() {
622
- const element = document.getElementById('_append_live_');
628
+ const element = els.appendLive || document.getElementById('_append_live_');
623
629
  if (element) {
624
630
  if (element.classList.contains('visible')) {
625
631
  element.classList.remove('visible');
626
632
  element.classList.add('hidden');
627
- // timeout to clear content
628
633
  setTimeout(function() {
629
- element.innerHTML = '';
634
+ element.replaceChildren();
630
635
  }, 1000);
631
636
  } else {
632
- element.innerHTML = '';
637
+ element.replaceChildren();
633
638
  }
634
639
  }
635
640
  }
@@ -670,24 +675,25 @@ class Body:
670
675
  }
671
676
  }
672
677
  function updateCSS(styles) {
673
- const style = document.createElement('style');
674
- style.innerHTML = styles;
675
- const oldStyle = document.querySelector('style');
676
- if (oldStyle) {
677
- oldStyle.remove();
678
+ let style = document.getElementById('app-style');
679
+ if (!style) {
680
+ style = document.createElement('style');
681
+ style.id = 'app-style';
682
+ document.head.appendChild(style);
678
683
  }
679
- document.head.appendChild(style);
684
+ style.textContent = styles;
680
685
  }
681
- function restoreCollapsedCode() {
682
- const codeWrappers = document.querySelectorAll('.code-wrapper');
686
+ function restoreCollapsedCode(root) {
687
+ const scope = root || document;
688
+ const codeWrappers = scope.querySelectorAll('.code-wrapper');
683
689
  codeWrappers.forEach(function(wrapper) {
684
690
  const index = wrapper.getAttribute('data-index');
685
691
  const localeCollapse = wrapper.getAttribute('data-locale-collapse');
686
692
  const localeExpand = wrapper.getAttribute('data-locale-expand');
687
- const source = wrapper.querySelector('code');
693
+ const source = wrapper.querySelector('code');
688
694
  if (source && collapsed_idx.includes(index)) {
689
695
  source.style.display = 'none';
690
- const collapseBtn = wrapper.querySelector('.code-header-collapse');
696
+ const collapseBtn = wrapper.querySelector('.code-header-collapse');
691
697
  if (collapseBtn) {
692
698
  const collapseSpan = collapseBtn.querySelector('span');
693
699
  if (collapseSpan) {
@@ -734,10 +740,10 @@ class Body:
734
740
  function setScrollPosition(pos) {
735
741
  window.scrollTo(0, pos);
736
742
  prevScroll = parseInt(pos);
737
- }
743
+ }
738
744
  function showLoading() {
739
745
  hideTips();
740
- const el = document.getElementById('_loader_');
746
+ const el = els.loader || document.getElementById('_loader_');
741
747
  if (el) {
742
748
  if (el.classList.contains('hidden')) {
743
749
  el.classList.remove('hidden');
@@ -746,7 +752,7 @@ class Body:
746
752
  }
747
753
  }
748
754
  function hideLoading() {
749
- const el = document.getElementById('_loader_');
755
+ const el = els.loader || document.getElementById('_loader_');
750
756
  if (el) {
751
757
  if (el.classList.contains('visible')) {
752
758
  el.classList.remove('visible');
@@ -755,7 +761,15 @@ class Body:
755
761
  }
756
762
  }
757
763
  document.addEventListener('DOMContentLoaded', function() {
758
- const container = document.getElementById('container');
764
+ new QWebChannel(qt.webChannelTransport, function (channel) {
765
+ bridge = channel.objects.bridge;
766
+ bridge.chunk.connect((name, html, chunk, replace, isCode) => {
767
+ appendStream(name, html, chunk, replace, isCode);
768
+ });
769
+ if (bridge.js_ready) bridge.js_ready();
770
+ });
771
+ initDomRefs();
772
+ const container = els.container;
759
773
  function addClassToMsg(id, className) {
760
774
  const msgElement = document.getElementById('msg-bot-' + id);
761
775
  if (msgElement) {
@@ -773,7 +787,7 @@ class Body:
773
787
  const id = event.target.getAttribute('data-id');
774
788
  addClassToMsg(id, 'msg-highlight');
775
789
  }
776
- });
790
+ });
777
791
  container.addEventListener('mouseout', function(event) {
778
792
  if (event.target.classList.contains('action-img')) {
779
793
  const id = event.target.getAttribute('data-id');
@@ -781,7 +795,6 @@ class Body:
781
795
  }
782
796
  });
783
797
  container.addEventListener('click', function(event) {
784
- // btn copy
785
798
  const copyButton = event.target.closest('.code-header-copy');
786
799
  if (copyButton) {
787
800
  event.preventDefault();
@@ -799,9 +812,8 @@ class Body:
799
812
  copySpan.textContent = localeCopy;
800
813
  }, 1000);
801
814
  }
802
- }
803
- }
804
- // btn run
815
+ }
816
+ }
805
817
  const runButton = event.target.closest('.code-header-run');
806
818
  if (runButton) {
807
819
  event.preventDefault();
@@ -810,9 +822,8 @@ class Body:
810
822
  if (source) {
811
823
  const text = source.textContent || source.innerText;
812
824
  bridgeRunCode(text);
813
- }
814
- }
815
- // btn preview
825
+ }
826
+ }
816
827
  const previewButton = event.target.closest('.code-header-preview');
817
828
  if (previewButton) {
818
829
  event.preventDefault();
@@ -821,9 +832,8 @@ class Body:
821
832
  if (source) {
822
833
  const text = source.textContent || source.innerText;
823
834
  bridgePreviewCode(text);
824
- }
835
+ }
825
836
  }
826
- // btn collapse
827
837
  const collapseButton = event.target.closest('.code-header-collapse');
828
838
  if (collapseButton) {
829
839
  event.preventDefault();
@@ -868,7 +878,7 @@ class Body:
868
878
  <div id="_nodes_" class="nodes empty_list"></div>
869
879
  <div id="_append_input_" class="append_input"></div>
870
880
  <div id="_append_output_before_" class="append_output"></div>
871
- <div id="_append_output_" class="append_output"></div>
881
+ <div id="_append_output_" class="append_output"></div>
872
882
  <div id="_append_live_" class="append_live hidden"></div>
873
883
  <div id="_footer_" class="footer"></div>
874
884
  <div id="_loader_" class="loader-global hidden">
@@ -977,7 +987,7 @@ class Body:
977
987
  theme_css = self.window.controller.theme.markdown.get_web_css().replace('%fonts%', fonts_path)
978
988
  parts = [self._SPINNER, theme_css]
979
989
  parts.append("pre { color: #fff; }" if syntax_style in self._syntax_dark else "pre { color: #000; }")
980
- parts.append(self.highlight.get_style_defs()) # highlight style
990
+ parts.append(self.highlight.get_style_defs())
981
991
  return "\n".join(parts)
982
992
 
983
993
  def prepare_action_icons(self, ctx: CtxItem) -> str:
@@ -1005,32 +1015,26 @@ class Body:
1005
1015
  cid = ctx.id
1006
1016
  t = trans
1007
1017
 
1008
- # audio read
1009
1018
  icons.append(
1010
1019
  f'<a href="extra-audio-read:{cid}" class="action-icon" data-id="{cid}" role="button">'
1011
1020
  f'<span class="cmd">{self.get_icon("volume", t("ctx.extra.audio"), ctx)}</span></a>'
1012
1021
  )
1013
- # copy ctx
1014
1022
  icons.append(
1015
1023
  f'<a href="extra-copy:{cid}" class="action-icon" data-id="{cid}" role="button">'
1016
1024
  f'<span class="cmd">{self.get_icon("copy", t("ctx.extra.copy"), ctx)}</span></a>'
1017
1025
  )
1018
- # regen link
1019
1026
  icons.append(
1020
1027
  f'<a href="extra-replay:{cid}" class="action-icon" data-id="{cid}" role="button">'
1021
1028
  f'<span class="cmd">{self.get_icon("reload", t("ctx.extra.reply"), ctx)}</span></a>'
1022
1029
  )
1023
- # edit link
1024
1030
  icons.append(
1025
1031
  f'<a href="extra-edit:{cid}" class="action-icon edit-icon" data-id="{cid}" role="button">'
1026
1032
  f'<span class="cmd">{self.get_icon("edit", t("ctx.extra.edit"), ctx)}</span></a>'
1027
1033
  )
1028
- # delete link
1029
1034
  icons.append(
1030
1035
  f'<a href="extra-delete:{cid}" class="action-icon edit-icon" data-id="{cid}" role="button">'
1031
1036
  f'<span class="cmd">{self.get_icon("delete", t("ctx.extra.delete"), ctx)}</span></a>'
1032
1037
  )
1033
- # join link
1034
1038
  if not self.window.core.ctx.is_first_item(cid):
1035
1039
  icons.append(
1036
1040
  f'<a href="extra-join:{cid}" class="action-icon edit-icon" data-id="{cid}" role="button">'
@@ -1173,20 +1177,18 @@ class Body:
1173
1177
 
1174
1178
  parts: List[str] = ['<div class="msg-extra">']
1175
1179
 
1176
- # single tool
1177
1180
  if "plugin" in extra:
1178
1181
  event = Event(Event.TOOL_OUTPUT_RENDER, {
1179
1182
  'tool': extra["plugin"],
1180
1183
  'html': '',
1181
1184
  'multiple': False,
1182
- 'content': extra, # tool output
1185
+ 'content': extra,
1183
1186
  })
1184
1187
  event.ctx = ctx
1185
- self.window.dispatch(event, all=True) # handle by plugins
1188
+ self.window.dispatch(event, all=True)
1186
1189
  if event.data['html']:
1187
1190
  parts.append(f'<div class="tool-output-block">{event.data["html"]}</div>')
1188
1191
 
1189
- # multiple tools, list
1190
1192
  elif "tool_output" in extra and isinstance(extra["tool_output"], list):
1191
1193
  for tool in extra["tool_output"]:
1192
1194
  if "plugin" not in tool:
@@ -1195,7 +1197,7 @@ class Body:
1195
1197
  'tool': tool["plugin"],
1196
1198
  'html': '',
1197
1199
  'multiple': True,
1198
- 'content': tool, # tool output[]
1200
+ 'content': tool,
1199
1201
  })
1200
1202
  event.ctx = ctx
1201
1203
  self.window.dispatch(event, all=True)
@@ -1237,11 +1239,11 @@ class Body:
1237
1239
  if self.is_timestamp_enabled():
1238
1240
  classes.append("display-timestamp")
1239
1241
  classes_str = f' class="{" ".join(classes)}"' if classes else ""
1240
- styles_css = self.prepare_styles() # CSS string
1241
- tips_json = self.get_all_tips() # JSON string
1242
+ styles_css = self.prepare_styles()
1243
+ tips_json = self.get_all_tips()
1242
1244
 
1243
1245
  return ''.join((
1244
- self. _HTML_P0,
1246
+ self._HTML_P0,
1245
1247
  styles_css,
1246
1248
  self._HTML_P1,
1247
1249
  str(pid),
@@ -1250,4 +1252,4 @@ class Body:
1250
1252
  self._HTML_P3,
1251
1253
  classes_str,
1252
1254
  self._HTML_P4,
1253
- ))
1255
+ ))