pygpt-net 2.6.5__py3-none-any.whl → 2.6.7__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 +8 -0
- pygpt_net/__init__.py +1 -1
- pygpt_net/app.py +6 -1
- pygpt_net/controller/chat/stream.py +2 -1
- pygpt_net/core/render/web/body.py +51 -39
- pygpt_net/core/render/web/renderer.py +54 -80
- pygpt_net/data/config/config.json +2 -2
- pygpt_net/data/config/models.json +2 -2
- pygpt_net/ui/main.py +13 -1
- pygpt_net/ui/widget/textarea/web.py +144 -35
- pygpt_net/utils.py +8 -1
- {pygpt_net-2.6.5.dist-info → pygpt_net-2.6.7.dist-info}/METADATA +10 -2
- {pygpt_net-2.6.5.dist-info → pygpt_net-2.6.7.dist-info}/RECORD +16 -16
- {pygpt_net-2.6.5.dist-info → pygpt_net-2.6.7.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.5.dist-info → pygpt_net-2.6.7.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.5.dist-info → pygpt_net-2.6.7.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt
CHANGED
pygpt_net/__init__.py
CHANGED
|
@@ -13,7 +13,7 @@ __author__ = "Marcin Szczygliński"
|
|
|
13
13
|
__copyright__ = "Copyright 2025, Marcin Szczygliński"
|
|
14
14
|
__credits__ = ["Marcin Szczygliński"]
|
|
15
15
|
__license__ = "MIT"
|
|
16
|
-
__version__ = "2.6.
|
|
16
|
+
__version__ = "2.6.7"
|
|
17
17
|
__build__ = "2025-08-16"
|
|
18
18
|
__maintainer__ = "Marcin Szczygliński"
|
|
19
19
|
__github__ = "https://github.com/szczyglis-dev/py-gpt"
|
pygpt_net/app.py
CHANGED
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.08.16 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -22,8 +22,13 @@ if platform.system() == 'Windows':
|
|
|
22
22
|
# fix ffmpeg bug: [SWR] Output channel layout "" is invalid or unsupported.
|
|
23
23
|
os.environ['QT_MEDIA_BACKEND'] = 'windows'
|
|
24
24
|
|
|
25
|
+
elif platform.system() == 'Linux':
|
|
26
|
+
os.environ.setdefault("MALLOC_ARENA_MAX", "2") # 2 arenas
|
|
27
|
+
os.environ.setdefault("MALLOC_TRIM_THRESHOLD_", "131072") # 128 KiB
|
|
28
|
+
|
|
25
29
|
# enable debug logging
|
|
26
30
|
# os.environ["QT_LOGGING_RULES"] = "*.debug=true"
|
|
31
|
+
# os.environ["QTWEBENGINE_REMOTE_DEBUGGING"] = "9222"
|
|
27
32
|
|
|
28
33
|
_original_open = builtins.open
|
|
29
34
|
|
|
@@ -6,10 +6,11 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.08.16 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import base64
|
|
13
|
+
import gc
|
|
13
14
|
import io
|
|
14
15
|
from typing import Optional, Literal
|
|
15
16
|
|
|
@@ -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.
|
|
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_');
|
|
@@ -68,6 +67,7 @@ class Body:
|
|
|
68
67
|
let pendingHighlightRoot = null;
|
|
69
68
|
let pendingHighlightMath = false;
|
|
70
69
|
let scrollScheduled = false;
|
|
70
|
+
const AMP_LT_GT = /&(lt|gt);/g;
|
|
71
71
|
|
|
72
72
|
history.scrollRestoration = "manual";
|
|
73
73
|
document.addEventListener('keydown', function(event) {
|
|
@@ -213,7 +213,8 @@ class Body:
|
|
|
213
213
|
prevScroll = el.scrollHeight;
|
|
214
214
|
}
|
|
215
215
|
function sanitize(content) {
|
|
216
|
-
|
|
216
|
+
if (content.indexOf('&') === -1) return content;
|
|
217
|
+
return content.replace(AMP_LT_GT, '&$1;'); // &lt; -> <, &gt; -> >
|
|
217
218
|
}
|
|
218
219
|
function appendToInput(content) {
|
|
219
220
|
const element = els.appendInput || document.getElementById('_append_input_');
|
|
@@ -350,11 +351,22 @@ class Body:
|
|
|
350
351
|
function endStream() {
|
|
351
352
|
clearOutput();
|
|
352
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
|
+
}
|
|
353
367
|
function appendStream(name_header, content, chunk, replace = false, is_code_block = false) {
|
|
354
368
|
hideTips();
|
|
355
369
|
const element = getStreamContainer();
|
|
356
|
-
let doHighlight = true;
|
|
357
|
-
let doMath = true;
|
|
358
370
|
let msg;
|
|
359
371
|
if (element) {
|
|
360
372
|
let box = element.querySelector('.msg-box');
|
|
@@ -378,7 +390,15 @@ class Body:
|
|
|
378
390
|
}
|
|
379
391
|
if (msg) {
|
|
380
392
|
if (replace) {
|
|
381
|
-
msg.
|
|
393
|
+
msg.replaceChildren();
|
|
394
|
+
if (content) {
|
|
395
|
+
msg.insertAdjacentHTML('afterbegin', sanitize(content));
|
|
396
|
+
}
|
|
397
|
+
let doMath = true;
|
|
398
|
+
if (!is_code_block) {
|
|
399
|
+
doMath = false;
|
|
400
|
+
}
|
|
401
|
+
highlightCode(doMath, msg);
|
|
382
402
|
domLastCodeBlock = null;
|
|
383
403
|
domLastParagraphBlock = null;
|
|
384
404
|
} else {
|
|
@@ -393,47 +413,31 @@ class Body:
|
|
|
393
413
|
}
|
|
394
414
|
}
|
|
395
415
|
if (lastCodeBlock) {
|
|
396
|
-
lastCodeBlock.insertAdjacentHTML('beforeend', chunk);
|
|
416
|
+
lastCodeBlock.insertAdjacentHTML('beforeend', sanitize(chunk));
|
|
397
417
|
domLastCodeBlock = lastCodeBlock;
|
|
398
|
-
doHighlight = false;
|
|
399
418
|
} else {
|
|
400
|
-
msg.insertAdjacentHTML('beforeend', chunk);
|
|
419
|
+
msg.insertAdjacentHTML('beforeend', sanitize(chunk));
|
|
401
420
|
domLastCodeBlock = null;
|
|
402
421
|
}
|
|
403
|
-
doMath = false;
|
|
404
422
|
} else {
|
|
405
423
|
domLastCodeBlock = null;
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (lastParagraphBlock) {
|
|
417
|
-
domLastParagraphBlock = lastParagraphBlock;
|
|
418
|
-
lastParagraphBlock.insertAdjacentHTML('beforeend', chunk);
|
|
419
|
-
} else {
|
|
420
|
-
domLastParagraphBlock = null;
|
|
421
|
-
msg.insertAdjacentHTML('beforeend', chunk);
|
|
422
|
-
}
|
|
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;
|
|
423
432
|
} else {
|
|
424
|
-
|
|
425
|
-
msg.
|
|
433
|
+
msg.insertAdjacentHTML('beforeend', sanitize(chunk));
|
|
434
|
+
const last = msg.lastElementChild;
|
|
435
|
+
domLastParagraphBlock = (last && last.tagName === 'P') ? last : null;
|
|
426
436
|
}
|
|
427
|
-
doHighlight = false;
|
|
428
437
|
}
|
|
429
438
|
}
|
|
430
439
|
}
|
|
431
440
|
}
|
|
432
|
-
if (replace) {
|
|
433
|
-
if (doHighlight) {
|
|
434
|
-
highlightCode(doMath, msg);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
441
|
scheduleScroll(true);
|
|
438
442
|
}
|
|
439
443
|
function replaceOutput(name_header, content) {
|
|
@@ -461,7 +465,8 @@ class Body:
|
|
|
461
465
|
msg = box.querySelector('.msg');
|
|
462
466
|
}
|
|
463
467
|
if (msg) {
|
|
464
|
-
msg.
|
|
468
|
+
msg.replaceChildren();
|
|
469
|
+
msg.insertAdjacentHTML('afterbegin', sanitize(content));
|
|
465
470
|
highlightCode(true, msg);
|
|
466
471
|
scheduleScroll();
|
|
467
472
|
}
|
|
@@ -756,6 +761,13 @@ class Body:
|
|
|
756
761
|
}
|
|
757
762
|
}
|
|
758
763
|
document.addEventListener('DOMContentLoaded', function() {
|
|
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
|
+
});
|
|
759
771
|
initDomRefs();
|
|
760
772
|
const container = els.container;
|
|
761
773
|
function addClassToMsg(id, className) {
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.08.16 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
@@ -33,6 +33,16 @@ from pygpt_net.core.events import RenderEvent
|
|
|
33
33
|
class Renderer(BaseRenderer):
|
|
34
34
|
NODE_INPUT = 0
|
|
35
35
|
NODE_OUTPUT = 1
|
|
36
|
+
ENDINGS_CODE = (
|
|
37
|
+
"</code></pre></div>",
|
|
38
|
+
"</code></pre></div><br/>",
|
|
39
|
+
"</code></pre></div><br>"
|
|
40
|
+
)
|
|
41
|
+
ENDINGS_LIST = (
|
|
42
|
+
"</ul>",
|
|
43
|
+
"</ol>",
|
|
44
|
+
"</li>"
|
|
45
|
+
)
|
|
36
46
|
|
|
37
47
|
def __init__(self, window=None):
|
|
38
48
|
super(Renderer, self).__init__(window)
|
|
@@ -71,7 +81,7 @@ class Renderer(BaseRenderer):
|
|
|
71
81
|
self.reset(meta)
|
|
72
82
|
self.parser.reset()
|
|
73
83
|
try:
|
|
74
|
-
node.page().runJavaScript(
|
|
84
|
+
node.page().runJavaScript("if (typeof window.prepare !== 'undefined') prepare();")
|
|
75
85
|
except Exception as e:
|
|
76
86
|
pass
|
|
77
87
|
|
|
@@ -167,7 +177,7 @@ class Renderer(BaseRenderer):
|
|
|
167
177
|
node = self.get_output_node_by_pid(pid)
|
|
168
178
|
try:
|
|
169
179
|
node.page().runJavaScript(
|
|
170
|
-
|
|
180
|
+
"if (typeof window.showLoading !== 'undefined') showLoading();")
|
|
171
181
|
except Exception as e:
|
|
172
182
|
pass
|
|
173
183
|
|
|
@@ -177,7 +187,7 @@ class Renderer(BaseRenderer):
|
|
|
177
187
|
if node is not None:
|
|
178
188
|
try:
|
|
179
189
|
node.page().runJavaScript(
|
|
180
|
-
|
|
190
|
+
"if (typeof window.hideLoading !== 'undefined') hideLoading();")
|
|
181
191
|
except Exception as e:
|
|
182
192
|
pass
|
|
183
193
|
|
|
@@ -187,7 +197,7 @@ class Renderer(BaseRenderer):
|
|
|
187
197
|
if node is not None:
|
|
188
198
|
try:
|
|
189
199
|
node.page().runJavaScript(
|
|
190
|
-
|
|
200
|
+
"if (typeof window.hideLoading !== 'undefined') hideLoading();")
|
|
191
201
|
except Exception as e:
|
|
192
202
|
pass
|
|
193
203
|
|
|
@@ -410,7 +420,7 @@ class Renderer(BaseRenderer):
|
|
|
410
420
|
output = ctx.output
|
|
411
421
|
if isinstance(ctx.extra, dict) and ctx.extra.get("output"):
|
|
412
422
|
if self.window.core.config.get("llama.idx.chat.agent.render.all", False):
|
|
413
|
-
output = "__agent_begin__
|
|
423
|
+
output = f"__agent_begin__{ctx.output}__agent_end__{ctx.extra['output']}"
|
|
414
424
|
else:
|
|
415
425
|
output = ctx.extra["output"]
|
|
416
426
|
else:
|
|
@@ -450,21 +460,20 @@ class Renderer(BaseRenderer):
|
|
|
450
460
|
|
|
451
461
|
name_header_str = self.get_name_header(ctx)
|
|
452
462
|
self.update_names(meta, ctx)
|
|
453
|
-
|
|
454
|
-
|
|
463
|
+
text_chunk = text_chunk if isinstance(text_chunk, str) else str(text_chunk)
|
|
464
|
+
text_chunk = text_chunk.translate({ord('<'): '<', ord('>'): '>'})
|
|
455
465
|
|
|
456
466
|
if begin:
|
|
457
|
-
debug = ""
|
|
458
467
|
if self.is_debug():
|
|
459
468
|
debug = self.append_debug(ctx, pid, "stream")
|
|
460
|
-
|
|
461
|
-
|
|
469
|
+
if debug:
|
|
470
|
+
text_chunk = debug + text_chunk
|
|
462
471
|
pctx.clear() # reset buffer
|
|
463
472
|
pctx.is_cmd = False # reset command flag
|
|
464
473
|
self.clear_chunks_output(pid)
|
|
465
474
|
self.prev_chunk_replace = False
|
|
466
475
|
|
|
467
|
-
pctx.append_buffer(
|
|
476
|
+
pctx.append_buffer(text_chunk)
|
|
468
477
|
|
|
469
478
|
buffer = pctx.buffer
|
|
470
479
|
if has_unclosed_code_tag(buffer):
|
|
@@ -473,17 +482,13 @@ class Renderer(BaseRenderer):
|
|
|
473
482
|
buffer_to_parse = buffer
|
|
474
483
|
|
|
475
484
|
html = self.parser.parse(buffer_to_parse)
|
|
476
|
-
is_code_block = html.endswith(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
"</code></pre></div><br>"
|
|
480
|
-
))
|
|
481
|
-
is_list = html.endswith(("</ul>", "</ol>", "</li>"))
|
|
482
|
-
is_newline = ("\n" in raw_chunk) or buffer.endswith("\n") or is_code_block
|
|
485
|
+
is_code_block = html.endswith(self.ENDINGS_CODE)
|
|
486
|
+
is_list = html.endswith(self.ENDINGS_LIST)
|
|
487
|
+
is_newline = ("\n" in text_chunk) or buffer.endswith("\n") or is_code_block
|
|
483
488
|
force_replace = False
|
|
484
489
|
if self.prev_chunk_newline:
|
|
485
490
|
force_replace = True
|
|
486
|
-
if "\n" in
|
|
491
|
+
if "\n" in text_chunk:
|
|
487
492
|
self.prev_chunk_newline = True
|
|
488
493
|
else:
|
|
489
494
|
self.prev_chunk_newline = False
|
|
@@ -493,36 +498,25 @@ class Renderer(BaseRenderer):
|
|
|
493
498
|
replace_bool = True
|
|
494
499
|
if is_code_block:
|
|
495
500
|
# don't replace if it is a code block
|
|
496
|
-
if "\n" not in
|
|
501
|
+
if "\n" not in text_chunk:
|
|
497
502
|
# if there is no newline in raw_chunk, then don't replace
|
|
498
503
|
replace_bool = False
|
|
499
504
|
|
|
500
|
-
code_block_arg = "true" if is_code_block else "false"
|
|
501
505
|
if not is_code_block:
|
|
502
|
-
|
|
506
|
+
text_chunk = text_chunk.replace("\n", "<br/>")
|
|
503
507
|
else:
|
|
504
|
-
|
|
505
|
-
if self.prev_chunk_replace and not has_unclosed_code_tag(raw_chunk):
|
|
508
|
+
if self.prev_chunk_replace and not has_unclosed_code_tag(text_chunk):
|
|
506
509
|
# if previous chunk was replaced and current is code block, then add \n to chunk
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
escaped_chunk = json.dumps(out_chunk)
|
|
510
|
-
if name_header_str:
|
|
511
|
-
name_header = json.dumps(name_header_str)
|
|
512
|
-
else:
|
|
513
|
-
name_header = '""'
|
|
514
|
-
replace = "true" if replace_bool else "false"
|
|
515
|
-
|
|
516
|
-
if replace_bool:
|
|
517
|
-
escaped_buffer = json.dumps(html)
|
|
518
|
-
else:
|
|
519
|
-
escaped_buffer = '""'
|
|
510
|
+
text_chunk = "".join(("\n", text_chunk)) # add newline to chunk
|
|
520
511
|
|
|
521
512
|
self.prev_chunk_replace = replace_bool
|
|
522
|
-
|
|
523
513
|
try:
|
|
524
|
-
self.get_output_node(meta).page().
|
|
525
|
-
|
|
514
|
+
self.get_output_node(meta).page().bridge.chunk.emit(
|
|
515
|
+
name_header_str or "",
|
|
516
|
+
html if replace_bool else "",
|
|
517
|
+
text_chunk if not replace_bool else "",
|
|
518
|
+
bool(replace_bool),
|
|
519
|
+
bool(is_code_block),
|
|
526
520
|
)
|
|
527
521
|
except Exception as e:
|
|
528
522
|
pass
|
|
@@ -546,7 +540,7 @@ class Renderer(BaseRenderer):
|
|
|
546
540
|
self.prev_chunk_newline = False
|
|
547
541
|
try:
|
|
548
542
|
self.get_output_node(meta).page().runJavaScript(
|
|
549
|
-
|
|
543
|
+
"nextStream();")
|
|
550
544
|
except Exception as e:
|
|
551
545
|
pass
|
|
552
546
|
|
|
@@ -780,7 +774,7 @@ class Renderer(BaseRenderer):
|
|
|
780
774
|
except Exception as e:
|
|
781
775
|
pass
|
|
782
776
|
if files_html:
|
|
783
|
-
html_parts.append("<br
|
|
777
|
+
html_parts.append("<br/><br/>".join(files_html))
|
|
784
778
|
|
|
785
779
|
c = len(ctx.urls)
|
|
786
780
|
if c > 0:
|
|
@@ -797,7 +791,7 @@ class Renderer(BaseRenderer):
|
|
|
797
791
|
except Exception as e:
|
|
798
792
|
pass
|
|
799
793
|
if urls_html:
|
|
800
|
-
html_parts.append("<br
|
|
794
|
+
html_parts.append("<br/><br/>".join(urls_html))
|
|
801
795
|
|
|
802
796
|
if self.window.core.config.get('ctx.sources'):
|
|
803
797
|
if ctx.doc_ids is not None and len(ctx.doc_ids) > 0:
|
|
@@ -814,7 +808,7 @@ class Renderer(BaseRenderer):
|
|
|
814
808
|
else:
|
|
815
809
|
escaped_html = json.dumps(html)
|
|
816
810
|
try:
|
|
817
|
-
self.get_output_node(meta).page().runJavaScript("appendExtra('{}',{});"
|
|
811
|
+
self.get_output_node(meta).page().runJavaScript(f"appendExtra('{ctx.id}',{escaped_html});")
|
|
818
812
|
except Exception as e:
|
|
819
813
|
pass
|
|
820
814
|
|
|
@@ -843,7 +837,7 @@ class Renderer(BaseRenderer):
|
|
|
843
837
|
if timestamp is not None:
|
|
844
838
|
ts = datetime.fromtimestamp(timestamp)
|
|
845
839
|
hour = ts.strftime("%H:%M:%S")
|
|
846
|
-
text = '<span class="ts">{}: </span>{}'
|
|
840
|
+
text = f'<span class="ts">{hour}: </span>{text}'
|
|
847
841
|
return text
|
|
848
842
|
|
|
849
843
|
def reset(
|
|
@@ -1031,7 +1025,7 @@ class Renderer(BaseRenderer):
|
|
|
1031
1025
|
self.helpers.format_user_text(html),
|
|
1032
1026
|
type=self.NODE_INPUT
|
|
1033
1027
|
)
|
|
1034
|
-
html = "<p>
|
|
1028
|
+
html = f"<p>{content}</p>"
|
|
1035
1029
|
html = self.helpers.post_format_text(html)
|
|
1036
1030
|
name = self.pids[pid].name_user
|
|
1037
1031
|
|
|
@@ -1086,7 +1080,7 @@ class Renderer(BaseRenderer):
|
|
|
1086
1080
|
(len(ctx.cmds) > 0 or (ctx.extra_ctx is not None and len(ctx.extra_ctx) > 0))
|
|
1087
1081
|
)
|
|
1088
1082
|
pid = self.get_or_create_pid(meta)
|
|
1089
|
-
msg_id = "msg-bot-
|
|
1083
|
+
msg_id = f"msg-bot-{ctx.id}" if ctx is not None else ""
|
|
1090
1084
|
html = self.helpers.pre_format_text(html)
|
|
1091
1085
|
html = self.parser.parse(html)
|
|
1092
1086
|
html = self.append_timestamp(ctx, html, type=self.NODE_OUTPUT)
|
|
@@ -1099,7 +1093,7 @@ class Renderer(BaseRenderer):
|
|
|
1099
1093
|
output_class = "display:none"
|
|
1100
1094
|
cmd_icon = f'<img src="{self._file_prefix}{self._icon_expand}" width="25" height="25" valign="middle">'
|
|
1101
1095
|
expand_btn = (
|
|
1102
|
-
f"<span class='toggle-cmd-output' onclick='toggleToolOutput({
|
|
1096
|
+
f"<span class='toggle-cmd-output' onclick='toggleToolOutput({ctx.id});' title='{trans('action.cmd.expand')}' "
|
|
1103
1097
|
f"role='button'>{cmd_icon}</span>"
|
|
1104
1098
|
)
|
|
1105
1099
|
|
|
@@ -1116,15 +1110,12 @@ class Renderer(BaseRenderer):
|
|
|
1116
1110
|
and isinstance(ctx.extra, dict) and "agent_step" in ctx.extra:
|
|
1117
1111
|
tool_output = self.helpers.format_cmd_text(str(ctx.input))
|
|
1118
1112
|
else:
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
ctx.output.strip().endswith("}</tool>") or
|
|
1126
|
-
len(ctx.cmds) > 0
|
|
1127
|
-
)
|
|
1113
|
+
out = (getattr(ctx, "output", "") or "")
|
|
1114
|
+
cmds = getattr(ctx, "cmds", ())
|
|
1115
|
+
if next_ctx is None and (
|
|
1116
|
+
cmds
|
|
1117
|
+
or out.startswith(('<tool>{"cmd"', '<tool>{"cmd"'))
|
|
1118
|
+
or out.rstrip().endswith(('}</tool>', '}</tool>'))
|
|
1128
1119
|
):
|
|
1129
1120
|
spinner_class = "" if ctx.live else "display:none"
|
|
1130
1121
|
spinner = (
|
|
@@ -1196,7 +1187,7 @@ class Renderer(BaseRenderer):
|
|
|
1196
1187
|
|
|
1197
1188
|
if not output_name and not avatar_html:
|
|
1198
1189
|
return ""
|
|
1199
|
-
return "<div class=\"name-header name-bot\">
|
|
1190
|
+
return f"<div class=\"name-header name-bot\">{avatar_html}{output_name}</div>"
|
|
1200
1191
|
|
|
1201
1192
|
def flush_output(
|
|
1202
1193
|
self,
|
|
@@ -1234,7 +1225,6 @@ class Renderer(BaseRenderer):
|
|
|
1234
1225
|
return
|
|
1235
1226
|
|
|
1236
1227
|
html = self.body.get_html(pid)
|
|
1237
|
-
self.pids[pid].document = html
|
|
1238
1228
|
node = self.get_output_node_by_pid(pid)
|
|
1239
1229
|
if node is not None:
|
|
1240
1230
|
node.setHtml(html, baseUrl="file://")
|
|
@@ -1253,9 +1243,12 @@ class Renderer(BaseRenderer):
|
|
|
1253
1243
|
return
|
|
1254
1244
|
html = self.body.get_html(pid)
|
|
1255
1245
|
self.pids[pid].loaded = False
|
|
1256
|
-
self.pids[pid].document = html
|
|
1257
1246
|
node = self.get_output_node_by_pid(pid)
|
|
1258
1247
|
if node is not None:
|
|
1248
|
+
# hard reset
|
|
1249
|
+
# old_view = node
|
|
1250
|
+
# new_view = old_view.hard_reset()
|
|
1251
|
+
# self.window.ui.nodes['output'][pid] = new_view
|
|
1259
1252
|
node.resetPage()
|
|
1260
1253
|
node.setHtml(html, baseUrl="file://")
|
|
1261
1254
|
|
|
@@ -1291,25 +1284,6 @@ class Renderer(BaseRenderer):
|
|
|
1291
1284
|
"""
|
|
1292
1285
|
return self.window.ui.nodes['input']
|
|
1293
1286
|
|
|
1294
|
-
def get_document(
|
|
1295
|
-
self,
|
|
1296
|
-
plain: bool = False
|
|
1297
|
-
):
|
|
1298
|
-
"""
|
|
1299
|
-
Get document content (plain or HTML)
|
|
1300
|
-
|
|
1301
|
-
:param plain: True to convert to plain text
|
|
1302
|
-
:return: document content
|
|
1303
|
-
"""
|
|
1304
|
-
pid = self.window.core.ctx.container.get_active_pid()
|
|
1305
|
-
if pid is None:
|
|
1306
|
-
return ""
|
|
1307
|
-
if plain:
|
|
1308
|
-
return self.parser.to_plain_text(
|
|
1309
|
-
self.pids[pid].document.replace("<br>", "\n").replace("<br/>", "\n")
|
|
1310
|
-
)
|
|
1311
|
-
return self.pids[pid].document
|
|
1312
|
-
|
|
1313
1287
|
def remove_item(self, ctx: CtxItem):
|
|
1314
1288
|
"""
|
|
1315
1289
|
Remove item from output
|
pygpt_net/ui/main.py
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import os
|
|
13
13
|
|
|
14
|
-
from PySide6.QtCore import QTimer, Signal, Slot, QThreadPool, QEvent, Qt, QLoggingCategory
|
|
14
|
+
from PySide6.QtCore import QTimer, Signal, Slot, QThreadPool, QEvent, Qt, QLoggingCategory, QEventLoop
|
|
15
15
|
from PySide6.QtGui import QShortcut, QKeySequence
|
|
16
16
|
from qasync import QApplication
|
|
17
17
|
from PySide6.QtWidgets import QMainWindow
|
|
@@ -22,6 +22,7 @@ from pygpt_net.container import Container
|
|
|
22
22
|
from pygpt_net.controller import Controller
|
|
23
23
|
from pygpt_net.tools import Tools
|
|
24
24
|
from pygpt_net.ui import UI
|
|
25
|
+
from pygpt_net.ui.widget.textarea.web import ChatWebOutput
|
|
25
26
|
from pygpt_net.utils import get_app_meta
|
|
26
27
|
|
|
27
28
|
|
|
@@ -280,6 +281,17 @@ class MainWindow(QMainWindow, QtStyleTools):
|
|
|
280
281
|
event.ignore()
|
|
281
282
|
self.hide()
|
|
282
283
|
return
|
|
284
|
+
|
|
285
|
+
for view in self.findChildren(ChatWebOutput):
|
|
286
|
+
try:
|
|
287
|
+
view.on_delete()
|
|
288
|
+
except Exception:
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
for _ in range(6):
|
|
292
|
+
QApplication.sendPostedEvents(None, QEvent.DeferredDelete)
|
|
293
|
+
QApplication.processEvents(QEventLoop.AllEvents, 50)
|
|
294
|
+
|
|
283
295
|
self.shutdown()
|
|
284
296
|
event.accept()
|
|
285
297
|
|
|
@@ -6,15 +6,15 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.08.16 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
-
from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent
|
|
12
|
+
from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QTimer
|
|
13
13
|
from PySide6.QtWebChannel import QWebChannel
|
|
14
14
|
from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEnginePage, QWebEngineProfile
|
|
15
15
|
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
16
16
|
from PySide6.QtGui import QAction, QIcon
|
|
17
|
-
from PySide6.QtWidgets import QMenu
|
|
17
|
+
from PySide6.QtWidgets import QMenu, QApplication
|
|
18
18
|
|
|
19
19
|
from pygpt_net.core.events import RenderEvent
|
|
20
20
|
from pygpt_net.item.ctx import CtxMeta
|
|
@@ -45,24 +45,46 @@ class ChatWebOutput(QWebEngineView):
|
|
|
45
45
|
self.html_content = ""
|
|
46
46
|
self.meta = None
|
|
47
47
|
self.tab = None
|
|
48
|
+
self.setProperty('class', 'layout-output-web')
|
|
49
|
+
|
|
48
50
|
self._glwidget = None
|
|
49
51
|
self._glwidget_filter_installed = False
|
|
50
|
-
self.
|
|
52
|
+
self._profile = self._create_profile(parent=self)
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
self.setPage(CustomWebEnginePage(self.window, self, profile=self._profile))
|
|
55
|
+
|
|
56
|
+
def _detach_gl_event_filter(self):
|
|
57
|
+
"""
|
|
58
|
+
Detach OpenGL widget event filter if installed
|
|
59
|
+
"""
|
|
60
|
+
if self._glwidget and self._glwidget_filter_installed:
|
|
61
|
+
try:
|
|
62
|
+
self._glwidget.removeEventFilter(self)
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
self._glwidget = None
|
|
66
|
+
self._glwidget_filter_installed = False
|
|
67
|
+
|
|
68
|
+
def _release_profile_after_page(self, page, profile):
|
|
69
|
+
"""
|
|
70
|
+
Release profile after page is destroyed
|
|
71
|
+
|
|
72
|
+
:param page: QWebEnginePage - page to check
|
|
73
|
+
:param profile: QWebEngineProfile - profile to release
|
|
74
|
+
"""
|
|
75
|
+
if not profile or profile is QWebEngineProfile.defaultProfile():
|
|
76
|
+
return
|
|
77
|
+
if page:
|
|
78
|
+
try:
|
|
79
|
+
page.destroyed.connect(profile.deleteLater)
|
|
80
|
+
return
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
57
83
|
try:
|
|
58
|
-
|
|
59
|
-
prof.setHttpCacheMaximumSize(0)
|
|
60
|
-
prof.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
|
|
84
|
+
profile.deleteLater()
|
|
61
85
|
except Exception:
|
|
62
86
|
pass
|
|
63
87
|
|
|
64
|
-
self.setPage(CustomWebEnginePage(self.window, self, profile=prof))
|
|
65
|
-
|
|
66
88
|
def _teardown_page(self, page: QWebEnginePage):
|
|
67
89
|
"""
|
|
68
90
|
Teardown page to clean up resources
|
|
@@ -71,7 +93,6 @@ class ChatWebOutput(QWebEngineView):
|
|
|
71
93
|
"""
|
|
72
94
|
if not page:
|
|
73
95
|
return
|
|
74
|
-
|
|
75
96
|
try:
|
|
76
97
|
# detach the channel from the page to break JS<->Python references
|
|
77
98
|
page.setWebChannel(None)
|
|
@@ -81,29 +102,97 @@ class ChatWebOutput(QWebEngineView):
|
|
|
81
102
|
# bridge, channel, and signals have parent=page, so deleteLater of the page will clean them up
|
|
82
103
|
page.deleteLater()
|
|
83
104
|
|
|
105
|
+
def _create_profile(self, parent=None) -> QWebEngineProfile:
|
|
106
|
+
"""
|
|
107
|
+
Create a new QWebEngineProfile with off-the-record settings
|
|
108
|
+
|
|
109
|
+
:param parent: QWidget - parent widget
|
|
110
|
+
:return: QWebEngineProfile - new profile instance
|
|
111
|
+
"""
|
|
112
|
+
p = QWebEngineProfile(parent or self)
|
|
113
|
+
try:
|
|
114
|
+
p.setHttpCacheType(QWebEngineProfile.NoCache)
|
|
115
|
+
p.setHttpCacheMaximumSize(0)
|
|
116
|
+
p.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies)
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
return p
|
|
120
|
+
|
|
121
|
+
def hard_reset(self, focus_after=True):
|
|
122
|
+
"""
|
|
123
|
+
Hard reset of the view, creating a new instance of ChatWebOutput
|
|
124
|
+
|
|
125
|
+
:param focus_after: bool - whether to focus the new view after reset
|
|
126
|
+
"""
|
|
127
|
+
parent = self.parentWidget()
|
|
128
|
+
if parent is None or parent.layout() is None:
|
|
129
|
+
new_view = ChatWebOutput(self.window)
|
|
130
|
+
new_view.set_tab(self.tab)
|
|
131
|
+
new_view.set_meta(self.meta)
|
|
132
|
+
QTimer.singleShot(0, self.on_delete)
|
|
133
|
+
return new_view
|
|
134
|
+
|
|
135
|
+
layout = parent.layout()
|
|
136
|
+
idx = layout.indexOf(self)
|
|
137
|
+
if idx < 0:
|
|
138
|
+
idx = layout.count()
|
|
139
|
+
|
|
140
|
+
new_view = ChatWebOutput(self.window)
|
|
141
|
+
new_view.setProperty('class', self.property('class') or 'layout-output-web')
|
|
142
|
+
new_view.set_tab(self.tab)
|
|
143
|
+
new_view.set_meta(self.meta)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
z = self.get_zoom_value()
|
|
147
|
+
p = new_view.page()
|
|
148
|
+
if p:
|
|
149
|
+
p.setZoomFactor(z)
|
|
150
|
+
except Exception:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
layout.insertWidget(idx, new_view)
|
|
154
|
+
layout.removeWidget(self)
|
|
155
|
+
self.hide()
|
|
156
|
+
|
|
157
|
+
QTimer.singleShot(0, self.on_delete)
|
|
158
|
+
if focus_after:
|
|
159
|
+
try:
|
|
160
|
+
new_view.setFocus()
|
|
161
|
+
except Exception:
|
|
162
|
+
pass
|
|
163
|
+
try:
|
|
164
|
+
mem_clean()
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
return new_view
|
|
169
|
+
|
|
84
170
|
def resetPage(self):
|
|
85
171
|
"""Reset current page (clear memory)"""
|
|
86
172
|
self.meta = None
|
|
87
173
|
self.plain = ""
|
|
88
174
|
self.html_content = ""
|
|
89
175
|
|
|
90
|
-
try:
|
|
91
|
-
(self.page().profile() if self.page() else QWebEngineProfile.defaultProfile()).clearHttpCache()
|
|
92
|
-
except Exception:
|
|
93
|
-
pass
|
|
94
|
-
|
|
95
|
-
self.setUpdatesEnabled(False)
|
|
96
176
|
old_page = self.page()
|
|
97
|
-
|
|
98
|
-
new_page = CustomWebEnginePage(self.window, self, profile=prof)
|
|
177
|
+
old_profile = getattr(self, "_profile", None)
|
|
99
178
|
|
|
179
|
+
self.setUpdatesEnabled(False)
|
|
180
|
+
new_profile = self._create_profile(parent=self)
|
|
181
|
+
new_page = CustomWebEnginePage(self.window, self, profile=new_profile)
|
|
100
182
|
self.setPage(new_page)
|
|
101
|
-
self._teardown_page(old_page)
|
|
102
183
|
|
|
184
|
+
if old_page:
|
|
185
|
+
self._teardown_page(old_page)
|
|
186
|
+
|
|
187
|
+
self._release_profile_after_page(old_page, old_profile)
|
|
188
|
+
self._profile = new_profile
|
|
189
|
+
|
|
190
|
+
QTimer.singleShot(0, lambda: QApplication.sendPostedEvents(None, QEvent.DeferredDelete))
|
|
103
191
|
mem_clean()
|
|
104
192
|
|
|
105
193
|
def on_delete(self):
|
|
106
194
|
"""Clean up on delete"""
|
|
195
|
+
self._detach_gl_event_filter()
|
|
107
196
|
if self.finder:
|
|
108
197
|
try:
|
|
109
198
|
self.finder.disconnect()
|
|
@@ -114,11 +203,14 @@ class ChatWebOutput(QWebEngineView):
|
|
|
114
203
|
self.tab = None
|
|
115
204
|
self.meta = None
|
|
116
205
|
|
|
117
|
-
# remove the page
|
|
206
|
+
# remove the page and profile
|
|
118
207
|
page = self.page()
|
|
208
|
+
prof = getattr(self, "_profile", None)
|
|
119
209
|
if page:
|
|
120
210
|
self._teardown_page(page)
|
|
121
211
|
|
|
212
|
+
self._release_profile_after_page(page, prof)
|
|
213
|
+
|
|
122
214
|
# safely unhook signals (may not have been hooked)
|
|
123
215
|
for sig, slot in (
|
|
124
216
|
(self.loadFinished, self.on_page_loaded),
|
|
@@ -136,25 +228,35 @@ class ChatWebOutput(QWebEngineView):
|
|
|
136
228
|
|
|
137
229
|
def eventFilter(self, source, event):
|
|
138
230
|
"""
|
|
139
|
-
|
|
231
|
+
Event filter to handle child added events and mouse button presses
|
|
140
232
|
|
|
141
|
-
:param source: source
|
|
142
|
-
:param event: event
|
|
233
|
+
:param source: QWidget - source of the event
|
|
234
|
+
:param event: QEvent - event to filter
|
|
143
235
|
"""
|
|
144
|
-
if
|
|
145
|
-
|
|
146
|
-
event.child().isWidgetType()):
|
|
236
|
+
if event.type() == QEvent.ChildAdded and source is self and event.child().isWidgetType():
|
|
237
|
+
self._detach_gl_event_filter()
|
|
147
238
|
self._glwidget = event.child()
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
239
|
+
try:
|
|
240
|
+
self._glwidget.installEventFilter(self)
|
|
241
|
+
self._glwidget_filter_installed = True
|
|
242
|
+
except Exception:
|
|
243
|
+
self._glwidget = None
|
|
244
|
+
self._glwidget_filter_installed = False
|
|
245
|
+
|
|
246
|
+
elif event.type() == QEvent.Type.MouseButtonPress:
|
|
247
|
+
try:
|
|
248
|
+
col_idx = self.tab.column_idx
|
|
249
|
+
self.window.controller.ui.tabs.on_column_focus(col_idx)
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
152
252
|
|
|
153
253
|
return super().eventFilter(source, event)
|
|
154
254
|
|
|
155
255
|
def on_focus(self, widget):
|
|
156
256
|
"""
|
|
157
257
|
On widget clicked
|
|
258
|
+
|
|
259
|
+
:param widget: QWidget - widget that received focus
|
|
158
260
|
"""
|
|
159
261
|
if self.tab is not None:
|
|
160
262
|
self.window.controller.ui.tabs.on_column_focus(self.tab.column_idx)
|
|
@@ -405,6 +507,13 @@ class Bridge(QObject):
|
|
|
405
507
|
super(Bridge, self).__init__(parent)
|
|
406
508
|
self.window = window
|
|
407
509
|
|
|
510
|
+
chunk = Signal(str, str, str, bool, bool) # name, buffer, chunk, replace, is_code
|
|
511
|
+
readyChanged = Signal(bool)
|
|
512
|
+
|
|
513
|
+
@Slot()
|
|
514
|
+
def js_ready(self):
|
|
515
|
+
self.readyChanged.emit(True)
|
|
516
|
+
|
|
408
517
|
@Slot(str)
|
|
409
518
|
def log(self, text: str):
|
|
410
519
|
print(f"JS log: {text}")
|
pygpt_net/utils.py
CHANGED
|
@@ -6,13 +6,16 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.08.16 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
import os
|
|
14
14
|
import re
|
|
15
15
|
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
from PySide6.QtCore import QEvent, QCoreApplication
|
|
18
|
+
|
|
16
19
|
from pygpt_net.core.locale import Locale
|
|
17
20
|
|
|
18
21
|
locale = None
|
|
@@ -265,6 +268,10 @@ def mem_clean():
|
|
|
265
268
|
gc.collect()
|
|
266
269
|
except Exception:
|
|
267
270
|
pass
|
|
271
|
+
try:
|
|
272
|
+
QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete)
|
|
273
|
+
except Exception:
|
|
274
|
+
pass
|
|
268
275
|
try:
|
|
269
276
|
if sys.platform.startswith("linux"):
|
|
270
277
|
import ctypes, ctypes.util
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pygpt-net
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.7
|
|
4
4
|
Summary: Desktop AI Assistant powered by: OpenAI GPT-5, o1, o3, GPT-4, Gemini, Claude, Grok, DeepSeek, and other models supported by Llama Index, and Ollama. Chatbot, agents, completion, image generation, vision analysis, speech-to-text, plugins, internet access, file handling, command execution and more.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: py_gpt,py-gpt,pygpt,desktop,app,o1,o3,gpt-5,gpt,gpt4,gpt-4o,gpt-4v,gpt3.5,gpt-4,gpt-4-vision,gpt-3.5,llama3,mistral,gemini,grok,deepseek,bielik,claude,tts,whisper,vision,chatgpt,dall-e,chat,chatbot,assistant,text completion,image generation,ai,api,openai,api key,langchain,llama-index,ollama,presets,ui,qt,pyside
|
|
@@ -108,7 +108,7 @@ Description-Content-Type: text/markdown
|
|
|
108
108
|
|
|
109
109
|
[](https://snapcraft.io/pygpt)
|
|
110
110
|
|
|
111
|
-
Release: **2.6.
|
|
111
|
+
Release: **2.6.7** | build: **2025-08-16** | Python: **>=3.10, <3.14**
|
|
112
112
|
|
|
113
113
|
> Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
|
|
114
114
|
>
|
|
@@ -4514,6 +4514,14 @@ may consume additional tokens that are not displayed in the main window.
|
|
|
4514
4514
|
|
|
4515
4515
|
## Recent changes:
|
|
4516
4516
|
|
|
4517
|
+
**2.6.7 (2025-08-16)**
|
|
4518
|
+
|
|
4519
|
+
- Fix: missing entity sanitize.
|
|
4520
|
+
|
|
4521
|
+
**2.6.6 (2025-08-16)**
|
|
4522
|
+
|
|
4523
|
+
- Output rendering optimization.
|
|
4524
|
+
|
|
4517
4525
|
**2.6.5 (2025-08-16)**
|
|
4518
4526
|
|
|
4519
4527
|
- Fix: crash when creating a context in a new group.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
pygpt_net/CHANGELOG.txt,sha256=
|
|
1
|
+
pygpt_net/CHANGELOG.txt,sha256=QZ2QQvtQrUcsSAcitx_akp0H01DcWJ2jZYbidxX4gPo,98778
|
|
2
2
|
pygpt_net/LICENSE,sha256=dz9sfFgYahvu2NZbx4C1xCsVn9GVer2wXcMkFRBvqzY,1146
|
|
3
|
-
pygpt_net/__init__.py,sha256=
|
|
4
|
-
pygpt_net/app.py,sha256=
|
|
3
|
+
pygpt_net/__init__.py,sha256=vTKB9aiZrQ0r8CZYy5EkKL41A0JgiVn4Eg-_IjSm6gk,1372
|
|
4
|
+
pygpt_net/app.py,sha256=H0bDAM1kNQswEPnMRgbxJ_wa5KF1LIG-eLwAhXzdNxI,20675
|
|
5
5
|
pygpt_net/config.py,sha256=P-D-vRWwq77moEMveMNyUImH8hG3PXpxqBsWyriEGzs,16731
|
|
6
6
|
pygpt_net/container.py,sha256=NsMSHURaEC_eW8vrCNdztwqkxB7jui3yVlzUOMYvCHg,4124
|
|
7
7
|
pygpt_net/controller/__init__.py,sha256=FiAP-Md0a57HSH1sSFflB4aq6Jho9M1lejk9VJxM8is,5969
|
|
@@ -43,7 +43,7 @@ pygpt_net/controller/chat/input.py,sha256=YJ_e-oNVcJewrRJznDXLmpAdoTdb9PkXdHy0dG
|
|
|
43
43
|
pygpt_net/controller/chat/output.py,sha256=lqb7gCTTVfTocXJdjWvLhCOQrfR6wph2NGqpuILhHTo,10877
|
|
44
44
|
pygpt_net/controller/chat/render.py,sha256=Vxy2523puEL0wk9tbvgF24AX7NWCTS2rNTTKHF89fVc,20191
|
|
45
45
|
pygpt_net/controller/chat/response.py,sha256=vJqS6KkxXubqq07fTEgVXrZxInIfF96gBlCA9SBGc9s,12417
|
|
46
|
-
pygpt_net/controller/chat/stream.py,sha256=
|
|
46
|
+
pygpt_net/controller/chat/stream.py,sha256=5k70B4izs8cdv4dhzr4P13VWXTE91eJv6NapRUN_5QA,21817
|
|
47
47
|
pygpt_net/controller/chat/text.py,sha256=6tR6c733GN0qKX1s27_xO2j47Qnj8Rdl7kIPaFHcIm4,10659
|
|
48
48
|
pygpt_net/controller/chat/vision.py,sha256=LsFc0TZZwY8dVtJH6Q5iha8rUQCf5HhOMuRXMtnLzZU,3578
|
|
49
49
|
pygpt_net/controller/command/__init__.py,sha256=xHOjVYXKiHT-FEXNSGJZoYcU8SslQ2vr6DMqJcVPDNE,511
|
|
@@ -303,11 +303,11 @@ pygpt_net/core/render/plain/helpers.py,sha256=CMF84kSeuQnkgZVHmN_9YWaL5BC958tDE9
|
|
|
303
303
|
pygpt_net/core/render/plain/pid.py,sha256=Pz3v1tnLj-XI_9vcaVkCf9SZ2EgVs4LYV4qzelBMoOg,1119
|
|
304
304
|
pygpt_net/core/render/plain/renderer.py,sha256=Cm-HXHd5Mc6LAiDW5qNXOyZoLKlUo16I_cU-B2cGnYM,15595
|
|
305
305
|
pygpt_net/core/render/web/__init__.py,sha256=istp5dsn6EkLEP7lOBeDb8RjodUcWZqjcEvTroaTT-w,489
|
|
306
|
-
pygpt_net/core/render/web/body.py,sha256
|
|
306
|
+
pygpt_net/core/render/web/body.py,sha256=3BqHyBibKQOo_3ikLOvytTJGm6OG519A2i9eRUhmzfo,55975
|
|
307
307
|
pygpt_net/core/render/web/helpers.py,sha256=ivrXrCqRIUWHDmu3INu-i6XUlB2W9IOO8iYyqpbnSRU,5438
|
|
308
308
|
pygpt_net/core/render/web/parser.py,sha256=2ATdydXNDIgf4Jq44m1kvd7Vr4mW_FopovrYbwfv7rc,12740
|
|
309
309
|
pygpt_net/core/render/web/pid.py,sha256=EOp8hUJMFXNgZs8Kl5Qt8fz3Rslj6meK8Yn9aDmUrdM,3092
|
|
310
|
-
pygpt_net/core/render/web/renderer.py,sha256=
|
|
310
|
+
pygpt_net/core/render/web/renderer.py,sha256=SCeUFC6GxAAvmxg07N1EM6FALaBwEGtLLMd05i3T2P8,47301
|
|
311
311
|
pygpt_net/core/render/web/syntax_highlight.py,sha256=QSLGF5cJL_Xeqej7_TYwY_5C2w9enXV_cMEuaJ3C43U,2005
|
|
312
312
|
pygpt_net/core/settings/__init__.py,sha256=GQ6_gJ2jf_Chm7ZuZLvkcvEh_sfMDVMBieeoJi2iPI4,512
|
|
313
313
|
pygpt_net/core/settings/settings.py,sha256=onqwNiICm2VhHfmXLvp1MiEJ14m2jzeeI2pjUiaUwtY,7787
|
|
@@ -343,8 +343,8 @@ pygpt_net/css_rc.py,sha256=i13kX7irhbYCWZ5yJbcMmnkFp_UfS4PYnvRFSPF7XXo,11349
|
|
|
343
343
|
pygpt_net/data/audio/click_off.mp3,sha256=aNiRDP1pt-Jy7ija4YKCNFBwvGWbzU460F4pZWZDS90,65201
|
|
344
344
|
pygpt_net/data/audio/click_on.mp3,sha256=qfdsSnthAEHVXzeyN4LlC0OvXuyW8p7stb7VXtlvZ1k,65201
|
|
345
345
|
pygpt_net/data/audio/ok.mp3,sha256=LTiV32pEBkpUGBkKkcOdOFB7Eyt_QoP2Nv6c5AaXftk,32256
|
|
346
|
-
pygpt_net/data/config/config.json,sha256=
|
|
347
|
-
pygpt_net/data/config/models.json,sha256=
|
|
346
|
+
pygpt_net/data/config/config.json,sha256=zdQC0oIjws1UtweU7xqa3T3sqx2Ogpt6vmqwRqTayjs,24887
|
|
347
|
+
pygpt_net/data/config/models.json,sha256=qNfg4fj0JoOvAKufESkCwzc236M7FbwwnG1u-HVK0qE,109648
|
|
348
348
|
pygpt_net/data/config/modes.json,sha256=M882iiqX_R2sNQl9cqZ3k-uneEvO9wpARtHRMLx_LHw,2265
|
|
349
349
|
pygpt_net/data/config/presets/agent_code_act.json,sha256=GYHqhxtKFLUCvRI3IJAJ7Qe1k8yD9wGGNwManldWzlI,754
|
|
350
350
|
pygpt_net/data/config/presets/agent_openai.json,sha256=vMTR-soRBiEZrpJJHuFLWyx8a3Ez_BqtqjyXgxCAM_Q,733
|
|
@@ -2307,7 +2307,7 @@ pygpt_net/ui/layout/toolbox/presets.py,sha256=uEmMy3gudpjKFQbDVNShjK1iBw-bl-1IXS
|
|
|
2307
2307
|
pygpt_net/ui/layout/toolbox/prompt.py,sha256=jebF-q1S1Et6ISa9vI0_nM4sb7liDesAXJHtZ5Ll7ZI,4006
|
|
2308
2308
|
pygpt_net/ui/layout/toolbox/toolbox.py,sha256=zEZr_XDz9QbPKL0u0KMSt1b8yOG-ao1gmZPvWWVpuVs,3392
|
|
2309
2309
|
pygpt_net/ui/layout/toolbox/vision.py,sha256=GZY-N2z8re1LN1ntsy-3Ius8OY4DujmJpyJ1qP2ZRxs,2447
|
|
2310
|
-
pygpt_net/ui/main.py,sha256=
|
|
2310
|
+
pygpt_net/ui/main.py,sha256=ObCj3nMmjzDEWgNSdh0Dwn8BocEjmuSl-AALDQr2g8I,13783
|
|
2311
2311
|
pygpt_net/ui/menu/__init__.py,sha256=wAIKG9wLWfYv6tpXCTXptWb_XKoCc-4lYWLDvV1bVYk,508
|
|
2312
2312
|
pygpt_net/ui/menu/about.py,sha256=bs0iGge52THU72oyu98QBxvh5iKA9APQIStRKQT5IQc,5891
|
|
2313
2313
|
pygpt_net/ui/menu/audio.py,sha256=Sb8NTAyMnPj4johTvBKwocHzq67XypIdw7K7hjf2760,3494
|
|
@@ -2429,12 +2429,12 @@ pygpt_net/ui/widget/textarea/output.py,sha256=8T2spzqVYHKopSB83p1ULazGZ14nFJhXLB
|
|
|
2429
2429
|
pygpt_net/ui/widget/textarea/rename.py,sha256=NwuGRIeWMo7WfsMguAFpTqdOz1eTiXbxrDXGsbWF_TY,1358
|
|
2430
2430
|
pygpt_net/ui/widget/textarea/search_input.py,sha256=phEXf50VcfCRBen0p2iEAzuX2zmrSE3nWVRfWmtHKpo,5228
|
|
2431
2431
|
pygpt_net/ui/widget/textarea/url.py,sha256=xbNQxoM5fYI1ZWbvybQkPmNPrIq3yhtNPBOSOWftZCg,1337
|
|
2432
|
-
pygpt_net/ui/widget/textarea/web.py,sha256=
|
|
2432
|
+
pygpt_net/ui/widget/textarea/web.py,sha256=6Kt9IUkjx3rhWBmH3xdnmX1X-ACUMYnfHnRN0qoPw2g,17798
|
|
2433
2433
|
pygpt_net/ui/widget/vision/__init__.py,sha256=8HT4tQFqQogEEpGYTv2RplKBthlsFKcl5egnv4lzzEw,488
|
|
2434
2434
|
pygpt_net/ui/widget/vision/camera.py,sha256=T8b5cmK6uhf_WSSxzPt_Qod8JgMnst6q8sQqRvgQiSA,2584
|
|
2435
|
-
pygpt_net/utils.py,sha256=
|
|
2436
|
-
pygpt_net-2.6.
|
|
2437
|
-
pygpt_net-2.6.
|
|
2438
|
-
pygpt_net-2.6.
|
|
2439
|
-
pygpt_net-2.6.
|
|
2440
|
-
pygpt_net-2.6.
|
|
2435
|
+
pygpt_net/utils.py,sha256=YL0czRa1v6ilKNszAI9NyaE9Lgz6HiUGpNFAYQ9Wj8s,8835
|
|
2436
|
+
pygpt_net-2.6.7.dist-info/LICENSE,sha256=rbPqNB_xxANH8hKayJyIcTwD4bj4Y2G-Mcm85r1OImM,1126
|
|
2437
|
+
pygpt_net-2.6.7.dist-info/METADATA,sha256=2phPEirUV7ohpBd1unAhVh7eQ9Uoy6LUQLcLuiYmdEM,186632
|
|
2438
|
+
pygpt_net-2.6.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
2439
|
+
pygpt_net-2.6.7.dist-info/entry_points.txt,sha256=qvpII6UHIt8XfokmQWnCYQrTgty8FeJ9hJvOuUFCN-8,43
|
|
2440
|
+
pygpt_net-2.6.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|