pygpt-net 2.6.37__py3-none-any.whl → 2.6.39__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 +12 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/chat/handler/anthropic_stream.py +0 -2
- pygpt_net/controller/chat/handler/worker.py +6 -2
- pygpt_net/controller/debug/debug.py +6 -6
- pygpt_net/controller/model/editor.py +20 -42
- pygpt_net/controller/model/importer.py +9 -2
- pygpt_net/controller/painter/common.py +0 -8
- pygpt_net/controller/plugins/plugins.py +11 -3
- pygpt_net/controller/presets/presets.py +2 -2
- pygpt_net/core/ctx/bag.py +7 -2
- pygpt_net/core/ctx/reply.py +17 -2
- pygpt_net/core/db/viewer.py +19 -34
- pygpt_net/core/render/plain/pid.py +12 -1
- pygpt_net/core/render/web/body.py +292 -445
- pygpt_net/core/tabs/tab.py +24 -1
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/item/assistant.py +51 -2
- pygpt_net/item/attachment.py +21 -20
- pygpt_net/item/calendar_note.py +19 -2
- pygpt_net/item/ctx.py +115 -2
- pygpt_net/item/index.py +9 -2
- pygpt_net/item/mode.py +9 -6
- pygpt_net/item/model.py +20 -3
- pygpt_net/item/notepad.py +14 -2
- pygpt_net/item/preset.py +42 -2
- pygpt_net/item/prompt.py +8 -2
- pygpt_net/plugin/cmd_files/plugin.py +2 -2
- pygpt_net/provider/api/anthropic/tools.py +1 -1
- pygpt_net/provider/api/google/realtime/client.py +2 -2
- pygpt_net/provider/core/attachment/json_file.py +2 -2
- pygpt_net/tools/text_editor/tool.py +4 -1
- pygpt_net/tools/text_editor/ui/dialogs.py +1 -1
- pygpt_net/ui/dialog/db.py +177 -59
- pygpt_net/ui/dialog/dictionary.py +57 -59
- pygpt_net/ui/dialog/editor.py +3 -2
- pygpt_net/ui/dialog/image.py +1 -1
- pygpt_net/ui/dialog/logger.py +3 -2
- pygpt_net/ui/dialog/models.py +171 -21
- pygpt_net/ui/dialog/plugins.py +26 -20
- pygpt_net/ui/layout/ctx/ctx_list.py +3 -4
- pygpt_net/ui/layout/toolbox/__init__.py +2 -2
- pygpt_net/ui/layout/toolbox/assistants.py +8 -9
- pygpt_net/ui/layout/toolbox/presets.py +2 -2
- pygpt_net/ui/main.py +9 -4
- pygpt_net/ui/widget/element/labels.py +2 -2
- pygpt_net/ui/widget/textarea/editor.py +0 -4
- pygpt_net/utils.py +12 -13
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.dist-info}/METADATA +14 -2
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.dist-info}/RECORD +54 -54
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.37.dist-info → pygpt_net-2.6.39.dist-info}/entry_points.txt +0 -0
pygpt_net/ui/dialog/logger.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.05 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtWidgets import QPushButton, QHBoxLayout, QVBoxLayout
|
|
@@ -44,7 +44,8 @@ class Logger:
|
|
|
44
44
|
|
|
45
45
|
self.window.ui.nodes['logger.btn.clear'] = QPushButton(trans("dialog.logger.btn.clear"))
|
|
46
46
|
self.window.ui.nodes['logger.btn.clear'].clicked.connect(
|
|
47
|
-
lambda: self.window.controller.debug.clear_logger()
|
|
47
|
+
lambda: self.window.controller.debug.clear_logger()
|
|
48
|
+
)
|
|
48
49
|
|
|
49
50
|
bottom_layout = QHBoxLayout()
|
|
50
51
|
bottom_layout.addWidget(self.window.ui.nodes['logger.btn.clear'])
|
pygpt_net/ui/dialog/models.py
CHANGED
|
@@ -6,17 +6,17 @@
|
|
|
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.05 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
13
|
+
from typing import Dict, List, Optional
|
|
13
14
|
|
|
14
15
|
from PySide6.QtCore import Qt
|
|
15
16
|
from PySide6.QtGui import QStandardItemModel, QIcon
|
|
16
17
|
from PySide6.QtWidgets import QPushButton, QHBoxLayout, QLabel, QVBoxLayout, QScrollArea, QWidget, QTabWidget, QFrame, \
|
|
17
18
|
QSplitter, QSizePolicy
|
|
18
19
|
|
|
19
|
-
from pygpt_net.item.model import ModelItem
|
|
20
20
|
from pygpt_net.ui.widget.dialog.model import ModelDialog
|
|
21
21
|
from pygpt_net.ui.widget.element.group import CollapsedGroup
|
|
22
22
|
from pygpt_net.ui.widget.element.labels import UrlLabel, DescLabel
|
|
@@ -28,8 +28,10 @@ from pygpt_net.ui.widget.option.dictionary import OptionDict
|
|
|
28
28
|
from pygpt_net.ui.widget.option.input import OptionInput, PasswordInput
|
|
29
29
|
from pygpt_net.ui.widget.option.slider import OptionSlider
|
|
30
30
|
from pygpt_net.ui.widget.option.textarea import OptionTextarea
|
|
31
|
+
from pygpt_net.ui.widget.textarea.search_input import SearchInput
|
|
31
32
|
from pygpt_net.utils import trans
|
|
32
33
|
|
|
34
|
+
|
|
33
35
|
class Models:
|
|
34
36
|
def __init__(self, window=None):
|
|
35
37
|
"""
|
|
@@ -40,6 +42,13 @@ class Models:
|
|
|
40
42
|
self.window = window
|
|
41
43
|
self.dialog_id = "models.editor"
|
|
42
44
|
|
|
45
|
+
# Internal state for filtering/mapping
|
|
46
|
+
self._filter_text: str = ""
|
|
47
|
+
self._filtered_ids: List[str] = [] # current list of model keys displayed in the view (order matters)
|
|
48
|
+
self._index_to_id: List[str] = [] # row index -> model key
|
|
49
|
+
self._id_to_index: Dict[str, int] = {} # model key -> row index
|
|
50
|
+
self._all_data: Dict[str, object] = {} # last known (unfiltered) data snapshot used to render the list
|
|
51
|
+
|
|
43
52
|
def setup(self, idx=None):
|
|
44
53
|
"""
|
|
45
54
|
Setup editor dialog
|
|
@@ -56,13 +65,17 @@ class Models:
|
|
|
56
65
|
QPushButton(trans("dialog.models.editor.btn.save"))
|
|
57
66
|
|
|
58
67
|
self.window.ui.nodes['models.editor.btn.new'].clicked.connect(
|
|
59
|
-
lambda: self.window.controller.model.editor.new()
|
|
68
|
+
lambda: self.window.controller.model.editor.new()
|
|
69
|
+
)
|
|
60
70
|
self.window.ui.nodes['models.editor.btn.defaults.user'].clicked.connect(
|
|
61
|
-
lambda: self.window.controller.model.editor.load_defaults_user()
|
|
71
|
+
lambda: self.window.controller.model.editor.load_defaults_user()
|
|
72
|
+
)
|
|
62
73
|
self.window.ui.nodes['models.editor.btn.defaults.app'].clicked.connect(
|
|
63
|
-
lambda: self.window.controller.model.editor.load_defaults_app()
|
|
74
|
+
lambda: self.window.controller.model.editor.load_defaults_app()
|
|
75
|
+
)
|
|
64
76
|
self.window.ui.nodes['models.editor.btn.save'].clicked.connect(
|
|
65
|
-
lambda: self.window.controller.model.editor.save()
|
|
77
|
+
lambda: self.window.controller.model.editor.save()
|
|
78
|
+
)
|
|
66
79
|
|
|
67
80
|
# set enter key to save button
|
|
68
81
|
self.window.ui.nodes['models.editor.btn.new'].setAutoDefault(False)
|
|
@@ -111,17 +124,16 @@ class Models:
|
|
|
111
124
|
# append advanced options at the end
|
|
112
125
|
if len(advanced_keys) > 0:
|
|
113
126
|
group_id = 'models.editor.advanced'
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
group = CollapsedGroup(self.window, group_id, None, False, None)
|
|
128
|
+
group.box.setText(trans('settings.advanced.collapse'))
|
|
116
129
|
for key in widgets:
|
|
117
130
|
if key not in advanced_keys: # ignore non-advanced options
|
|
118
131
|
continue
|
|
119
|
-
|
|
120
|
-
option = self.add_option(widgets[key], options[key]) # build option
|
|
121
|
-
self.window.ui.groups[group_id].add_layout(option) # add option to group
|
|
132
|
+
group.add_layout(self.add_option(widgets[key], options[key])) # add option to group
|
|
122
133
|
|
|
123
134
|
# add advanced options group to scroll
|
|
124
|
-
content.addWidget(
|
|
135
|
+
content.addWidget(group)
|
|
136
|
+
self.window.ui.groups[group_id] = group
|
|
125
137
|
|
|
126
138
|
content.addStretch()
|
|
127
139
|
|
|
@@ -146,16 +158,29 @@ class Models:
|
|
|
146
158
|
self.window.ui.nodes[id] = ModelEditorList(self.window, id)
|
|
147
159
|
self.window.ui.models[id] = self.create_model(self.window)
|
|
148
160
|
self.window.ui.nodes[id].setModel(self.window.ui.models[id])
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
self.window.ui.nodes[id].setMinimumWidth(250) # set max width to list
|
|
162
|
+
|
|
163
|
+
# search input (placed above the list)
|
|
164
|
+
self.window.ui.nodes['models.editor.search'] = SearchInput()
|
|
165
|
+
# Connect to provided callback API (callables assigned to attributes)
|
|
166
|
+
self.window.ui.nodes['models.editor.search'].on_search = self._on_search_models
|
|
167
|
+
self.window.ui.nodes['models.editor.search'].on_clear = self._on_clear_models # clear via "X" button
|
|
168
|
+
|
|
169
|
+
# container for search + list (left panel)
|
|
170
|
+
left_layout = QVBoxLayout()
|
|
171
|
+
left_layout.setContentsMargins(0, 0, 0, 0)
|
|
172
|
+
left_layout.setSpacing(6)
|
|
173
|
+
left_layout.addWidget(self.window.ui.nodes['models.editor.search'])
|
|
174
|
+
left_layout.addWidget(self.window.ui.nodes[id])
|
|
175
|
+
left_widget = QWidget()
|
|
176
|
+
left_widget.setLayout(left_layout)
|
|
177
|
+
|
|
178
|
+
# update models list (initial, unfiltered)
|
|
151
179
|
self.update_list(id, data)
|
|
152
180
|
|
|
153
|
-
# set max width to list
|
|
154
|
-
self.window.ui.nodes[id].setMinimumWidth(250)
|
|
155
|
-
|
|
156
181
|
# splitter
|
|
157
182
|
self.window.ui.splitters['dialog.models'] = QSplitter(Qt.Horizontal)
|
|
158
|
-
self.window.ui.splitters['dialog.models'].addWidget(
|
|
183
|
+
self.window.ui.splitters['dialog.models'].addWidget(left_widget) # search + list
|
|
159
184
|
self.window.ui.splitters['dialog.models'].addWidget(area_widget) # tabs
|
|
160
185
|
self.window.ui.splitters['dialog.models'].setStretchFactor(0, 2)
|
|
161
186
|
self.window.ui.splitters['dialog.models'].setStretchFactor(1, 5)
|
|
@@ -182,6 +207,86 @@ class Models:
|
|
|
182
207
|
if self.window.controller.model.editor.current is None:
|
|
183
208
|
self.window.controller.model.editor.set_by_tab(0)
|
|
184
209
|
|
|
210
|
+
def _on_search_models(self, text: str):
|
|
211
|
+
"""
|
|
212
|
+
Handle SearchInput callback. Filtering is prefix-based on model id or name (case-insensitive).
|
|
213
|
+
"""
|
|
214
|
+
# Keep normalized filter text
|
|
215
|
+
self._filter_text = (text or "").strip().casefold()
|
|
216
|
+
# Re-apply list render using the last known dataset
|
|
217
|
+
self.update_list('models.list', self._all_data)
|
|
218
|
+
# Do not force-select rows here to avoid heavy editor re-initialization on each keystroke.
|
|
219
|
+
|
|
220
|
+
def _on_clear_models(self):
|
|
221
|
+
"""
|
|
222
|
+
Handle SearchInput clear (click on 'X' button).
|
|
223
|
+
"""
|
|
224
|
+
if not self._filter_text:
|
|
225
|
+
return
|
|
226
|
+
self._filter_text = ""
|
|
227
|
+
self.update_list('models.list', self._all_data)
|
|
228
|
+
# Try to restore selection for the current model if it is visible again.
|
|
229
|
+
self._restore_selection_for_current()
|
|
230
|
+
|
|
231
|
+
def _restore_selection_for_current(self):
|
|
232
|
+
"""
|
|
233
|
+
Attempt to restore selection on the list to the current editor model if present in the view.
|
|
234
|
+
"""
|
|
235
|
+
current_id = getattr(self.window.controller.model.editor, "current", None)
|
|
236
|
+
if not current_id:
|
|
237
|
+
return
|
|
238
|
+
idx = self.get_row_by_model_id(current_id)
|
|
239
|
+
if idx is None:
|
|
240
|
+
return
|
|
241
|
+
current = self.window.ui.models['models.list'].index(idx, 0)
|
|
242
|
+
self.window.ui.nodes['models.list'].setCurrentIndex(current)
|
|
243
|
+
|
|
244
|
+
def _apply_filter(self, data: Dict[str, object]) -> Dict[str, object]:
|
|
245
|
+
"""
|
|
246
|
+
Return filtered data view, preserving original order.
|
|
247
|
+
"""
|
|
248
|
+
if not self._filter_text:
|
|
249
|
+
return data
|
|
250
|
+
|
|
251
|
+
out: Dict[str, object] = {}
|
|
252
|
+
needle = self._filter_text
|
|
253
|
+
for mid, item in data.items():
|
|
254
|
+
# Normalize fields for prefix matching
|
|
255
|
+
model_id = (str(getattr(item, "id", mid)) or "").casefold()
|
|
256
|
+
model_name = (getattr(item, "name", "") or "").casefold()
|
|
257
|
+
if (needle in model_name
|
|
258
|
+
or needle in model_id):
|
|
259
|
+
out[mid] = item
|
|
260
|
+
return out
|
|
261
|
+
|
|
262
|
+
def _refresh_index_mapping(self, ids: List[str]):
|
|
263
|
+
"""
|
|
264
|
+
Build fast row<->id mapping for the current filtered list.
|
|
265
|
+
"""
|
|
266
|
+
self._filtered_ids = list(ids)
|
|
267
|
+
self._index_to_id = list(ids)
|
|
268
|
+
self._id_to_index = {mid: idx for idx, mid in enumerate(ids)}
|
|
269
|
+
|
|
270
|
+
def get_model_id_by_row(self, row: int) -> Optional[str]:
|
|
271
|
+
"""
|
|
272
|
+
Map a view row index to the model id currently displayed at that row.
|
|
273
|
+
"""
|
|
274
|
+
if 0 <= row < len(self._index_to_id):
|
|
275
|
+
return self._index_to_id[row]
|
|
276
|
+
return None
|
|
277
|
+
|
|
278
|
+
def get_row_by_model_id(self, model_id: str) -> Optional[int]:
|
|
279
|
+
"""
|
|
280
|
+
Map a model id to its current row index in the filtered view.
|
|
281
|
+
"""
|
|
282
|
+
return self._id_to_index.get(model_id)
|
|
283
|
+
|
|
284
|
+
def get_filtered_ids(self) -> List[str]:
|
|
285
|
+
"""
|
|
286
|
+
Return a copy of currently visible model ids (filtered order).
|
|
287
|
+
"""
|
|
288
|
+
return list(self._filtered_ids)
|
|
289
|
+
|
|
185
290
|
def build_widgets(self, options: dict) -> dict:
|
|
186
291
|
"""
|
|
187
292
|
Build settings options widgets
|
|
@@ -331,6 +436,21 @@ class Models:
|
|
|
331
436
|
"""
|
|
332
437
|
return QStandardItemModel(0, 1, parent)
|
|
333
438
|
|
|
439
|
+
def _get_provider_display_name(self, provider_id: str) -> str:
|
|
440
|
+
"""
|
|
441
|
+
Resolve provider display name using app's LLM provider registry.
|
|
442
|
+
"""
|
|
443
|
+
if not provider_id:
|
|
444
|
+
return ""
|
|
445
|
+
try:
|
|
446
|
+
name = self.window.core.llm.get_provider_name(provider_id)
|
|
447
|
+
if isinstance(name, str):
|
|
448
|
+
name = name.strip()
|
|
449
|
+
return name or ""
|
|
450
|
+
except Exception:
|
|
451
|
+
# Fail silently to avoid breaking the models list rendering
|
|
452
|
+
return ""
|
|
453
|
+
|
|
334
454
|
def update_list(self, id: str, data: dict):
|
|
335
455
|
"""
|
|
336
456
|
Update list
|
|
@@ -338,10 +458,40 @@ class Models:
|
|
|
338
458
|
:param id: ID of the list
|
|
339
459
|
:param data: Data to update
|
|
340
460
|
"""
|
|
461
|
+
# Keep latest source data for subsequent filtering cycles
|
|
462
|
+
self._all_data = dict(data)
|
|
463
|
+
|
|
464
|
+
# Apply current filter (preserve insertion/sorted order)
|
|
465
|
+
filtered = self._apply_filter(self._all_data)
|
|
466
|
+
|
|
467
|
+
# Reset model rows
|
|
341
468
|
self.window.ui.models[id].removeRows(0, self.window.ui.models[id].rowCount())
|
|
469
|
+
|
|
470
|
+
# Count occurrences of each model display name to detect duplicates (in filtered view)
|
|
471
|
+
name_counts = {}
|
|
472
|
+
for key in filtered:
|
|
473
|
+
item = filtered[key]
|
|
474
|
+
base_name = getattr(item, "name", "") or ""
|
|
475
|
+
name_counts[base_name] = name_counts.get(base_name, 0) + 1
|
|
476
|
+
|
|
477
|
+
# Populate rows with optional provider suffix for duplicate names
|
|
342
478
|
i = 0
|
|
343
|
-
|
|
479
|
+
row_ids: List[str] = []
|
|
480
|
+
for n in filtered:
|
|
481
|
+
item = filtered[n]
|
|
482
|
+
base_name = getattr(item, "name", "") or ""
|
|
483
|
+
display_name = base_name
|
|
484
|
+
if name_counts.get(base_name, 0) > 1:
|
|
485
|
+
provider_id = getattr(item, "provider", None)
|
|
486
|
+
provider_name = self._get_provider_display_name(provider_id) if provider_id else ""
|
|
487
|
+
if provider_name:
|
|
488
|
+
display_name = f"{base_name} ({provider_name})"
|
|
489
|
+
|
|
344
490
|
self.window.ui.models[id].insertRow(i)
|
|
345
|
-
|
|
346
|
-
|
|
491
|
+
self.window.ui.models[id].setData(self.window.ui.models[id].index(i, 0), display_name)
|
|
492
|
+
# IMPORTANT: map list row to the dictionary key (stable and unique within models.items)
|
|
493
|
+
row_ids.append(n)
|
|
347
494
|
i += 1
|
|
495
|
+
|
|
496
|
+
# Refresh index mappings used by Editor
|
|
497
|
+
self._refresh_index_mapping(row_ids)
|
pygpt_net/ui/dialog/plugins.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.09.
|
|
9
|
+
# Updated Date: 2025.09.05 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt
|
|
@@ -55,11 +55,14 @@ class Plugins:
|
|
|
55
55
|
QPushButton(trans("dialog.plugin.settings.btn.save"))
|
|
56
56
|
|
|
57
57
|
self.window.ui.nodes['plugin.settings.btn.defaults.user'].clicked.connect(
|
|
58
|
-
lambda: self.window.controller.plugins.settings.load_defaults_user()
|
|
58
|
+
lambda: self.window.controller.plugins.settings.load_defaults_user()
|
|
59
|
+
)
|
|
59
60
|
self.window.ui.nodes['plugin.settings.btn.defaults.app'].clicked.connect(
|
|
60
|
-
lambda: self.window.controller.plugins.settings.load_defaults_app()
|
|
61
|
+
lambda: self.window.controller.plugins.settings.load_defaults_app()
|
|
62
|
+
)
|
|
61
63
|
self.window.ui.nodes['plugin.settings.btn.save'].clicked.connect(
|
|
62
|
-
lambda: self.window.controller.plugins.settings.save()
|
|
64
|
+
lambda: self.window.controller.plugins.settings.save()
|
|
65
|
+
)
|
|
63
66
|
|
|
64
67
|
# set enter key to save button
|
|
65
68
|
self.window.ui.nodes['plugin.settings.btn.defaults.user'].setAutoDefault(False)
|
|
@@ -321,33 +324,35 @@ class Plugins:
|
|
|
321
324
|
widgets = {}
|
|
322
325
|
base_types = ('text', 'int', 'float')
|
|
323
326
|
number_types = ('int', 'float')
|
|
327
|
+
apply = self.window.controller.config.placeholder.apply
|
|
324
328
|
|
|
325
329
|
for key in options:
|
|
326
330
|
option = options[key]
|
|
331
|
+
t = option['type']
|
|
327
332
|
# create widget by option type
|
|
328
|
-
if
|
|
329
|
-
if 'slider' in option and option['slider'] and
|
|
333
|
+
if t in base_types:
|
|
334
|
+
if 'slider' in option and option['slider'] and t in number_types:
|
|
330
335
|
widgets[key] = OptionSlider(self.window, parent, key, option) # slider + text input
|
|
331
336
|
else:
|
|
332
337
|
if 'secret' in option and option['secret']:
|
|
333
338
|
widgets[key] = PasswordInput(self.window, parent, key, option) # password input
|
|
334
339
|
else:
|
|
335
340
|
widgets[key] = OptionInput(self.window, parent, key, option) # text input
|
|
336
|
-
elif
|
|
341
|
+
elif t == 'textarea':
|
|
337
342
|
widgets[key] = OptionTextarea(self.window, parent, key, option) # textarea
|
|
338
|
-
elif
|
|
343
|
+
elif t == 'bool':
|
|
339
344
|
widgets[key] = OptionCheckbox(self.window, parent, key, option) # checkbox
|
|
340
|
-
elif
|
|
341
|
-
|
|
345
|
+
elif t == 'bool_list':
|
|
346
|
+
apply(option)
|
|
342
347
|
widgets[key] = OptionCheckboxList(self.window, parent, key, option) # checkbox list
|
|
343
|
-
elif
|
|
344
|
-
|
|
348
|
+
elif t == 'dict':
|
|
349
|
+
apply(option)
|
|
345
350
|
widgets[key] = OptionDict(self.window, parent, key, option) # dictionary
|
|
346
|
-
elif
|
|
347
|
-
|
|
351
|
+
elif t == 'combo':
|
|
352
|
+
apply(option)
|
|
348
353
|
widgets[key] = OptionCombo(self.window, parent, key, option) # combobox
|
|
349
|
-
elif
|
|
350
|
-
|
|
354
|
+
elif t == 'cmd':
|
|
355
|
+
apply(option)
|
|
351
356
|
widgets[key] = OptionCmd(self.window, plugin, parent, key, option) # command
|
|
352
357
|
|
|
353
358
|
return widgets
|
|
@@ -508,12 +513,13 @@ class Plugins:
|
|
|
508
513
|
:param id: ID of the list
|
|
509
514
|
:param data: Data to update
|
|
510
515
|
"""
|
|
511
|
-
|
|
516
|
+
model = self.window.ui.models[id]
|
|
517
|
+
model.removeRows(0, model.rowCount())
|
|
512
518
|
i = 0
|
|
513
519
|
for n in data:
|
|
514
|
-
|
|
520
|
+
model.insertRow(i)
|
|
515
521
|
name = self.window.core.plugins.get_name(data[n].id)
|
|
516
522
|
tooltip = self.window.core.plugins.get_desc(data[n].id)
|
|
517
|
-
|
|
518
|
-
|
|
523
|
+
model.setData(model.index(i, 0), name)
|
|
524
|
+
model.setData(model.index(i, 0), tooltip, Qt.ToolTipRole)
|
|
519
525
|
i += 1
|
|
@@ -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.05 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6 import QtCore
|
|
@@ -210,7 +210,7 @@ class CtxList:
|
|
|
210
210
|
files_str = ", ".join(files)
|
|
211
211
|
if len(files_str) > 40:
|
|
212
212
|
files_str = files_str[:40] + '...'
|
|
213
|
-
tooltip_str = trans(
|
|
213
|
+
tooltip_str = f"{trans('attachments.ctx.tooltip.list').format(num=len(files))}: {files_str}"
|
|
214
214
|
group_item.setToolTip(tooltip_str)
|
|
215
215
|
|
|
216
216
|
group_item.setData(custom_data, QtCore.Qt.ItemDataRole.UserRole)
|
|
@@ -282,8 +282,7 @@ class CtxList:
|
|
|
282
282
|
files_str = ", ".join(files)
|
|
283
283
|
if len(files_str) > 40:
|
|
284
284
|
files_str = files_str[:40] + '...'
|
|
285
|
-
|
|
286
|
-
tooltip_text += "\n" + tooltip_str
|
|
285
|
+
tooltip_text += f"\n{trans('attachments.ctx.tooltip.list').format(num=len(files))}: {files_str}"
|
|
287
286
|
|
|
288
287
|
item = Item(name, id)
|
|
289
288
|
item.id = id
|
|
@@ -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:
|
|
9
|
+
# Updated Date: 2025.09.05 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
-
from .toolbox import
|
|
12
|
+
from .toolbox import ToolboxMain
|
|
@@ -115,15 +115,14 @@ class Assistants:
|
|
|
115
115
|
view.backup_selection()
|
|
116
116
|
view.setUpdatesEnabled(False)
|
|
117
117
|
try:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
model.setData(index, item.name)
|
|
118
|
+
model.setRowCount(0)
|
|
119
|
+
count = len(data)
|
|
120
|
+
if count:
|
|
121
|
+
model.setRowCount(count)
|
|
122
|
+
for i, item in enumerate(data.values()):
|
|
123
|
+
index = model.index(i, 0)
|
|
124
|
+
model.setData(index, "ID: " + item.id, QtCore.Qt.ToolTipRole)
|
|
125
|
+
model.setData(index, item.name)
|
|
127
126
|
finally:
|
|
128
127
|
view.setUpdatesEnabled(True)
|
|
129
128
|
|
|
@@ -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.05 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6 import QtCore
|
|
@@ -144,7 +144,7 @@ class Presets:
|
|
|
144
144
|
for i, (key, item) in enumerate(data.items()):
|
|
145
145
|
name = item.name
|
|
146
146
|
if is_expert_mode and item.enabled and not key.startswith(startswith_current):
|
|
147
|
-
name = "[x] "
|
|
147
|
+
name = f"[x] {name}"
|
|
148
148
|
elif is_agent_mode:
|
|
149
149
|
num_experts = count_experts(key)
|
|
150
150
|
if num_experts > 0:
|
pygpt_net/ui/main.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.05 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -231,7 +231,7 @@ class MainWindow(QMainWindow, QtStyleTools):
|
|
|
231
231
|
|
|
232
232
|
def update(self):
|
|
233
233
|
"""Called on every update (real-time)"""
|
|
234
|
-
self.controller.on_update()
|
|
234
|
+
# self.controller.on_update()
|
|
235
235
|
self.controller.plugins.on_update()
|
|
236
236
|
self.tools.on_update()
|
|
237
237
|
|
|
@@ -254,8 +254,13 @@ class MainWindow(QMainWindow, QtStyleTools):
|
|
|
254
254
|
|
|
255
255
|
:param message: status message
|
|
256
256
|
"""
|
|
257
|
-
|
|
258
|
-
|
|
257
|
+
self.dispatch(
|
|
258
|
+
KernelEvent(
|
|
259
|
+
KernelEvent.STATUS, {
|
|
260
|
+
"status": message if isinstance(message, str) else str(message)
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
)
|
|
259
264
|
|
|
260
265
|
@Slot(str)
|
|
261
266
|
def update_state(self, state: str):
|
|
@@ -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.09.
|
|
9
|
+
# Updated Date: 2025.09.05 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt, QTimer, QRect, Signal, QUrl, QEvent
|
|
@@ -50,7 +50,7 @@ class ChatStatusLabel(QLabel):
|
|
|
50
50
|
def __init__(self, text, window=None):
|
|
51
51
|
super().__init__(text, window)
|
|
52
52
|
self.window = window
|
|
53
|
-
self.setWordWrap(
|
|
53
|
+
self.setWordWrap(False)
|
|
54
54
|
self.setAlignment(Qt.AlignRight)
|
|
55
55
|
self.setProperty('class', 'label-chat-status')
|
|
56
56
|
self.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
|
@@ -113,10 +113,6 @@ class BaseCodeEditor(QTextEdit):
|
|
|
113
113
|
self.finder.clear()
|
|
114
114
|
|
|
115
115
|
def on_destroy(self):
|
|
116
|
-
try:
|
|
117
|
-
self.textChanged.disconnect(self.text_changed)
|
|
118
|
-
except Exception:
|
|
119
|
-
pass
|
|
120
116
|
self.window.controller.finder.unset(self.finder)
|
|
121
117
|
|
|
122
118
|
def keyPressEvent(self, e):
|
pygpt_net/utils.py
CHANGED
|
@@ -269,28 +269,25 @@ def natsort(l: list) -> list:
|
|
|
269
269
|
return sorted(l, key=alphanum_key)
|
|
270
270
|
|
|
271
271
|
def mem_clean():
|
|
272
|
-
"""
|
|
273
|
-
|
|
274
|
-
"""
|
|
275
|
-
return # temporary disabled
|
|
276
|
-
|
|
272
|
+
"""Clean memory by removing unused objects"""
|
|
273
|
+
return
|
|
277
274
|
import sys, gc
|
|
278
275
|
ok = False
|
|
279
276
|
try:
|
|
280
277
|
gc.collect()
|
|
281
|
-
except Exception:
|
|
282
|
-
|
|
278
|
+
except Exception as e:
|
|
279
|
+
print(e)
|
|
283
280
|
|
|
284
281
|
try:
|
|
285
282
|
QApplication.sendPostedEvents(None, QtCore.QEvent.DeferredDelete)
|
|
286
283
|
QApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
|
|
287
|
-
except Exception:
|
|
288
|
-
|
|
284
|
+
except Exception as e:
|
|
285
|
+
print(e)
|
|
289
286
|
|
|
290
287
|
try:
|
|
291
288
|
QtGui.QPixmapCache.clear()
|
|
292
|
-
except Exception:
|
|
293
|
-
|
|
289
|
+
except Exception as e:
|
|
290
|
+
print(e)
|
|
294
291
|
|
|
295
292
|
try:
|
|
296
293
|
if sys.platform.startswith("linux"):
|
|
@@ -299,6 +296,7 @@ def mem_clean():
|
|
|
299
296
|
if hasattr(libc, "malloc_trim"):
|
|
300
297
|
libc.malloc_trim(0)
|
|
301
298
|
ok = True
|
|
299
|
+
'''
|
|
302
300
|
elif sys.platform == "win32":
|
|
303
301
|
import ctypes, ctypes.wintypes
|
|
304
302
|
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
@@ -322,6 +320,7 @@ def mem_clean():
|
|
|
322
320
|
ok = True
|
|
323
321
|
except Exception:
|
|
324
322
|
pass
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
'''
|
|
324
|
+
except Exception as e:
|
|
325
|
+
print(e)
|
|
327
326
|
return ok
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pygpt-net
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.39
|
|
4
4
|
Summary: Desktop AI Assistant powered by: OpenAI GPT-5, GPT-4, o1, o3, Gemini, Claude, Grok, DeepSeek, and other models supported by Llama Index, and Ollama. Chatbot, agents, completion, image generation, vision analysis, speech-to-text, plugins, internet access, file handling, command execution and more.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: ai,api,api key,app,assistant,bielik,chat,chatbot,chatgpt,claude,dall-e,deepseek,desktop,gemini,gpt,gpt-3.5,gpt-4,gpt-4-vision,gpt-4o,gpt-5,gpt-oss,gpt3.5,gpt4,grok,langchain,llama-index,llama3,mistral,o1,o3,ollama,openai,presets,py-gpt,py_gpt,pygpt,pyside,qt,text completion,tts,ui,vision,whisper
|
|
@@ -118,7 +118,7 @@ Description-Content-Type: text/markdown
|
|
|
118
118
|
|
|
119
119
|
[](https://snapcraft.io/pygpt)
|
|
120
120
|
|
|
121
|
-
Release: **2.6.
|
|
121
|
+
Release: **2.6.39** | build: **2025-09-06** | Python: **>=3.10, <3.14**
|
|
122
122
|
|
|
123
123
|
> Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
|
|
124
124
|
>
|
|
@@ -3565,6 +3565,18 @@ may consume additional tokens that are not displayed in the main window.
|
|
|
3565
3565
|
|
|
3566
3566
|
## Recent changes:
|
|
3567
3567
|
|
|
3568
|
+
**2.6.39 (2025-09-06)**
|
|
3569
|
+
|
|
3570
|
+
- Added: Search input to the models editor.
|
|
3571
|
+
- Fixed: Brush color switching when changing modes in the Painter.
|
|
3572
|
+
|
|
3573
|
+
**2.6.38 (2025-09-05)**
|
|
3574
|
+
|
|
3575
|
+
- Fixed: Detection of chunk type in Ollama.
|
|
3576
|
+
- Fixed: Import of models with existing IDs.
|
|
3577
|
+
- Fixed: Updating the Assistants UI list after creating a new Assistant.
|
|
3578
|
+
- Refactored and optimized the code.
|
|
3579
|
+
|
|
3568
3580
|
**2.6.37 (2025-09-05)**
|
|
3569
3581
|
|
|
3570
3582
|
- Fixed: Function parameters sanitization in the Google Gen AI SDK.
|