pygpt-net 2.6.47__py3-none-any.whl → 2.6.49__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 (35) hide show
  1. pygpt_net/CHANGELOG.txt +8 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app_core.py +39 -39
  4. pygpt_net/controller/__init__.py +72 -62
  5. pygpt_net/controller/ctx/common.py +0 -7
  6. pygpt_net/controller/ctx/ctx.py +176 -8
  7. pygpt_net/controller/ctx/extra.py +3 -3
  8. pygpt_net/controller/settings/editor.py +3 -1
  9. pygpt_net/controller/theme/common.py +8 -2
  10. pygpt_net/controller/ui/tabs.py +10 -43
  11. pygpt_net/core/ctx/ctx.py +79 -26
  12. pygpt_net/core/render/web/renderer.py +4 -10
  13. pygpt_net/core/tabs/tabs.py +50 -11
  14. pygpt_net/data/config/config.json +3 -3
  15. pygpt_net/data/config/models.json +3 -3
  16. pygpt_net/data/css/web-blocks.css +256 -270
  17. pygpt_net/data/css/web-chatgpt.css +276 -301
  18. pygpt_net/data/css/web-chatgpt_wide.css +286 -294
  19. pygpt_net/provider/core/config/patch.py +9 -0
  20. pygpt_net/provider/core/ctx/db_sqlite/storage.py +19 -5
  21. pygpt_net/tools/code_interpreter/ui/html.py +176 -31
  22. pygpt_net/tools/code_interpreter/ui/widgets.py +1 -4
  23. pygpt_net/tools/html_canvas/ui/widgets.py +2 -5
  24. pygpt_net/ui/__init__.py +9 -14
  25. pygpt_net/ui/layout/chat/chat.py +2 -2
  26. pygpt_net/ui/layout/ctx/ctx_list.py +71 -1
  27. pygpt_net/ui/widget/lists/base.py +32 -1
  28. pygpt_net/ui/widget/lists/context.py +45 -2
  29. pygpt_net/ui/widget/tabs/body.py +23 -1
  30. pygpt_net/ui/widget/textarea/web.py +85 -45
  31. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/METADATA +10 -2
  32. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/RECORD +35 -35
  33. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/LICENSE +0 -0
  34. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/WHEEL +0 -0
  35. {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,11 @@
1
+ 2.6.49 (2025-09-16)
2
+
3
+ - Fixed: Occasional crashes when focusing on an output container unloaded from memory in the second column.
4
+
5
+ 2.6.48 (2025-09-15)
6
+
7
+ - Added: auto-loading of next items to the list of contexts when scrolling to the end of the list.
8
+
1
9
  2.6.47 (2025-09-15)
2
10
 
3
11
  - Improved: Parsing of custom markup tags.
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.15 00:00:00 #
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.47"
17
- __build__ = "2025-09-15"
16
+ __version__ = "2.6.49"
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/app_core.py CHANGED
@@ -6,48 +6,48 @@
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.01 23:00:00 #
9
+ # Updated Date: 2025.09.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from pygpt_net.config import Config
13
- from pygpt_net.core.access import Access
14
- from pygpt_net.core.agents import Agents
15
- from pygpt_net.core.assistants import Assistants
16
- from pygpt_net.core.attachments import Attachments
17
- from pygpt_net.core.audio import Audio
18
- from pygpt_net.core.bridge import Bridge
19
- from pygpt_net.core.calendar import Calendar
20
- from pygpt_net.core.camera import Camera
21
- # from pygpt_net.core.chain import Chain
22
- from pygpt_net.core.command import Command
23
- from pygpt_net.core.ctx import Ctx
24
- from pygpt_net.core.db import Database
25
- from pygpt_net.core.debug import Debug
26
- from pygpt_net.core.dispatcher import Dispatcher
27
- from pygpt_net.core.experts import Experts
28
- from pygpt_net.core.idx import Idx
29
- from pygpt_net.core.installer import Installer
30
- from pygpt_net.core.filesystem import Filesystem
31
- from pygpt_net.core.history import History
32
- from pygpt_net.core.image import Image
33
- from pygpt_net.core.llm import LLM
34
- from pygpt_net.core.models import Models
35
- from pygpt_net.core.modes import Modes
36
- from pygpt_net.core.notepad import Notepad
37
- from pygpt_net.core.platforms import Platforms
38
- from pygpt_net.core.plugins import Plugins
39
- from pygpt_net.core.presets import Presets
40
- from pygpt_net.core.prompt import Prompt
41
- from pygpt_net.core.settings import Settings
42
- from pygpt_net.core.tabs import Tabs
43
- from pygpt_net.core.text import Text
44
- from pygpt_net.core.tokens import Tokens
45
- from pygpt_net.core.updater import Updater
46
- from pygpt_net.core.video import Video
47
- from pygpt_net.core.vision import Vision
48
- from pygpt_net.core.web import Web
12
+ from .config import Config
13
+ from .core.access import Access
14
+ from .core.agents import Agents
15
+ from .core.assistants import Assistants
16
+ from .core.attachments import Attachments
17
+ from .core.audio import Audio
18
+ from .core.bridge import Bridge
19
+ from .core.calendar import Calendar
20
+ from .core.camera import Camera
21
+ # from .core.chain import Chain
22
+ from .core.command import Command
23
+ from .core.ctx import Ctx
24
+ from .core.db import Database
25
+ from .core.debug import Debug
26
+ from .core.dispatcher import Dispatcher
27
+ from .core.experts import Experts
28
+ from .core.idx import Idx
29
+ from .core.installer import Installer
30
+ from .core.filesystem import Filesystem
31
+ from .core.history import History
32
+ from .core.image import Image
33
+ from .core.llm import LLM
34
+ from .core.models import Models
35
+ from .core.modes import Modes
36
+ from .core.notepad import Notepad
37
+ from .core.platforms import Platforms
38
+ from .core.plugins import Plugins
39
+ from .core.presets import Presets
40
+ from .core.prompt import Prompt
41
+ from .core.settings import Settings
42
+ from .core.tabs import Tabs
43
+ from .core.text import Text
44
+ from .core.tokens import Tokens
45
+ from .core.updater import Updater
46
+ from .core.video import Video
47
+ from .core.vision import Vision
48
+ from .core.web import Web
49
49
 
50
- from pygpt_net.provider.api import Api
50
+ from .provider.api import Api
51
51
 
52
52
  class Core:
53
53
  def __init__(self, window=None):
@@ -6,42 +6,43 @@
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.01 23:00:00 #
9
+ # Updated Date: 2025.09.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from pygpt_net.controller.access import Access
13
- from pygpt_net.controller.agent import Agent
14
- from pygpt_net.controller.assistant import Assistant
15
- from pygpt_net.controller.attachment import Attachment
16
- from pygpt_net.controller.audio import Audio
17
- from pygpt_net.controller.calendar import Calendar
18
- from pygpt_net.controller.camera import Camera
19
- from pygpt_net.controller.chat import Chat
20
- from pygpt_net.controller.command import Command
21
- from pygpt_net.controller.config import Config
22
- from pygpt_net.controller.ctx import Ctx
23
- from pygpt_net.controller.debug import Debug
24
- from pygpt_net.controller.dialogs import Dialogs
25
- from pygpt_net.controller.files import Files
26
- from pygpt_net.controller.finder import Finder
27
- from pygpt_net.controller.idx import Idx
28
- from pygpt_net.controller.kernel import Kernel
29
- from pygpt_net.controller.lang import Lang
30
- from pygpt_net.controller.launcher import Launcher
31
- from pygpt_net.controller.layout import Layout
32
- from pygpt_net.controller.media import Media
33
- from pygpt_net.controller.mode import Mode
34
- from pygpt_net.controller.model import Model
35
- from pygpt_net.controller.notepad import Notepad
36
- from pygpt_net.controller.painter import Painter
37
- from pygpt_net.controller.plugins import Plugins
38
- from pygpt_net.controller.realtime import Realtime
39
- from pygpt_net.controller.presets import Presets
40
- from pygpt_net.controller.settings import Settings
41
- from pygpt_net.controller.theme import Theme
42
- from pygpt_net.controller.tools import Tools
43
- from pygpt_net.controller.ui import UI
44
- from pygpt_net.utils import trans
12
+ from .access import Access
13
+ from .agent import Agent
14
+ from .assistant import Assistant
15
+ from .attachment import Attachment
16
+ from .audio import Audio
17
+ from .calendar import Calendar
18
+ from .camera import Camera
19
+ from .chat import Chat
20
+ from .command import Command
21
+ from .config import Config
22
+ from .ctx import Ctx
23
+ from .debug import Debug
24
+ from .dialogs import Dialogs
25
+ from .files import Files
26
+ from .finder import Finder
27
+ from .idx import Idx
28
+ from .kernel import Kernel
29
+ from .lang import Lang
30
+ from .launcher import Launcher
31
+ from .layout import Layout
32
+ from .media import Media
33
+ from .mode import Mode
34
+ from .model import Model
35
+ from .notepad import Notepad
36
+ from .painter import Painter
37
+ from .plugins import Plugins
38
+ from .realtime import Realtime
39
+ from .presets import Presets
40
+ from .settings import Settings
41
+ from .theme import Theme
42
+ from .tools import Tools
43
+ from .ui import UI
44
+
45
+ from pygpt_net.utils import trans, mem_clean
45
46
 
46
47
 
47
48
  class Controller:
@@ -154,36 +155,45 @@ class Controller:
154
155
 
155
156
  print(trans("status.reloading.profile.begin"))
156
157
 
157
- self.window.core.reload() # db, config, patch, etc.
158
- self.ui.tabs.reload()
159
- self.ctx.reload()
160
- self.settings.reload()
161
- self.assistant.reload()
162
- self.attachment.reload()
163
- self.presets.reload()
164
- self.idx.reload()
165
- self.agent.reload()
166
- self.calendar.reload()
167
- self.plugins.reload()
168
- self.painter.reload()
169
- self.notepad.reload()
170
- self.files.reload()
171
- self.lang.reload()
172
- self.debug.reload()
173
- self.chat.reload()
174
- self.media.reload()
175
- self.window.tools.on_reload()
176
- self.access.reload()
177
- self.tools.reload()
178
-
179
- # post-reload
180
- self.ui.tabs.reload_after()
181
- self.ctx.reload_after()
182
- self.ui.tabs.restore_data() # restore opened tabs data
183
- self.kernel.restart()
184
- self.theme.reload_all() # do not reload theme if no change
158
+ try:
159
+ self.window.core.reload() # db, config, patch, etc.
160
+ self.ui.tabs.reload()
161
+ self.ctx.reload()
162
+ self.settings.reload()
163
+ self.assistant.reload()
164
+ self.attachment.reload()
165
+ self.presets.reload()
166
+ self.idx.reload()
167
+ self.agent.reload()
168
+ self.calendar.reload()
169
+ self.plugins.reload()
170
+ self.painter.reload()
171
+ self.notepad.reload()
172
+ self.files.reload()
173
+ self.lang.reload()
174
+ self.debug.reload()
175
+ self.chat.reload()
176
+ self.media.reload()
177
+ self.window.tools.on_reload()
178
+ self.access.reload()
179
+ self.tools.reload()
180
+
181
+ # post-reload
182
+ self.ui.tabs.reload_after()
183
+ self.ctx.reload_after()
184
+ self.ui.tabs.restore_data() # restore opened tabs data
185
+ self.kernel.restart()
186
+ self.theme.reload_all() # do not reload theme if no change
187
+
188
+ except Exception as e:
189
+ self.window.core.debug.log(e)
185
190
 
186
191
  self.reloading = False # unlock
187
192
  self.presets.unlock()
188
193
 
194
+ try:
195
+ mem_clean(force=True) # try to clean memory
196
+ except Exception:
197
+ pass
198
+
189
199
  print(trans("status.reloading.profile.end"))
@@ -113,13 +113,6 @@ class Common:
113
113
  pinned_radio = nodes['filter.ctx.radio.pinned']
114
114
  indexed_radio = nodes['filter.ctx.radio.indexed']
115
115
 
116
- try:
117
- b1 = QSignalBlocker(all_radio)
118
- b2 = QSignalBlocker(pinned_radio)
119
- b3 = QSignalBlocker(indexed_radio)
120
- except:
121
- pass
122
-
123
116
  all_radio.setChecked(False)
124
117
  pinned_radio.setChecked(False)
125
118
  indexed_radio.setChecked(False)
@@ -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.24 23:00:00 #
9
+ # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional, List
@@ -47,6 +47,7 @@ class Ctx:
47
47
  # current group ID
48
48
  self.group_id = None
49
49
  self.selected = []
50
+ self._infinite_scroll_refresh = False
50
51
 
51
52
  def handle(self, event: BaseEvent):
52
53
  """
@@ -99,7 +100,10 @@ class Ctx:
99
100
 
100
101
  def setup(self):
101
102
  """Setup ctx"""
102
- self.common.restore_display_filter() # load filters first
103
+ try:
104
+ self.common.restore_display_filter() # load filters first
105
+ except Exception:
106
+ pass
103
107
 
104
108
  # load ctx list
105
109
  core = self.window.core
@@ -110,7 +114,9 @@ class Ctx:
110
114
  self.new()
111
115
  else:
112
116
  id = core.config.get('ctx')
113
- if id is not None and core.ctx.has(id):
117
+ if id is not None:
118
+ # Keep previously selected id; if it's not in the current page
119
+ # we will inject a placeholder into the list (see update_list).
114
120
  core.ctx.set_current(id)
115
121
  else:
116
122
  core.ctx.set_current(core.ctx.get_first())
@@ -395,13 +401,57 @@ class Ctx:
395
401
  :param reload: reload ctx list items
396
402
  :param restore_scroll: restore scroll position
397
403
  """
404
+ view = self.window.ui.nodes['ctx.list']
405
+
406
+ # Read raw scrollbar state BEFORE model rebuild
407
+ sb = None
408
+ prev_val = None
409
+ if restore_scroll:
410
+ try:
411
+ sb = view.verticalScrollBar()
412
+ prev_val = sb.value()
413
+ except Exception:
414
+ sb = None
415
+ prev_val = None
416
+
417
+ # In infinite-scroll mode do NOT call store/restore helpers to avoid collisions.
418
+ # Instead, schedule a pending scroll that will be applied while updates are disabled.
419
+ if self._infinite_scroll_refresh:
420
+ if prev_val is not None:
421
+ try:
422
+ view.set_pending_v_scroll(prev_val)
423
+ except Exception:
424
+ pass
425
+ else:
426
+ # Normal mode: keep existing helpers
427
+ try:
428
+ view.store_scroll_position()
429
+ except Exception:
430
+ pass
431
+
432
+ # Prepare data (with placeholder injection if current is outside of the page)
433
+ data = self.window.core.ctx.get_meta(reload)
434
+ data = self._inject_current_if_missing(data)
435
+
436
+ # Rebuild list (CtxList.update will apply pending scroll before enabling updates)
398
437
  self.window.ui.contexts.ctx_list.update(
399
438
  'ctx.list',
400
- self.window.core.ctx.get_meta(reload),
439
+ data,
401
440
  expand=False,
402
441
  )
442
+
443
+ # Post-fix: in normal mode let helpers restore; in infinite mode we already applied pending
403
444
  if restore_scroll:
404
- self.window.ui.nodes['ctx.list'].restore_scroll_position()
445
+ if self._infinite_scroll_refresh:
446
+ # Optional safety re-apply to the same value after layout settles
447
+ if sb is not None and prev_val is not None:
448
+ QTimer.singleShot(0, lambda: sb.setValue(prev_val))
449
+ else:
450
+ try:
451
+ view.restore_scroll_position()
452
+ QTimer.singleShot(0, view.restore_scroll_position)
453
+ except Exception:
454
+ pass
405
455
 
406
456
  def refresh(self, restore_model: bool = True):
407
457
  """
@@ -429,7 +479,8 @@ class Ctx:
429
479
  id: int,
430
480
  restore_model: bool = True,
431
481
  select_idx: Optional[int] = None,
432
- new_tab: Optional[bool] = False
482
+ new_tab: Optional[bool] = False,
483
+ no_fresh: bool = False
433
484
  ):
434
485
  """
435
486
  Load ctx data
@@ -438,6 +489,7 @@ class Ctx:
438
489
  :param restore_model: restore model if defined in ctx
439
490
  :param select_idx: select index on list after loading
440
491
  :param new_tab: open in new tab
492
+ :param no_fresh: do not fresh output
441
493
  """
442
494
  if new_tab:
443
495
  col_idx = self.window.controller.ui.tabs.column_idx
@@ -450,7 +502,7 @@ class Ctx:
450
502
  if meta is not None:
451
503
  self.set_group(meta.group_id)
452
504
 
453
- if meta is not None:
505
+ if meta is not None and not no_fresh:
454
506
  self.fresh_output(meta)
455
507
 
456
508
  self.reload_config()
@@ -820,6 +872,7 @@ class Ctx:
820
872
 
821
873
  :param text: search string
822
874
  """
875
+ self.reset_loaded_total() # reset paging on new search
823
876
  self.window.core.ctx.clear_tmp_meta()
824
877
  self.window.core.ctx.set_search_string(text)
825
878
  self.window.core.config.set('ctx.search.string', text)
@@ -845,6 +898,7 @@ class Ctx:
845
898
 
846
899
  :param labels: list of labels
847
900
  """
901
+ self.reset_loaded_total() # reset paging on label filter change
848
902
  self.window.core.ctx.clear_tmp_meta()
849
903
  self.window.core.ctx.filters_labels = labels
850
904
  self.window.core.config.set('ctx.records.filter.labels', labels)
@@ -1235,6 +1289,7 @@ class Ctx:
1235
1289
 
1236
1290
  def reload(self):
1237
1291
  """Reload ctx"""
1292
+ self.reset_loaded_total() # reset paging
1238
1293
  self.window.core.ctx.reset()
1239
1294
  self.setup()
1240
1295
  self.update()
@@ -1305,4 +1360,117 @@ class Ctx:
1305
1360
  "meta": meta,
1306
1361
  }
1307
1362
  event = RenderEvent(RenderEvent.ON_LOAD, data)
1308
- self.window.dispatch(event)
1363
+ self.window.dispatch(event)
1364
+
1365
+ def get_package_limit(self) -> int:
1366
+ """Return base package size from config."""
1367
+ try:
1368
+ return int(self.window.core.config.get('ctx.records.limit') or 0)
1369
+ except Exception:
1370
+ return 0
1371
+
1372
+ def get_loaded_total_limit(self) -> int:
1373
+ """Return persisted total loaded size; fallback to base package size."""
1374
+ base = self.get_package_limit()
1375
+ try:
1376
+ val = int(self.window.core.config.get('ctx.records.limit.total') or 0)
1377
+ except Exception:
1378
+ val = 0
1379
+ return val if val > 0 else base
1380
+
1381
+ def reset_loaded_total(self):
1382
+ """Reset total loaded to base package; used on filter/search changes."""
1383
+ base = self.get_package_limit()
1384
+ # If base is 0 (unlimited), keep total as 0 which means "unlimited"
1385
+ self.window.core.config.set('ctx.records.limit.total', base if base > 0 else 0)
1386
+ self.window.core.config.save()
1387
+
1388
+ def load_more(self, packages: int = 1):
1389
+ """
1390
+ Increase total loaded limit and append new ungrouped records without rebuilding the list.
1391
+ """
1392
+ base = self.get_package_limit()
1393
+ if base <= 0:
1394
+ # Unlimited mode – nothing to do
1395
+ return
1396
+
1397
+ current_total = self.get_loaded_total_limit()
1398
+ new_total = current_total + max(1, int(packages)) * base
1399
+ self.window.core.config.set('ctx.records.limit.total', new_total)
1400
+ self.window.core.config.save()
1401
+
1402
+ # If folders are at top (default), we can safely append without rebuilding.
1403
+ # Otherwise, fall back to safe rebuild with stable scroll restore.
1404
+ folders_top = bool(self.window.core.config.get("ctx.records.folders.top"))
1405
+
1406
+ # Pull fresh meta with increased total
1407
+ data = self.window.core.ctx.get_meta(reload=True)
1408
+
1409
+ if folders_top:
1410
+ # compute which ungrouped & not pinned IDs are already visible
1411
+ view = self.window.ui.nodes['ctx.list']
1412
+ visible_ids = set()
1413
+ try:
1414
+ visible_ids = view.get_visible_unpaged_ids()
1415
+ except Exception:
1416
+ pass
1417
+
1418
+ # build candidate list in provider order
1419
+ candidates = []
1420
+ for mid, meta in data.items():
1421
+ if (meta.group_id is None or meta.group_id == 0) and not meta.important:
1422
+ candidates.append(mid)
1423
+
1424
+ # filter only new ones (keep order)
1425
+ add_ids = [mid for mid in candidates if mid not in visible_ids]
1426
+
1427
+ # append without rebuilding
1428
+ self.window.ui.contexts.ctx_list.append_unpaginated('ctx.list', data, add_ids)
1429
+ else:
1430
+ # Fallback: rare config where groups are placed after items – do a stable rebuild
1431
+ view = self.window.ui.nodes['ctx.list']
1432
+ try:
1433
+ sb = view.verticalScrollBar()
1434
+ prev_val = sb.value()
1435
+ # mark special refresh to avoid collisions with helpers
1436
+ self._infinite_scroll_refresh = True
1437
+ # set pending to preserve old position during rebuild
1438
+ view.set_pending_v_scroll(prev_val)
1439
+ except Exception:
1440
+ prev_val = None
1441
+
1442
+ try:
1443
+ self.update_list(reload=True, restore_scroll=True)
1444
+ finally:
1445
+ self._infinite_scroll_refresh = False
1446
+
1447
+ def _inject_current_if_missing(self, data: dict) -> dict:
1448
+ """
1449
+ Inject currently selected meta at the end of the list if it's not in the current page.
1450
+ This produces a 'placeholder' entry that will be replaced by the real one once loaded.
1451
+ """
1452
+ try:
1453
+ cur_id = self.window.core.ctx.get_current()
1454
+ if cur_id is None:
1455
+ cur_id = self.window.core.config.get('ctx')
1456
+ if not cur_id or cur_id in data:
1457
+ return data
1458
+
1459
+ provider = self.window.core.ctx.get_provider()
1460
+ meta = provider.get_meta_by_id(cur_id)
1461
+ if meta is None:
1462
+ return data # nothing to inject
1463
+
1464
+ # Mark as placeholder in 'extra' for potential future diagnostics; UI ignores this flag.
1465
+ try:
1466
+ if meta.extra is None:
1467
+ meta.extra = {}
1468
+ meta.extra['__placeholder'] = True
1469
+ except Exception:
1470
+ pass
1471
+
1472
+ injected = dict(data) # keep original ordering, append at the end
1473
+ injected[meta.id] = meta
1474
+ return injected
1475
+ except Exception:
1476
+ return data
@@ -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.07 22:00:00 #
9
+ # Updated Date: 2025.09.15 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtWidgets import QApplication
@@ -67,8 +67,8 @@ class Extra:
67
67
  :param value: block text
68
68
  """
69
69
  QApplication.clipboard().setText(value.strip())
70
- suffix = value[:20] + "..." if len(value) > 20 else value
71
- self.window.update_status(trans("clipboard.copied_to") + " " + suffix)
70
+ suffix = f"{value[:20]}..." if len(value) > 20 else value
71
+ self.window.update_status(f"{trans('clipboard.copied_to')} {suffix}")
72
72
 
73
73
  def preview_code_text(self, value: str):
74
74
  """
@@ -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.24 02:00:00 #
9
+ # Updated Date: 2025.09.15 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -163,6 +163,7 @@ class Editor:
163
163
  self.config_changed('ctx.records.groups.separators') or
164
164
  self.config_changed('ctx.records.pinned.separators') or
165
165
  self.config_changed('ctx.records.separators')):
166
+ self.window.controller.ctx.reset_loaded_total() # reset paging
166
167
  self.window.controller.ctx.update()
167
168
 
168
169
  # syntax highlighter style
@@ -320,6 +321,7 @@ class Editor:
320
321
  # update ctx limit
321
322
  elif key.startswith('ctx.records.limit') and caller == "slider":
322
323
  self.window.core.config.set(key, value)
324
+ self.window.controller.ctx.reset_loaded_total() # reset paging
323
325
  QTimer.singleShot(1000, lambda: self.window.controller.ctx.update(True, False))
324
326
 
325
327
  # update layout density
@@ -81,11 +81,17 @@ class Common:
81
81
  if state:
82
82
  for node in nodes:
83
83
  if node in self.window.ui.nodes:
84
- self.window.ui.nodes[node].setVisible(True)
84
+ try:
85
+ self.window.ui.nodes[node].setVisible(True)
86
+ except Exception:
87
+ pass
85
88
  else:
86
89
  for node in nodes:
87
90
  if node in self.window.ui.nodes:
88
- self.window.ui.nodes[node].setVisible(False)
91
+ try:
92
+ self.window.ui.nodes[node].setVisible(False)
93
+ except Exception:
94
+ pass
89
95
 
90
96
  self.window.ui.menu['theme.tooltips'].setChecked(state)
91
97