pygpt-net 2.6.49__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 CHANGED
@@ -1,3 +1,9 @@
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
+
1
7
  2.6.49 (2025-09-16)
2
8
 
3
9
  - Fixed: Occasional crashes when focusing on an output container unloaded from memory in the second column.
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.49"
16
+ __version__ = "2.6.50"
17
17
  __build__ = "2025-09-16"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
@@ -445,7 +445,7 @@ class Plugins:
445
445
  """Reload plugins"""
446
446
  self.window.core.plugins.reload_all()
447
447
  self.setup()
448
- self.settings.setup()
448
+ self.settings.init()
449
449
  self.update()
450
450
 
451
451
  def save_all(self):
@@ -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:
@@ -17,7 +17,7 @@ from PySide6.QtGui import QIcon
17
17
  from PySide6.QtWidgets import QVBoxLayout, QWidget, QLayout
18
18
 
19
19
  from pygpt_net.ui.widget.tabs.body import TabBody
20
- from pygpt_net.utils import trans
20
+ from pygpt_net.utils import trans, mem_clean
21
21
 
22
22
  from .tab import Tab
23
23
 
@@ -318,6 +318,7 @@ class Tabs:
318
318
  print(f"Error unloading tab {pid}: {e}")
319
319
  self.window.core.debug.log(e)
320
320
 
321
+ mem_clean(force=True)
321
322
  column_idx = tab.column_idx
322
323
  self.window.ui.layout.get_tabs_by_idx(column_idx).removeTab(tab.idx)
323
324
  del self.pids[pid]
@@ -325,7 +326,7 @@ class Tabs:
325
326
 
326
327
  def remove_all(self):
327
328
  """Remove all tabs"""
328
- for pid in list(self.pids):
329
+ for pid in list(self.pids.keys()):
329
330
  self.remove(pid) # delete from PIDs and UI
330
331
  self.pids = {}
331
332
  self.window.core.ctx.output.clear() # clear mapping
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.49",
4
- "app.version": "2.6.49",
3
+ "version": "2.6.50",
4
+ "app.version": "2.6.50",
5
5
  "updated_at": "2025-09-16T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.49",
4
- "app.version": "2.6.49",
3
+ "version": "2.6.50",
4
+ "app.version": "2.6.50",
5
5
  "updated_at": "2025-09-16T08:03:34"
6
6
  },
7
7
  "items": {
@@ -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
- return this._applySourceReplacementsInChunk(s, s, 0, candidates);
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
- let out = '';
1400
- let last = 0;
1401
- for (let k = 0; k < fences.length; k++) {
1402
- const [a, b] = fences[k];
1403
- if (a > last) {
1404
- const chunk = s.slice(last, a);
1405
- out += this._applySourceReplacementsInChunk(s, chunk, last, candidates);
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
- if (last < s.length) {
1411
- const tail = s.slice(last);
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
  // ==========================================================================