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.
- pygpt_net/CHANGELOG.txt +8 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app_core.py +39 -39
- pygpt_net/controller/__init__.py +72 -62
- pygpt_net/controller/ctx/common.py +0 -7
- pygpt_net/controller/ctx/ctx.py +176 -8
- pygpt_net/controller/ctx/extra.py +3 -3
- pygpt_net/controller/settings/editor.py +3 -1
- pygpt_net/controller/theme/common.py +8 -2
- pygpt_net/controller/ui/tabs.py +10 -43
- pygpt_net/core/ctx/ctx.py +79 -26
- pygpt_net/core/render/web/renderer.py +4 -10
- pygpt_net/core/tabs/tabs.py +50 -11
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/css/web-blocks.css +256 -270
- pygpt_net/data/css/web-chatgpt.css +276 -301
- pygpt_net/data/css/web-chatgpt_wide.css +286 -294
- pygpt_net/provider/core/config/patch.py +9 -0
- pygpt_net/provider/core/ctx/db_sqlite/storage.py +19 -5
- 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/__init__.py +9 -14
- pygpt_net/ui/layout/chat/chat.py +2 -2
- pygpt_net/ui/layout/ctx/ctx_list.py +71 -1
- pygpt_net/ui/widget/lists/base.py +32 -1
- pygpt_net/ui/widget/lists/context.py +45 -2
- pygpt_net/ui/widget/tabs/body.py +23 -1
- pygpt_net/ui/widget/textarea/web.py +85 -45
- {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/METADATA +10 -2
- {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/RECORD +35 -35
- {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.47.dist-info → pygpt_net-2.6.49.dist-info}/WHEEL +0 -0
- {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.
|
|
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.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.
|
|
9
|
+
# Updated Date: 2025.09.15 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
# from
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
from
|
|
27
|
-
from
|
|
28
|
-
from
|
|
29
|
-
from
|
|
30
|
-
from
|
|
31
|
-
from
|
|
32
|
-
from
|
|
33
|
-
from
|
|
34
|
-
from
|
|
35
|
-
from
|
|
36
|
-
from
|
|
37
|
-
from
|
|
38
|
-
from
|
|
39
|
-
from
|
|
40
|
-
from
|
|
41
|
-
from
|
|
42
|
-
from
|
|
43
|
-
from
|
|
44
|
-
from
|
|
45
|
-
from
|
|
46
|
-
from
|
|
47
|
-
from
|
|
48
|
-
from
|
|
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
|
|
50
|
+
from .provider.api import Api
|
|
51
51
|
|
|
52
52
|
class Core:
|
|
53
53
|
def __init__(self, window=None):
|
pygpt_net/controller/__init__.py
CHANGED
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.15 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
from
|
|
27
|
-
from
|
|
28
|
-
from
|
|
29
|
-
from
|
|
30
|
-
from
|
|
31
|
-
from
|
|
32
|
-
from
|
|
33
|
-
from
|
|
34
|
-
from
|
|
35
|
-
from
|
|
36
|
-
from
|
|
37
|
-
from
|
|
38
|
-
from
|
|
39
|
-
from
|
|
40
|
-
from
|
|
41
|
-
from
|
|
42
|
-
from
|
|
43
|
-
from
|
|
44
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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)
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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]
|
|
71
|
-
self.window.update_status(trans(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|