pygpt-net 2.6.67__py3-none-any.whl → 2.7.1__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 +20 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/assistant/assistant.py +13 -8
- pygpt_net/controller/assistant/batch.py +29 -15
- pygpt_net/controller/assistant/files.py +19 -14
- pygpt_net/controller/assistant/store.py +63 -41
- pygpt_net/controller/attachment/attachment.py +45 -35
- pygpt_net/controller/chat/attachment.py +50 -39
- pygpt_net/controller/config/field/dictionary.py +26 -14
- pygpt_net/controller/ctx/common.py +27 -17
- pygpt_net/controller/ctx/ctx.py +185 -101
- pygpt_net/controller/files/files.py +101 -41
- pygpt_net/controller/idx/indexer.py +87 -31
- pygpt_net/controller/kernel/kernel.py +13 -2
- pygpt_net/controller/mode/mode.py +3 -3
- pygpt_net/controller/model/editor.py +70 -15
- pygpt_net/controller/model/importer.py +153 -54
- pygpt_net/controller/painter/common.py +43 -11
- pygpt_net/controller/painter/painter.py +2 -2
- pygpt_net/controller/presets/experts.py +68 -15
- pygpt_net/controller/presets/presets.py +72 -36
- pygpt_net/controller/settings/profile.py +76 -35
- pygpt_net/controller/settings/workdir.py +70 -39
- pygpt_net/core/assistants/files.py +20 -18
- pygpt_net/core/filesystem/actions.py +111 -10
- pygpt_net/core/filesystem/filesystem.py +72 -1
- pygpt_net/core/filesystem/packer.py +161 -1
- pygpt_net/core/idx/idx.py +12 -11
- pygpt_net/core/idx/worker.py +13 -1
- pygpt_net/core/image/image.py +2 -2
- pygpt_net/core/models/models.py +4 -4
- pygpt_net/core/profile/profile.py +13 -3
- pygpt_net/core/video/video.py +2 -3
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/css/style.dark.css +45 -0
- pygpt_net/data/css/style.light.css +46 -0
- pygpt_net/data/locale/locale.de.ini +5 -1
- pygpt_net/data/locale/locale.en.ini +5 -1
- pygpt_net/data/locale/locale.es.ini +5 -1
- pygpt_net/data/locale/locale.fr.ini +5 -1
- pygpt_net/data/locale/locale.it.ini +5 -1
- pygpt_net/data/locale/locale.pl.ini +6 -2
- pygpt_net/data/locale/locale.uk.ini +5 -1
- pygpt_net/data/locale/locale.zh.ini +5 -1
- pygpt_net/provider/api/openai/__init__.py +4 -2
- pygpt_net/provider/core/config/patch.py +17 -1
- pygpt_net/tools/image_viewer/tool.py +17 -0
- pygpt_net/tools/text_editor/tool.py +9 -0
- pygpt_net/ui/__init__.py +2 -2
- pygpt_net/ui/dialog/preset.py +1 -0
- pygpt_net/ui/layout/ctx/ctx_list.py +16 -6
- pygpt_net/ui/layout/toolbox/image.py +2 -1
- pygpt_net/ui/layout/toolbox/indexes.py +2 -0
- pygpt_net/ui/layout/toolbox/video.py +5 -1
- pygpt_net/ui/main.py +3 -1
- pygpt_net/ui/widget/calendar/select.py +3 -3
- pygpt_net/ui/widget/draw/painter.py +238 -51
- pygpt_net/ui/widget/filesystem/explorer.py +1164 -142
- pygpt_net/ui/widget/lists/assistant.py +185 -24
- pygpt_net/ui/widget/lists/assistant_store.py +245 -42
- pygpt_net/ui/widget/lists/attachment.py +230 -47
- pygpt_net/ui/widget/lists/attachment_ctx.py +189 -33
- pygpt_net/ui/widget/lists/base_list_combo.py +2 -2
- pygpt_net/ui/widget/lists/context.py +1253 -70
- pygpt_net/ui/widget/lists/experts.py +110 -8
- pygpt_net/ui/widget/lists/model_editor.py +217 -14
- pygpt_net/ui/widget/lists/model_importer.py +125 -6
- pygpt_net/ui/widget/lists/preset.py +460 -71
- pygpt_net/ui/widget/lists/profile.py +149 -27
- pygpt_net/ui/widget/lists/uploaded.py +230 -38
- pygpt_net/ui/widget/option/combo.py +1211 -33
- pygpt_net/ui/widget/option/dictionary.py +35 -7
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/METADATA +22 -57
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/RECORD +78 -78
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.67.dist-info → pygpt_net-2.7.1.dist-info}/entry_points.txt +0 -0
|
@@ -6,13 +6,14 @@
|
|
|
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.12.27 21:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
13
13
|
import os
|
|
14
14
|
from typing import List, Dict, Optional, Any
|
|
15
15
|
|
|
16
|
+
from PySide6 import QtCore
|
|
16
17
|
from PySide6.QtWidgets import QApplication
|
|
17
18
|
|
|
18
19
|
from pygpt_net.core.events import Event
|
|
@@ -45,8 +46,10 @@ class Importer:
|
|
|
45
46
|
self.items_current = {} # current models in use
|
|
46
47
|
self.pending = {} # waiting to be imported models
|
|
47
48
|
self.removed = {} # waiting to be removed models
|
|
48
|
-
self.selected_available = None # selected available model
|
|
49
|
-
self.selected_current = None # selected current model
|
|
49
|
+
self.selected_available = None # selected available model (single)
|
|
50
|
+
self.selected_current = None # selected current model (single)
|
|
51
|
+
self.selected_available_ids: List[str] = [] # multi-selected available models
|
|
52
|
+
self.selected_current_ids: List[str] = [] # multi-selected current models
|
|
50
53
|
self.all = False # show all models, not only available for import
|
|
51
54
|
self.provider = "_" # default provider
|
|
52
55
|
|
|
@@ -69,43 +72,71 @@ class Importer:
|
|
|
69
72
|
|
|
70
73
|
def change_available(self):
|
|
71
74
|
"""On change available model selection"""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if
|
|
75
|
+
view = self.window.ui.nodes["models.importer.available"]
|
|
76
|
+
sel_model = view.selectionModel()
|
|
77
|
+
rows = sel_model.selectedRows() if sel_model else []
|
|
78
|
+
# collect selected model keys by reading stored tooltip role (stable ID)
|
|
79
|
+
selected_ids = []
|
|
80
|
+
for ix in rows:
|
|
81
|
+
key = ix.data(QtCore.Qt.ToolTipRole)
|
|
82
|
+
if not key:
|
|
83
|
+
key = self.get_by_idx(ix.row(), self.items_available)
|
|
84
|
+
if key and key not in selected_ids:
|
|
85
|
+
selected_ids.append(key)
|
|
86
|
+
|
|
87
|
+
self.selected_available_ids = selected_ids
|
|
88
|
+
if not selected_ids:
|
|
75
89
|
self.selected_available = None
|
|
76
90
|
self.window.ui.nodes["models.importer.add"].setEnabled(False)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
# keep a single reference for backward compatibility (last selected)
|
|
94
|
+
self.selected_available = selected_ids[-1]
|
|
95
|
+
|
|
96
|
+
# enable add if any of selected can be added (not already in current)
|
|
97
|
+
can_add = False
|
|
98
|
+
for key in selected_ids:
|
|
99
|
+
if key is None:
|
|
100
|
+
continue
|
|
101
|
+
if not self.in_current(key) and self.items_available.get(key) is not None:
|
|
102
|
+
can_add = True
|
|
103
|
+
break
|
|
104
|
+
self.window.ui.nodes["models.importer.add"].setEnabled(can_add)
|
|
88
105
|
|
|
89
106
|
def change_current(self):
|
|
90
107
|
"""On change current model selection"""
|
|
91
108
|
if self.provider not in self.items_current:
|
|
92
109
|
self.items_current[self.provider] = {}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if
|
|
110
|
+
view = self.window.ui.nodes["models.importer.current"]
|
|
111
|
+
sel_model = view.selectionModel()
|
|
112
|
+
rows = sel_model.selectedRows() if sel_model else []
|
|
113
|
+
# collect selected model keys by reading stored tooltip role (stable ID)
|
|
114
|
+
selected_ids = []
|
|
115
|
+
for ix in rows:
|
|
116
|
+
key = ix.data(QtCore.Qt.ToolTipRole)
|
|
117
|
+
if not key:
|
|
118
|
+
key = self.get_by_idx(ix.row(), self.items_current[self.provider])
|
|
119
|
+
if key and key not in selected_ids:
|
|
120
|
+
selected_ids.append(key)
|
|
121
|
+
|
|
122
|
+
self.selected_current_ids = selected_ids
|
|
123
|
+
if not selected_ids:
|
|
96
124
|
self.selected_current = None
|
|
97
125
|
self.window.ui.nodes["models.importer.remove"].setEnabled(False)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
# keep a single reference for backward compatibility (last selected)
|
|
129
|
+
self.selected_current = selected_ids[-1]
|
|
130
|
+
|
|
131
|
+
# enable remove if any of selected can be removed
|
|
132
|
+
can_remove = False
|
|
133
|
+
for key in selected_ids:
|
|
134
|
+
if key is None:
|
|
135
|
+
continue
|
|
136
|
+
if (key in self.items_current[self.provider]) and self.in_available(key):
|
|
137
|
+
can_remove = True
|
|
138
|
+
break
|
|
139
|
+
self.window.ui.nodes["models.importer.remove"].setEnabled(can_remove)
|
|
109
140
|
|
|
110
141
|
def in_available(self, model: str) -> bool:
|
|
111
142
|
"""
|
|
@@ -122,43 +153,111 @@ class Importer:
|
|
|
122
153
|
return True
|
|
123
154
|
return False
|
|
124
155
|
|
|
156
|
+
def _selected_keys_from_view(self, node_id: str, items: Dict) -> List[str]:
|
|
157
|
+
"""
|
|
158
|
+
Resolve selected model keys from a given list view using stored tooltip role,
|
|
159
|
+
falling back to index->dict mapping when needed.
|
|
160
|
+
|
|
161
|
+
:param node_id: ui node id
|
|
162
|
+
:param items: dict used to build the view's model
|
|
163
|
+
:return: list of selected keys
|
|
164
|
+
"""
|
|
165
|
+
keys: List[str] = []
|
|
166
|
+
try:
|
|
167
|
+
view = self.window.ui.nodes[node_id]
|
|
168
|
+
sel_model = view.selectionModel()
|
|
169
|
+
rows = sel_model.selectedRows() if sel_model else []
|
|
170
|
+
for ix in rows:
|
|
171
|
+
key = ix.data(QtCore.Qt.ToolTipRole)
|
|
172
|
+
if not key:
|
|
173
|
+
key = self.get_by_idx(ix.row(), items)
|
|
174
|
+
if key and key not in keys:
|
|
175
|
+
keys.append(key)
|
|
176
|
+
except Exception:
|
|
177
|
+
pass
|
|
178
|
+
return keys
|
|
179
|
+
|
|
125
180
|
def add(self):
|
|
126
|
-
"""Add model to current list"""
|
|
181
|
+
"""Add model(s) to current list"""
|
|
127
182
|
if self.provider not in self.items_current:
|
|
128
183
|
self.items_current[self.provider] = {}
|
|
129
|
-
|
|
184
|
+
|
|
185
|
+
# collect multi-selection; fallback to single selection
|
|
186
|
+
keys = self._selected_keys_from_view(
|
|
187
|
+
'models.importer.available',
|
|
188
|
+
self.items_available,
|
|
189
|
+
)
|
|
190
|
+
if not keys and self.selected_available is not None:
|
|
191
|
+
keys = [self.selected_available]
|
|
192
|
+
|
|
193
|
+
if not keys:
|
|
130
194
|
self.set_status(trans('models.importer.error.add.no_model'))
|
|
131
195
|
return
|
|
132
|
-
|
|
196
|
+
|
|
197
|
+
any_added = False
|
|
198
|
+
for key in list(keys):
|
|
199
|
+
if key is None:
|
|
200
|
+
continue
|
|
201
|
+
if self.in_current(key):
|
|
202
|
+
continue
|
|
203
|
+
model = self.items_available.get(key)
|
|
204
|
+
if model is None:
|
|
205
|
+
continue
|
|
206
|
+
self.items_current[self.provider][key] = model
|
|
207
|
+
if key not in self.pending:
|
|
208
|
+
self.pending[key] = model
|
|
209
|
+
if key in self.removed:
|
|
210
|
+
del self.removed[key]
|
|
211
|
+
if not self.all and key in self.items_available:
|
|
212
|
+
del self.items_available[key]
|
|
213
|
+
any_added = True
|
|
214
|
+
|
|
215
|
+
if not any_added:
|
|
133
216
|
self.set_status(trans('models.importer.error.add.not_exists'))
|
|
134
217
|
return
|
|
135
|
-
|
|
136
|
-
self.items_current[self.provider][self.selected_available] = model
|
|
137
|
-
if self.selected_available not in self.pending:
|
|
138
|
-
self.pending[self.selected_available] = model
|
|
139
|
-
if self.selected_available in self.removed:
|
|
140
|
-
del self.removed[self.selected_available]
|
|
141
|
-
if not self.all:
|
|
142
|
-
del self.items_available[self.selected_available]
|
|
218
|
+
|
|
143
219
|
self.refresh()
|
|
144
220
|
|
|
145
221
|
def remove(self):
|
|
146
|
-
"""Remove model from current list"""
|
|
222
|
+
"""Remove model(s) from current list"""
|
|
147
223
|
if self.provider not in self.items_current:
|
|
148
224
|
self.items_current[self.provider] = {}
|
|
149
|
-
|
|
225
|
+
|
|
226
|
+
# collect multi-selection; fallback to single selection
|
|
227
|
+
keys = self._selected_keys_from_view(
|
|
228
|
+
'models.importer.current',
|
|
229
|
+
self.items_current[self.provider],
|
|
230
|
+
)
|
|
231
|
+
if not keys and self.selected_current is not None:
|
|
232
|
+
keys = [self.selected_current]
|
|
233
|
+
|
|
234
|
+
if not keys:
|
|
150
235
|
self.set_status(trans('models.importer.error.remove.no_model'))
|
|
151
236
|
return
|
|
152
|
-
|
|
237
|
+
|
|
238
|
+
any_removed = False
|
|
239
|
+
for key in list(keys):
|
|
240
|
+
if key is None:
|
|
241
|
+
continue
|
|
242
|
+
if not self.in_current(key):
|
|
243
|
+
continue
|
|
244
|
+
model = self.items_current[self.provider].get(key)
|
|
245
|
+
if model is None:
|
|
246
|
+
continue
|
|
247
|
+
# return to available list
|
|
248
|
+
self.items_available[key] = model
|
|
249
|
+
if key not in self.removed:
|
|
250
|
+
self.removed[key] = model
|
|
251
|
+
if key in self.items_current[self.provider]:
|
|
252
|
+
del self.items_current[self.provider][key]
|
|
253
|
+
if key in self.pending:
|
|
254
|
+
del self.pending[key]
|
|
255
|
+
any_removed = True
|
|
256
|
+
|
|
257
|
+
if not any_removed:
|
|
153
258
|
self.set_status(trans('models.importer.error.remove.not_exists'))
|
|
154
259
|
return
|
|
155
|
-
|
|
156
|
-
self.items_available[self.selected_current] = model
|
|
157
|
-
if self.selected_current not in self.removed:
|
|
158
|
-
self.removed[self.selected_current] = model
|
|
159
|
-
del self.items_current[self.provider][self.selected_current]
|
|
160
|
-
if self.selected_current in self.pending:
|
|
161
|
-
del self.pending[self.selected_current]
|
|
260
|
+
|
|
162
261
|
self.refresh()
|
|
163
262
|
|
|
164
263
|
def setup(self):
|
|
@@ -328,7 +427,7 @@ class Importer:
|
|
|
328
427
|
name = model.get('id')
|
|
329
428
|
if "name" in model:
|
|
330
429
|
name = model.get('name')
|
|
331
|
-
m = self.window.core.models.create_empty(append=False)
|
|
430
|
+
m, _ = self.window.core.models.create_empty(append=False)
|
|
332
431
|
m.id = id
|
|
333
432
|
m.name = name
|
|
334
433
|
m.mode = [
|
|
@@ -504,7 +603,7 @@ class Importer:
|
|
|
504
603
|
else:
|
|
505
604
|
for model in ollama_models:
|
|
506
605
|
name = model.get('name')
|
|
507
|
-
m = self.window.core.models.create_empty(append=False)
|
|
606
|
+
m, _ = self.window.core.models.create_empty(append=False)
|
|
508
607
|
m.id = name
|
|
509
608
|
m.name = name
|
|
510
609
|
m.mode = [
|
|
@@ -47,7 +47,12 @@ class Common:
|
|
|
47
47
|
:param width: Canvas width
|
|
48
48
|
:param height: Canvas height
|
|
49
49
|
"""
|
|
50
|
-
self.window.ui.painter
|
|
50
|
+
painter = self.window.ui.painter
|
|
51
|
+
if hasattr(painter, "set_canvas_size_pixels"):
|
|
52
|
+
painter.set_canvas_size_pixels(width, height)
|
|
53
|
+
else:
|
|
54
|
+
# required on image open
|
|
55
|
+
self.window.ui.painter.setFixedSize(QSize(width, height))
|
|
51
56
|
|
|
52
57
|
def set_brush_mode(self, enabled: bool):
|
|
53
58
|
"""
|
|
@@ -81,14 +86,21 @@ class Common:
|
|
|
81
86
|
if self._changing_canvas_size:
|
|
82
87
|
return
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
# Be resilient if combobox node is not present in a given UI layout
|
|
90
|
+
combo: Optional[QComboBox] = None
|
|
91
|
+
try:
|
|
92
|
+
if hasattr(self.window.ui, "nodes"):
|
|
93
|
+
combo = self.window.ui.nodes.get('painter.select.canvas.size', None)
|
|
94
|
+
except Exception:
|
|
95
|
+
combo = None
|
|
96
|
+
|
|
85
97
|
painter = self.window.ui.painter
|
|
86
98
|
|
|
87
99
|
# Heuristic to detect manual UI change vs programmatic call
|
|
88
100
|
# - manual if: no arg, or int index (Qt int overload), or arg equals currentText/currentData
|
|
89
101
|
raw_arg = selected
|
|
90
|
-
current_text = combo.currentText()
|
|
91
|
-
current_data = combo.currentData()
|
|
102
|
+
current_text = combo.currentText() if combo is not None else ""
|
|
103
|
+
current_data = combo.currentData() if combo is not None else None
|
|
92
104
|
current_data_str = current_data if isinstance(current_data, str) else None
|
|
93
105
|
is_manual = (
|
|
94
106
|
raw_arg is None
|
|
@@ -105,8 +117,15 @@ class Common:
|
|
|
105
117
|
if not selected_norm:
|
|
106
118
|
return
|
|
107
119
|
|
|
120
|
+
# Use true logical canvas size when available
|
|
121
|
+
if hasattr(painter, "get_canvas_size"):
|
|
122
|
+
cur_sz = painter.get_canvas_size()
|
|
123
|
+
cur_val = f"{cur_sz.width()}x{cur_sz.height()}"
|
|
124
|
+
else:
|
|
125
|
+
cur_val = f"{painter.width()}x{painter.height()}"
|
|
126
|
+
|
|
108
127
|
# Save undo only for manual changes and only if size will change
|
|
109
|
-
will_change = selected_norm !=
|
|
128
|
+
will_change = selected_norm != cur_val
|
|
110
129
|
if is_manual and will_change:
|
|
111
130
|
painter.saveForUndo()
|
|
112
131
|
|
|
@@ -124,9 +143,10 @@ class Common:
|
|
|
124
143
|
self._sticky_custom_value = selected_norm
|
|
125
144
|
|
|
126
145
|
# Ensure combo reflects single custom at index 0 (sticky respected), then select current value
|
|
127
|
-
|
|
146
|
+
if combo is not None:
|
|
147
|
+
self._sync_canvas_size_combo(combo, selected_norm, sticky_to_keep=self._sticky_custom_value)
|
|
128
148
|
|
|
129
|
-
# Apply canvas size; PainterWidget handles rescaling in
|
|
149
|
+
# Apply canvas size; PainterWidget handles rescaling in its own logic
|
|
130
150
|
w, h = self.convert_to_size(selected_norm)
|
|
131
151
|
self.set_canvas_size(w, h)
|
|
132
152
|
|
|
@@ -272,10 +292,22 @@ class Common:
|
|
|
272
292
|
if self._changing_canvas_size:
|
|
273
293
|
return
|
|
274
294
|
|
|
275
|
-
combo: QComboBox =
|
|
295
|
+
combo: Optional[QComboBox] = None
|
|
296
|
+
try:
|
|
297
|
+
if hasattr(self.window.ui, "nodes"):
|
|
298
|
+
combo = self.window.ui.nodes.get('painter.select.canvas.size', None)
|
|
299
|
+
except Exception:
|
|
300
|
+
combo = None
|
|
301
|
+
|
|
276
302
|
painter = self.window.ui.painter
|
|
277
303
|
|
|
278
|
-
|
|
304
|
+
# Use true logical canvas size, not widget size
|
|
305
|
+
if hasattr(painter, "get_canvas_size"):
|
|
306
|
+
sz = painter.get_canvas_size()
|
|
307
|
+
canvas_value = f"{sz.width()}x{sz.height()}"
|
|
308
|
+
else:
|
|
309
|
+
canvas_value = f"{painter.width()}x{painter.height()}"
|
|
310
|
+
|
|
279
311
|
canvas_norm = self._normalize_canvas_value(canvas_value)
|
|
280
312
|
if not canvas_norm:
|
|
281
313
|
return
|
|
@@ -292,7 +324,8 @@ class Common:
|
|
|
292
324
|
try:
|
|
293
325
|
self._changing_canvas_size = True
|
|
294
326
|
self._sticky_custom_value = sticky
|
|
295
|
-
|
|
327
|
+
if combo is not None:
|
|
328
|
+
self._sync_canvas_size_combo(combo, canvas_norm, sticky_to_keep=sticky)
|
|
296
329
|
|
|
297
330
|
# Persist canvas size only (do not change sticky config-scope)
|
|
298
331
|
self.window.core.config.set('painter.canvas.size', canvas_norm)
|
|
@@ -409,7 +442,6 @@ class Common:
|
|
|
409
442
|
combo.setCurrentText(value)
|
|
410
443
|
else:
|
|
411
444
|
# Current value is custom: ensure it exists at index 0 and select it
|
|
412
|
-
# If sticky differs or is None, overwrite/create the custom at index 0 to reflect true current value.
|
|
413
445
|
if not sticky_to_keep or sticky_to_keep != value:
|
|
414
446
|
self._ensure_custom_index0(combo, value, predef)
|
|
415
447
|
if combo.currentIndex() != 0:
|
|
@@ -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.12.27 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -53,7 +53,7 @@ class Painter:
|
|
|
53
53
|
"""
|
|
54
54
|
self.open(path)
|
|
55
55
|
if not self.is_active():
|
|
56
|
-
self.window.controller.ui.switch_tab(
|
|
56
|
+
self.window.controller.ui.tabs.switch_tab(Tab.TAB_TOOL_PAINTER)
|
|
57
57
|
|
|
58
58
|
def save(self):
|
|
59
59
|
"""Store current image"""
|
|
@@ -6,9 +6,11 @@
|
|
|
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.12.27 21:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
+
from PySide6 import QtCore
|
|
13
|
+
|
|
12
14
|
from pygpt_net.core.types import (
|
|
13
15
|
MODE_EXPERT,
|
|
14
16
|
)
|
|
@@ -23,6 +25,8 @@ class Experts:
|
|
|
23
25
|
:param window: Window instance
|
|
24
26
|
"""
|
|
25
27
|
self.window = window
|
|
28
|
+
self.selected_available_uuids = [] # multi-selected available experts
|
|
29
|
+
self.selected_selected_uuids = [] # multi-selected selected experts
|
|
26
30
|
|
|
27
31
|
def refresh(self):
|
|
28
32
|
"""Refresh presets"""
|
|
@@ -78,13 +82,43 @@ class Experts:
|
|
|
78
82
|
|
|
79
83
|
self.update_tab()
|
|
80
84
|
|
|
85
|
+
def _selected_uuids_from_view(self, node_id: str, fallback_idx_resolver) -> list[str]:
|
|
86
|
+
"""
|
|
87
|
+
Resolve selected expert UUIDs from a given list view using stored tooltip role,
|
|
88
|
+
falling back to idx -> uuid resolver when needed.
|
|
89
|
+
|
|
90
|
+
:param node_id: ui node id
|
|
91
|
+
:param fallback_idx_resolver: callable that maps row index -> uuid
|
|
92
|
+
:return: list of selected uuids
|
|
93
|
+
"""
|
|
94
|
+
uuids = []
|
|
95
|
+
try:
|
|
96
|
+
view = self.window.ui.nodes[node_id]
|
|
97
|
+
sel_model = view.selectionModel()
|
|
98
|
+
rows = sel_model.selectedRows() if sel_model else []
|
|
99
|
+
for ix in rows:
|
|
100
|
+
uuid = ix.data(QtCore.Qt.ToolTipRole)
|
|
101
|
+
if not uuid:
|
|
102
|
+
uuid = fallback_idx_resolver(ix.row())
|
|
103
|
+
if uuid and uuid not in uuids:
|
|
104
|
+
uuids.append(uuid)
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
return uuids
|
|
108
|
+
|
|
81
109
|
def change_available(self):
|
|
82
|
-
"""Change selected expert"""
|
|
83
|
-
|
|
110
|
+
"""Change selected expert(s) in available list"""
|
|
111
|
+
self.selected_available_uuids = self._selected_uuids_from_view(
|
|
112
|
+
"preset.experts.available",
|
|
113
|
+
self.get_available_by_idx
|
|
114
|
+
)
|
|
84
115
|
|
|
85
116
|
def change_selected(self):
|
|
86
|
-
"""Change selected expert"""
|
|
87
|
-
|
|
117
|
+
"""Change selected expert(s) in selected list"""
|
|
118
|
+
self.selected_selected_uuids = self._selected_uuids_from_view(
|
|
119
|
+
"preset.experts.selected",
|
|
120
|
+
self.get_selected_by_idx
|
|
121
|
+
)
|
|
88
122
|
|
|
89
123
|
def get_current_available(self) -> str:
|
|
90
124
|
"""
|
|
@@ -138,27 +172,47 @@ class Experts:
|
|
|
138
172
|
i += 1
|
|
139
173
|
|
|
140
174
|
def add_expert(self):
|
|
141
|
-
"""Add expert"""
|
|
175
|
+
"""Add expert(s)"""
|
|
142
176
|
agent_uuid = self.get_current_agent_id()
|
|
143
177
|
if agent_uuid is None or not self.window.core.presets.exists_uuid(agent_uuid):
|
|
144
178
|
self.window.controller.presets.editor.save(close=False)
|
|
145
179
|
return
|
|
146
|
-
|
|
147
|
-
|
|
180
|
+
|
|
181
|
+
uuids = self.selected_available_uuids[:]
|
|
182
|
+
if not uuids:
|
|
183
|
+
one = self.get_current_available()
|
|
184
|
+
if one:
|
|
185
|
+
uuids = [one]
|
|
186
|
+
|
|
187
|
+
if not uuids:
|
|
148
188
|
return
|
|
149
|
-
|
|
189
|
+
|
|
190
|
+
for expert_uuid in uuids:
|
|
191
|
+
if expert_uuid and not self.is_active(expert_uuid):
|
|
192
|
+
self.window.core.presets.add_expert(agent_uuid, expert_uuid)
|
|
193
|
+
|
|
150
194
|
self.update_list()
|
|
151
195
|
|
|
152
196
|
def remove_expert(self):
|
|
153
|
-
"""Remove expert"""
|
|
197
|
+
"""Remove expert(s)"""
|
|
154
198
|
agent_uuid = self.get_current_agent_id()
|
|
155
199
|
if agent_uuid is None or not self.window.core.presets.exists_uuid(agent_uuid):
|
|
156
200
|
self.window.controller.presets.editor.save(close=False)
|
|
157
201
|
return
|
|
158
|
-
|
|
159
|
-
|
|
202
|
+
|
|
203
|
+
uuids = self.selected_selected_uuids[:]
|
|
204
|
+
if not uuids:
|
|
205
|
+
one = self.get_current_selected()
|
|
206
|
+
if one:
|
|
207
|
+
uuids = [one]
|
|
208
|
+
|
|
209
|
+
if not uuids:
|
|
160
210
|
return
|
|
161
|
-
|
|
211
|
+
|
|
212
|
+
for expert_uuid in uuids:
|
|
213
|
+
if expert_uuid and self.is_active(expert_uuid):
|
|
214
|
+
self.window.core.presets.remove_expert(agent_uuid, expert_uuid)
|
|
215
|
+
|
|
162
216
|
self.update_list()
|
|
163
217
|
|
|
164
218
|
def update_tab(self):
|
|
@@ -174,5 +228,4 @@ class Experts:
|
|
|
174
228
|
if num == 0:
|
|
175
229
|
tabs.setTabText(idx, trans("preset.tab.experts"))
|
|
176
230
|
else:
|
|
177
|
-
tabs.setTabText(idx, trans("preset.tab.experts") + f" ({num})")
|
|
178
|
-
|
|
231
|
+
tabs.setTabText(idx, trans("preset.tab.experts") + f" ({num})")
|