pygpt-net 2.6.48__py3-none-any.whl → 2.6.50__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 +10 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/ctx/ctx.py +5 -3
- pygpt_net/controller/plugins/plugins.py +1 -1
- pygpt_net/controller/ui/tabs.py +4 -6
- pygpt_net/core/render/plain/pid.py +2 -0
- pygpt_net/core/render/web/renderer.py +4 -10
- pygpt_net/core/tabs/tabs.py +10 -17
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/fixtures/fake_stream.txt +21 -0
- pygpt_net/data/js/app.js +69 -15
- pygpt_net/js_rc.py +12200 -12058
- pygpt_net/tools/code_interpreter/ui/html.py +176 -31
- pygpt_net/tools/code_interpreter/ui/widgets.py +1 -4
- pygpt_net/tools/html_canvas/ui/widgets.py +2 -5
- pygpt_net/ui/widget/tabs/body.py +23 -1
- pygpt_net/ui/widget/textarea/web.py +85 -45
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/METADATA +12 -2
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/RECORD +23 -23
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.48.dist-info → pygpt_net-2.6.50.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
2.6.50 (2025-09-16)
|
|
2
|
+
|
|
3
|
+
- Optimized: Improved memory cleanup when switching profiles and unloading tabs.
|
|
4
|
+
- Fix: Resolved missing PID data in text output.
|
|
5
|
+
- Fix: Enhanced real-time parsing of execute tags.
|
|
6
|
+
|
|
7
|
+
2.6.49 (2025-09-16)
|
|
8
|
+
|
|
9
|
+
- Fixed: Occasional crashes when focusing on an output container unloaded from memory in the second column.
|
|
10
|
+
|
|
1
11
|
2.6.48 (2025-09-15)
|
|
2
12
|
|
|
3
13
|
- Added: auto-loading of next items to the list of contexts when scrolling to the end of the list.
|
pygpt_net/__init__.py
CHANGED
|
@@ -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.09.
|
|
9
|
+
# Updated Date: 2025.09.16 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
__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.
|
|
17
|
-
__build__ = "2025-09-
|
|
16
|
+
__version__ = "2.6.50"
|
|
17
|
+
__build__ = "2025-09-16"
|
|
18
18
|
__maintainer__ = "Marcin Szczygliński"
|
|
19
19
|
__github__ = "https://github.com/szczyglis-dev/py-gpt"
|
|
20
20
|
__report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
|
pygpt_net/controller/ctx/ctx.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.09.
|
|
9
|
+
# Updated Date: 2025.09.16 02:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Optional, List
|
|
@@ -479,7 +479,8 @@ class Ctx:
|
|
|
479
479
|
id: int,
|
|
480
480
|
restore_model: bool = True,
|
|
481
481
|
select_idx: Optional[int] = None,
|
|
482
|
-
new_tab: Optional[bool] = False
|
|
482
|
+
new_tab: Optional[bool] = False,
|
|
483
|
+
no_fresh: bool = False
|
|
483
484
|
):
|
|
484
485
|
"""
|
|
485
486
|
Load ctx data
|
|
@@ -488,6 +489,7 @@ class Ctx:
|
|
|
488
489
|
:param restore_model: restore model if defined in ctx
|
|
489
490
|
:param select_idx: select index on list after loading
|
|
490
491
|
:param new_tab: open in new tab
|
|
492
|
+
:param no_fresh: do not fresh output
|
|
491
493
|
"""
|
|
492
494
|
if new_tab:
|
|
493
495
|
col_idx = self.window.controller.ui.tabs.column_idx
|
|
@@ -500,7 +502,7 @@ class Ctx:
|
|
|
500
502
|
if meta is not None:
|
|
501
503
|
self.set_group(meta.group_id)
|
|
502
504
|
|
|
503
|
-
if meta is not None:
|
|
505
|
+
if meta is not None and not no_fresh:
|
|
504
506
|
self.fresh_output(meta)
|
|
505
507
|
|
|
506
508
|
self.reload_config()
|
pygpt_net/controller/ui/tabs.py
CHANGED
|
@@ -6,14 +6,12 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.09.16 02:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
|
-
import gc
|
|
12
|
-
from typing import Any, Optional, Tuple
|
|
13
11
|
|
|
14
|
-
from
|
|
15
|
-
from PySide6.QtWebEngineCore import QWebEnginePage
|
|
12
|
+
from typing import Any, Optional, Tuple
|
|
16
13
|
|
|
14
|
+
from PySide6.QtCore import QTimer
|
|
17
15
|
from pygpt_net.core.events import AppEvent, RenderEvent
|
|
18
16
|
from pygpt_net.core.tabs.tab import Tab
|
|
19
17
|
from pygpt_net.item.ctx import CtxMeta
|
|
@@ -363,7 +361,7 @@ class Tabs:
|
|
|
363
361
|
if tab.type == Tab.TAB_CHAT and self.column_idx == 1 and not getattr(tab, "loaded", False):
|
|
364
362
|
meta = self.window.core.ctx.get_meta_by_id(tab.data_id)
|
|
365
363
|
if meta is not None:
|
|
366
|
-
self.window.controller.ctx.load(meta.id)
|
|
364
|
+
self.window.controller.ctx.load(meta.id, no_fresh=True)
|
|
367
365
|
tab.loaded = True
|
|
368
366
|
|
|
369
367
|
current_ctx = self.window.core.ctx.get_current()
|
|
@@ -23,6 +23,7 @@ class PidData:
|
|
|
23
23
|
files_appended: list = field(default_factory=list)
|
|
24
24
|
_buffer: io.StringIO = field(default_factory=io.StringIO)
|
|
25
25
|
is_cmd: bool = False
|
|
26
|
+
loaded: bool = False
|
|
26
27
|
|
|
27
28
|
def __init__(self, pid, meta=None):
|
|
28
29
|
"""Pid Data"""
|
|
@@ -33,6 +34,7 @@ class PidData:
|
|
|
33
34
|
self.files_appended = []
|
|
34
35
|
self._buffer = io.StringIO()
|
|
35
36
|
self.is_cmd = False
|
|
37
|
+
self.loaded = False
|
|
36
38
|
|
|
37
39
|
@property
|
|
38
40
|
def buffer(self) -> str:
|
|
@@ -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.09.
|
|
9
|
+
# Updated Date: 2025.09.16 02:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import gc
|
|
@@ -1274,12 +1274,7 @@ class Renderer(BaseRenderer):
|
|
|
1274
1274
|
pass
|
|
1275
1275
|
self._bridge_ready[pid] = False
|
|
1276
1276
|
self._pending_nodes[pid] = []
|
|
1277
|
-
node.
|
|
1278
|
-
p = node.page()
|
|
1279
|
-
p.triggerAction(QWebEnginePage.Stop)
|
|
1280
|
-
p.setUrl(QUrl("about:blank"))
|
|
1281
|
-
p.history().clear()
|
|
1282
|
-
p.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
|
|
1277
|
+
node.unload() # unload web page
|
|
1283
1278
|
self._stream_reset(pid)
|
|
1284
1279
|
self.pids[pid].clear(all=True)
|
|
1285
1280
|
self.pids[pid].loaded = False
|
|
@@ -1294,9 +1289,8 @@ class Renderer(BaseRenderer):
|
|
|
1294
1289
|
:param meta: context meta
|
|
1295
1290
|
"""
|
|
1296
1291
|
tab = node.get_tab()
|
|
1297
|
-
tab.delete_ref(node)
|
|
1298
1292
|
layout = tab.child.layout()
|
|
1299
|
-
|
|
1293
|
+
tab.child.remove_widget(node)
|
|
1300
1294
|
self.window.ui.nodes['output'].pop(tab.pid, None)
|
|
1301
1295
|
|
|
1302
1296
|
node.on_delete()
|
|
@@ -1307,7 +1301,7 @@ class Renderer(BaseRenderer):
|
|
|
1307
1301
|
view.signals.save_as.connect(self.window.controller.chat.render.handle_save_as)
|
|
1308
1302
|
view.signals.audio_read.connect(self.window.controller.chat.render.handle_audio_read)
|
|
1309
1303
|
|
|
1310
|
-
layout.addWidget(view)
|
|
1304
|
+
layout.addWidget(view) # tab body layout
|
|
1311
1305
|
view.setVisible(True)
|
|
1312
1306
|
self.window.ui.nodes['output'][tab.pid] = view
|
|
1313
1307
|
try:
|
pygpt_net/core/tabs/tabs.py
CHANGED
|
@@ -6,20 +6,18 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.09.16 02:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import uuid
|
|
13
13
|
from datetime import datetime
|
|
14
14
|
from typing import Optional, Any, Dict, Tuple, Union
|
|
15
15
|
|
|
16
|
-
from PySide6.QtCore import QUrl
|
|
17
16
|
from PySide6.QtGui import QIcon
|
|
18
|
-
from PySide6.QtWebEngineCore import QWebEnginePage
|
|
19
17
|
from PySide6.QtWidgets import QVBoxLayout, QWidget, QLayout
|
|
20
18
|
|
|
21
19
|
from pygpt_net.ui.widget.tabs.body import TabBody
|
|
22
|
-
from pygpt_net.utils import trans
|
|
20
|
+
from pygpt_net.utils import trans, mem_clean
|
|
23
21
|
|
|
24
22
|
from .tab import Tab
|
|
25
23
|
|
|
@@ -301,31 +299,26 @@ class Tabs:
|
|
|
301
299
|
if tab.type == Tab.TAB_CHAT:
|
|
302
300
|
node = self.window.ui.nodes['output'].get(tab.pid)
|
|
303
301
|
if node:
|
|
304
|
-
node.
|
|
305
|
-
|
|
306
|
-
p.triggerAction(QWebEnginePage.Stop)
|
|
307
|
-
p.setUrl(QUrl("about:blank"))
|
|
308
|
-
p.history().clear()
|
|
309
|
-
p.setLifecycleState(QWebEnginePage.LifecycleState.Discarded)
|
|
310
|
-
tab.delete_ref(node)
|
|
311
|
-
layout = tab.child.layout()
|
|
312
|
-
layout.removeWidget(node)
|
|
302
|
+
node.unload() # unload web page
|
|
303
|
+
tab.child.remove_widget(node)
|
|
313
304
|
self.window.ui.nodes['output'].pop(pid, None)
|
|
314
305
|
node.on_delete()
|
|
315
306
|
node_plain = self.window.ui.nodes['output_plain'].get(tab.pid)
|
|
316
307
|
if node_plain:
|
|
317
|
-
tab.
|
|
318
|
-
layout = tab.child.layout()
|
|
319
|
-
layout.removeWidget(node_plain)
|
|
308
|
+
tab.child.remove_widget(node_plain)
|
|
320
309
|
self.window.ui.nodes['output_plain'].pop(pid, None)
|
|
321
310
|
node_plain.on_delete()
|
|
322
311
|
|
|
323
312
|
if tab.type in (Tab.TAB_CHAT, Tab.TAB_NOTEPAD, Tab.TAB_TOOL):
|
|
324
313
|
tab.cleanup() # unload assigned data from memory
|
|
325
314
|
|
|
315
|
+
# tab.delete_refs()
|
|
316
|
+
|
|
326
317
|
except Exception as e:
|
|
327
318
|
print(f"Error unloading tab {pid}: {e}")
|
|
319
|
+
self.window.core.debug.log(e)
|
|
328
320
|
|
|
321
|
+
mem_clean(force=True)
|
|
329
322
|
column_idx = tab.column_idx
|
|
330
323
|
self.window.ui.layout.get_tabs_by_idx(column_idx).removeTab(tab.idx)
|
|
331
324
|
del self.pids[pid]
|
|
@@ -333,7 +326,7 @@ class Tabs:
|
|
|
333
326
|
|
|
334
327
|
def remove_all(self):
|
|
335
328
|
"""Remove all tabs"""
|
|
336
|
-
for pid in list(self.pids):
|
|
329
|
+
for pid in list(self.pids.keys()):
|
|
337
330
|
self.remove(pid) # delete from PIDs and UI
|
|
338
331
|
self.pids = {}
|
|
339
332
|
self.window.core.ctx.output.clear() # clear mapping
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"__meta__": {
|
|
3
|
-
"version": "2.6.
|
|
4
|
-
"app.version": "2.6.
|
|
5
|
-
"updated_at": "2025-09-
|
|
3
|
+
"version": "2.6.50",
|
|
4
|
+
"app.version": "2.6.50",
|
|
5
|
+
"updated_at": "2025-09-16T00:00:00"
|
|
6
6
|
},
|
|
7
7
|
"access.audio.event.speech": false,
|
|
8
8
|
"access.audio.event.speech.disabled": [],
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"__meta__": {
|
|
3
|
-
"version": "2.6.
|
|
4
|
-
"app.version": "2.6.
|
|
5
|
-
"updated_at": "2025-09-
|
|
3
|
+
"version": "2.6.50",
|
|
4
|
+
"app.version": "2.6.50",
|
|
5
|
+
"updated_at": "2025-09-16T08:03:34"
|
|
6
6
|
},
|
|
7
7
|
"items": {
|
|
8
8
|
"SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
|
|
@@ -6,6 +6,27 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent lobortis lorem
|
|
|
6
6
|
b = 2
|
|
7
7
|
c = a + b
|
|
8
8
|
print(c)
|
|
9
|
+
class ChatWebOutput(QWebEngineView):
|
|
10
|
+
def __init__(self, window=None):
|
|
11
|
+
"""
|
|
12
|
+
HTML output (WebEngine)
|
|
13
|
+
|
|
14
|
+
:param window: Window instance
|
|
15
|
+
"""
|
|
16
|
+
super(ChatWebOutput, self).__init__(window)
|
|
17
|
+
self.window = window
|
|
18
|
+
self.finder = WebFinder(window, self)
|
|
19
|
+
self.loadFinished.connect(self.on_page_loaded)
|
|
20
|
+
self.customContextMenuRequested.connect(self.on_context_menu)
|
|
21
|
+
self.signals = WebEngineSignals(self)
|
|
22
|
+
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
23
|
+
self.filter = FocusEventFilter(self, self.on_focus)
|
|
24
|
+
self.installEventFilter(self)
|
|
25
|
+
self.plain = None
|
|
26
|
+
self.html_content = None
|
|
27
|
+
self.meta = None
|
|
28
|
+
self.tab = None
|
|
29
|
+
self.setProperty('class', 'layout-output-web')
|
|
9
30
|
</execute>
|
|
10
31
|
|
|
11
32
|
<tool>{"cmd": "fake_cmd", "params": {"foo": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."}}</tool>
|
pygpt_net/data/js/app.js
CHANGED
|
@@ -1383,6 +1383,7 @@
|
|
|
1383
1383
|
const rules = this.__compiled;
|
|
1384
1384
|
if (!rules || !rules.length) return s;
|
|
1385
1385
|
|
|
1386
|
+
// Candidates: rules that want source-phase replacements (``` fences)
|
|
1386
1387
|
const candidates = [];
|
|
1387
1388
|
for (let i = 0; i < rules.length; i++) {
|
|
1388
1389
|
const r = rules[i];
|
|
@@ -1391,27 +1392,42 @@
|
|
|
1391
1392
|
}
|
|
1392
1393
|
if (!candidates.length) return s;
|
|
1393
1394
|
|
|
1395
|
+
// Avoid touching content already inside Markdown code fences
|
|
1394
1396
|
const fences = this._findFenceRanges(s);
|
|
1397
|
+
|
|
1398
|
+
let result = '';
|
|
1395
1399
|
if (!fences.length) {
|
|
1396
|
-
|
|
1400
|
+
result = this._applySourceReplacementsInChunk(s, s, 0, candidates);
|
|
1401
|
+
} else {
|
|
1402
|
+
let out = '';
|
|
1403
|
+
let last = 0;
|
|
1404
|
+
for (let k = 0; k < fences.length; k++) {
|
|
1405
|
+
const [a, b] = fences[k];
|
|
1406
|
+
if (a > last) {
|
|
1407
|
+
const chunk = s.slice(last, a);
|
|
1408
|
+
out += this._applySourceReplacementsInChunk(s, chunk, last, candidates);
|
|
1409
|
+
}
|
|
1410
|
+
out += s.slice(a, b);
|
|
1411
|
+
last = b;
|
|
1412
|
+
}
|
|
1413
|
+
if (last < s.length) {
|
|
1414
|
+
const tail = s.slice(last);
|
|
1415
|
+
out += this._applySourceReplacementsInChunk(s, tail, last, candidates);
|
|
1416
|
+
}
|
|
1417
|
+
result = out;
|
|
1397
1418
|
}
|
|
1398
1419
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1420
|
+
// NEW: streaming-aware partial injection for unmatched source-fence openers.
|
|
1421
|
+
// If we are streaming and see a last opener without a closer, convert it to its openReplace
|
|
1422
|
+
// (e.g., <execute> -> ```python\n) so the snapshot immediately materializes a code block.
|
|
1423
|
+
if (opts && opts.streaming === true) {
|
|
1424
|
+
const fenceRules = candidates.filter(r => !!r.isSourceFence);
|
|
1425
|
+
if (fenceRules.length) {
|
|
1426
|
+
result = this._injectUnmatchedSourceOpeners(result, fenceRules);
|
|
1406
1427
|
}
|
|
1407
|
-
out += s.slice(a, b);
|
|
1408
|
-
last = b;
|
|
1409
1428
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
out += this._applySourceReplacementsInChunk(s, tail, last, candidates);
|
|
1413
|
-
}
|
|
1414
|
-
return out;
|
|
1429
|
+
|
|
1430
|
+
return result;
|
|
1415
1431
|
}
|
|
1416
1432
|
|
|
1417
1433
|
getSourceFenceSpecs() {
|
|
@@ -1996,6 +2012,44 @@
|
|
|
1996
2012
|
}
|
|
1997
2013
|
return t;
|
|
1998
2014
|
}
|
|
2015
|
+
|
|
2016
|
+
// === NEW: streaming helper for unmatched source-fence openers ===
|
|
2017
|
+
_injectUnmatchedSourceOpeners(text, fenceRules) {
|
|
2018
|
+
// Production-grade guardrails
|
|
2019
|
+
let s = String(text || '');
|
|
2020
|
+
if (!s || !fenceRules || !fenceRules.length) return s;
|
|
2021
|
+
|
|
2022
|
+
// Find the last opener (closest to the end) that has no matching closer after it
|
|
2023
|
+
let best = null; // { r, idx }
|
|
2024
|
+
for (let i = 0; i < fenceRules.length; i++) {
|
|
2025
|
+
const r = fenceRules[i];
|
|
2026
|
+
if (!r || !r.open || !r.close || !r.openReplace) continue;
|
|
2027
|
+
|
|
2028
|
+
const idx = s.lastIndexOf(r.open);
|
|
2029
|
+
if (idx === -1) continue;
|
|
2030
|
+
|
|
2031
|
+
// Must be top-level line (so that ``` can start a fenced block)
|
|
2032
|
+
if (!this._isTopLevelLineInSource(s, idx)) continue;
|
|
2033
|
+
|
|
2034
|
+
// Ensure there is no closer after this opener in the current snapshot
|
|
2035
|
+
const after = s.indexOf(r.close, idx + r.open.length);
|
|
2036
|
+
if (after !== -1) continue;
|
|
2037
|
+
|
|
2038
|
+
if (!best || idx > best.idx) best = { r, idx };
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
if (!best) return s;
|
|
2042
|
+
|
|
2043
|
+
const r = best.r;
|
|
2044
|
+
const i = best.idx;
|
|
2045
|
+
|
|
2046
|
+
// Replace the raw opener token with its source-fence open replacement (e.g. ```python\n)
|
|
2047
|
+
// This is ephemeral per-snapshot; underlying buffer remains unchanged.
|
|
2048
|
+
const before = s.slice(0, i);
|
|
2049
|
+
const after = s.slice(i + r.open.length);
|
|
2050
|
+
const injected = String(r.openReplace || '');
|
|
2051
|
+
return before + injected + after;
|
|
2052
|
+
}
|
|
1999
2053
|
}
|
|
2000
2054
|
|
|
2001
2055
|
// ==========================================================================
|