pygpt-net 2.6.46__py3-none-any.whl → 2.6.48__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 +9 -0
- pygpt_net/__init__.py +1 -1
- pygpt_net/app.py +5 -0
- pygpt_net/app_core.py +39 -39
- pygpt_net/controller/__init__.py +72 -64
- pygpt_net/controller/audio/audio.py +2 -0
- pygpt_net/controller/chat/text.py +2 -1
- pygpt_net/controller/ctx/common.py +0 -7
- pygpt_net/controller/ctx/ctx.py +172 -6
- pygpt_net/controller/ctx/extra.py +3 -3
- pygpt_net/controller/notepad/notepad.py +0 -2
- pygpt_net/controller/settings/editor.py +3 -1
- pygpt_net/controller/theme/common.py +8 -2
- pygpt_net/controller/theme/theme.py +5 -5
- pygpt_net/controller/ui/tabs.py +9 -2
- pygpt_net/core/ctx/ctx.py +79 -26
- pygpt_net/core/render/web/renderer.py +2 -0
- pygpt_net/core/tabs/tab.py +2 -2
- pygpt_net/core/tabs/tabs.py +57 -10
- pygpt_net/data/config/config.json +2 -2
- pygpt_net/data/config/models.json +2 -2
- 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/data/js/app.js +1218 -1186
- pygpt_net/js_rc.py +14192 -14641
- pygpt_net/provider/core/config/patch.py +9 -0
- pygpt_net/provider/core/ctx/db_sqlite/storage.py +19 -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 +11 -3
- pygpt_net/ui/widget/textarea/notepad.py +0 -4
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/METADATA +11 -2
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/RECORD +40 -40
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.46.dist-info → pygpt_net-2.6.48.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
2.6.48 (2025-09-15)
|
|
2
|
+
|
|
3
|
+
- Added: auto-loading of next items to the list of contexts when scrolling to the end of the list.
|
|
4
|
+
|
|
5
|
+
2.6.47 (2025-09-15)
|
|
6
|
+
|
|
7
|
+
- Improved: Parsing of custom markup tags.
|
|
8
|
+
- Optimized: Switching profiles.
|
|
9
|
+
|
|
1
10
|
2.6.46 (2025-09-15)
|
|
2
11
|
|
|
3
12
|
- Added: Global proxy settings for all API SDKs.
|
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.48"
|
|
17
17
|
__build__ = "2025-09-15"
|
|
18
18
|
__maintainer__ = "Marcin Szczygliński"
|
|
19
19
|
__github__ = "https://github.com/szczyglis-dev/py-gpt"
|
pygpt_net/app.py
CHANGED
|
@@ -35,6 +35,11 @@ os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = (
|
|
|
35
35
|
"--js-flags=--expose-gc"
|
|
36
36
|
)
|
|
37
37
|
"""
|
|
38
|
+
# by default, optimize for low-end devices
|
|
39
|
+
os.environ.setdefault(
|
|
40
|
+
"QTWEBENGINE_CHROMIUM_FLAGS",
|
|
41
|
+
"--enable-low-end-device-mode"
|
|
42
|
+
)
|
|
38
43
|
|
|
39
44
|
_original_open = builtins.open
|
|
40
45
|
|
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,38 +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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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)
|
|
187
190
|
|
|
188
191
|
self.reloading = False # unlock
|
|
189
192
|
self.presets.unlock()
|
|
190
193
|
|
|
194
|
+
try:
|
|
195
|
+
mem_clean(force=True) # try to clean memory
|
|
196
|
+
except Exception:
|
|
197
|
+
pass
|
|
198
|
+
|
|
191
199
|
print(trans("status.reloading.profile.end"))
|
|
@@ -449,6 +449,8 @@ class Audio:
|
|
|
449
449
|
if is_enabled:
|
|
450
450
|
# show/hide extra options
|
|
451
451
|
tab = self.window.controller.ui.tabs.get_current_tab()
|
|
452
|
+
if not tab:
|
|
453
|
+
return
|
|
452
454
|
if tab.type == Tab.TAB_NOTEPAD:
|
|
453
455
|
self.window.controller.audio.ui.on_input_continuous_enable("input")
|
|
454
456
|
else:
|
|
@@ -92,7 +92,8 @@ class Text:
|
|
|
92
92
|
|
|
93
93
|
# create ctx item
|
|
94
94
|
meta = core.ctx.get_current_meta()
|
|
95
|
-
meta
|
|
95
|
+
if meta:
|
|
96
|
+
meta.preset = config.get("preset") # current preset
|
|
96
97
|
|
|
97
98
|
ctx = CtxItem()
|
|
98
99
|
ctx.meta = meta # CtxMeta (owner object)
|
|
@@ -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.15 22: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
|
"""
|
|
@@ -820,6 +870,7 @@ class Ctx:
|
|
|
820
870
|
|
|
821
871
|
:param text: search string
|
|
822
872
|
"""
|
|
873
|
+
self.reset_loaded_total() # reset paging on new search
|
|
823
874
|
self.window.core.ctx.clear_tmp_meta()
|
|
824
875
|
self.window.core.ctx.set_search_string(text)
|
|
825
876
|
self.window.core.config.set('ctx.search.string', text)
|
|
@@ -845,6 +896,7 @@ class Ctx:
|
|
|
845
896
|
|
|
846
897
|
:param labels: list of labels
|
|
847
898
|
"""
|
|
899
|
+
self.reset_loaded_total() # reset paging on label filter change
|
|
848
900
|
self.window.core.ctx.clear_tmp_meta()
|
|
849
901
|
self.window.core.ctx.filters_labels = labels
|
|
850
902
|
self.window.core.config.set('ctx.records.filter.labels', labels)
|
|
@@ -1235,6 +1287,7 @@ class Ctx:
|
|
|
1235
1287
|
|
|
1236
1288
|
def reload(self):
|
|
1237
1289
|
"""Reload ctx"""
|
|
1290
|
+
self.reset_loaded_total() # reset paging
|
|
1238
1291
|
self.window.core.ctx.reset()
|
|
1239
1292
|
self.setup()
|
|
1240
1293
|
self.update()
|
|
@@ -1305,4 +1358,117 @@ class Ctx:
|
|
|
1305
1358
|
"meta": meta,
|
|
1306
1359
|
}
|
|
1307
1360
|
event = RenderEvent(RenderEvent.ON_LOAD, data)
|
|
1308
|
-
self.window.dispatch(event)
|
|
1361
|
+
self.window.dispatch(event)
|
|
1362
|
+
|
|
1363
|
+
def get_package_limit(self) -> int:
|
|
1364
|
+
"""Return base package size from config."""
|
|
1365
|
+
try:
|
|
1366
|
+
return int(self.window.core.config.get('ctx.records.limit') or 0)
|
|
1367
|
+
except Exception:
|
|
1368
|
+
return 0
|
|
1369
|
+
|
|
1370
|
+
def get_loaded_total_limit(self) -> int:
|
|
1371
|
+
"""Return persisted total loaded size; fallback to base package size."""
|
|
1372
|
+
base = self.get_package_limit()
|
|
1373
|
+
try:
|
|
1374
|
+
val = int(self.window.core.config.get('ctx.records.limit.total') or 0)
|
|
1375
|
+
except Exception:
|
|
1376
|
+
val = 0
|
|
1377
|
+
return val if val > 0 else base
|
|
1378
|
+
|
|
1379
|
+
def reset_loaded_total(self):
|
|
1380
|
+
"""Reset total loaded to base package; used on filter/search changes."""
|
|
1381
|
+
base = self.get_package_limit()
|
|
1382
|
+
# If base is 0 (unlimited), keep total as 0 which means "unlimited"
|
|
1383
|
+
self.window.core.config.set('ctx.records.limit.total', base if base > 0 else 0)
|
|
1384
|
+
self.window.core.config.save()
|
|
1385
|
+
|
|
1386
|
+
def load_more(self, packages: int = 1):
|
|
1387
|
+
"""
|
|
1388
|
+
Increase total loaded limit and append new ungrouped records without rebuilding the list.
|
|
1389
|
+
"""
|
|
1390
|
+
base = self.get_package_limit()
|
|
1391
|
+
if base <= 0:
|
|
1392
|
+
# Unlimited mode – nothing to do
|
|
1393
|
+
return
|
|
1394
|
+
|
|
1395
|
+
current_total = self.get_loaded_total_limit()
|
|
1396
|
+
new_total = current_total + max(1, int(packages)) * base
|
|
1397
|
+
self.window.core.config.set('ctx.records.limit.total', new_total)
|
|
1398
|
+
self.window.core.config.save()
|
|
1399
|
+
|
|
1400
|
+
# If folders are at top (default), we can safely append without rebuilding.
|
|
1401
|
+
# Otherwise, fall back to safe rebuild with stable scroll restore.
|
|
1402
|
+
folders_top = bool(self.window.core.config.get("ctx.records.folders.top"))
|
|
1403
|
+
|
|
1404
|
+
# Pull fresh meta with increased total
|
|
1405
|
+
data = self.window.core.ctx.get_meta(reload=True)
|
|
1406
|
+
|
|
1407
|
+
if folders_top:
|
|
1408
|
+
# compute which ungrouped & not pinned IDs are already visible
|
|
1409
|
+
view = self.window.ui.nodes['ctx.list']
|
|
1410
|
+
visible_ids = set()
|
|
1411
|
+
try:
|
|
1412
|
+
visible_ids = view.get_visible_unpaged_ids()
|
|
1413
|
+
except Exception:
|
|
1414
|
+
pass
|
|
1415
|
+
|
|
1416
|
+
# build candidate list in provider order
|
|
1417
|
+
candidates = []
|
|
1418
|
+
for mid, meta in data.items():
|
|
1419
|
+
if (meta.group_id is None or meta.group_id == 0) and not meta.important:
|
|
1420
|
+
candidates.append(mid)
|
|
1421
|
+
|
|
1422
|
+
# filter only new ones (keep order)
|
|
1423
|
+
add_ids = [mid for mid in candidates if mid not in visible_ids]
|
|
1424
|
+
|
|
1425
|
+
# append without rebuilding
|
|
1426
|
+
self.window.ui.contexts.ctx_list.append_unpaginated('ctx.list', data, add_ids)
|
|
1427
|
+
else:
|
|
1428
|
+
# Fallback: rare config where groups are placed after items – do a stable rebuild
|
|
1429
|
+
view = self.window.ui.nodes['ctx.list']
|
|
1430
|
+
try:
|
|
1431
|
+
sb = view.verticalScrollBar()
|
|
1432
|
+
prev_val = sb.value()
|
|
1433
|
+
# mark special refresh to avoid collisions with helpers
|
|
1434
|
+
self._infinite_scroll_refresh = True
|
|
1435
|
+
# set pending to preserve old position during rebuild
|
|
1436
|
+
view.set_pending_v_scroll(prev_val)
|
|
1437
|
+
except Exception:
|
|
1438
|
+
prev_val = None
|
|
1439
|
+
|
|
1440
|
+
try:
|
|
1441
|
+
self.update_list(reload=True, restore_scroll=True)
|
|
1442
|
+
finally:
|
|
1443
|
+
self._infinite_scroll_refresh = False
|
|
1444
|
+
|
|
1445
|
+
def _inject_current_if_missing(self, data: dict) -> dict:
|
|
1446
|
+
"""
|
|
1447
|
+
Inject currently selected meta at the end of the list if it's not in the current page.
|
|
1448
|
+
This produces a 'placeholder' entry that will be replaced by the real one once loaded.
|
|
1449
|
+
"""
|
|
1450
|
+
try:
|
|
1451
|
+
cur_id = self.window.core.ctx.get_current()
|
|
1452
|
+
if cur_id is None:
|
|
1453
|
+
cur_id = self.window.core.config.get('ctx')
|
|
1454
|
+
if not cur_id or cur_id in data:
|
|
1455
|
+
return data
|
|
1456
|
+
|
|
1457
|
+
provider = self.window.core.ctx.get_provider()
|
|
1458
|
+
meta = provider.get_meta_by_id(cur_id)
|
|
1459
|
+
if meta is None:
|
|
1460
|
+
return data # nothing to inject
|
|
1461
|
+
|
|
1462
|
+
# Mark as placeholder in 'extra' for potential future diagnostics; UI ignores this flag.
|
|
1463
|
+
try:
|
|
1464
|
+
if meta.extra is None:
|
|
1465
|
+
meta.extra = {}
|
|
1466
|
+
meta.extra['__placeholder'] = True
|
|
1467
|
+
except Exception:
|
|
1468
|
+
pass
|
|
1469
|
+
|
|
1470
|
+
injected = dict(data) # keep original ordering, append at the end
|
|
1471
|
+
injected[meta.id] = meta
|
|
1472
|
+
return injected
|
|
1473
|
+
except Exception:
|
|
1474
|
+
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
|
|
|
@@ -34,6 +34,7 @@ class Theme:
|
|
|
34
34
|
self.markdown = Markdown(window)
|
|
35
35
|
self.menu = Menu(window)
|
|
36
36
|
self.nodes = Nodes(window)
|
|
37
|
+
self.current_theme = None
|
|
37
38
|
|
|
38
39
|
def setup(self):
|
|
39
40
|
"""Setup theme"""
|
|
@@ -192,7 +193,8 @@ class Theme:
|
|
|
192
193
|
|
|
193
194
|
:param force: force theme change (manual trigger)
|
|
194
195
|
"""
|
|
195
|
-
self.
|
|
196
|
+
self.current_theme = self.window.core.config.get('theme')
|
|
197
|
+
self.toggle(self.current_theme, force=force)
|
|
196
198
|
|
|
197
199
|
def update_syntax(self):
|
|
198
200
|
"""Update syntax menu"""
|
|
@@ -280,13 +282,11 @@ class Theme:
|
|
|
280
282
|
"""
|
|
281
283
|
return self.common.get_style(element)
|
|
282
284
|
|
|
283
|
-
def reload_all(self
|
|
285
|
+
def reload_all(self):
|
|
284
286
|
"""
|
|
285
287
|
Reload all
|
|
286
|
-
|
|
287
|
-
:param prev_theme: previous theme name
|
|
288
288
|
"""
|
|
289
|
-
if
|
|
289
|
+
if self.current_theme != self.window.core.config.get('theme'):
|
|
290
290
|
self.setup()
|
|
291
291
|
self.update_style()
|
|
292
292
|
self.update_syntax()
|