pygpt-net 2.6.66__py3-none-any.whl → 2.7.0__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 +18 -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/config/field/textarea.py +2 -2
- pygpt_net/controller/ctx/common.py +27 -17
- pygpt_net/controller/ctx/ctx.py +182 -101
- pygpt_net/controller/dialogs/info.py +2 -2
- 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/media/media.py +29 -1
- pygpt_net/controller/mode/mode.py +3 -3
- pygpt_net/controller/model/editor.py +141 -21
- pygpt_net/controller/model/importer.py +153 -54
- 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/editor.py +25 -1
- 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 +2 -1
- pygpt_net/core/idx/idx.py +12 -11
- pygpt_net/core/idx/worker.py +13 -1
- pygpt_net/core/models/models.py +4 -4
- pygpt_net/core/profile/profile.py +13 -3
- pygpt_net/core/types/image.py +10 -1
- pygpt_net/core/video/video.py +43 -3
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +25 -14
- pygpt_net/data/css/style.dark.css +39 -1
- pygpt_net/data/css/style.light.css +39 -1
- pygpt_net/data/locale/locale.de.ini +4 -1
- pygpt_net/data/locale/locale.en.ini +4 -1
- pygpt_net/data/locale/locale.es.ini +4 -1
- pygpt_net/data/locale/locale.fr.ini +4 -1
- pygpt_net/data/locale/locale.it.ini +4 -1
- pygpt_net/data/locale/locale.pl.ini +5 -2
- pygpt_net/data/locale/locale.uk.ini +4 -1
- pygpt_net/data/locale/locale.zh.ini +4 -1
- pygpt_net/item/model.py +1 -1
- pygpt_net/provider/api/openai/__init__.py +4 -2
- pygpt_net/provider/api/openai/video.py +2 -2
- pygpt_net/provider/core/config/patch.py +9 -1
- pygpt_net/provider/core/model/patch.py +26 -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/models.py +10 -1
- pygpt_net/ui/layout/ctx/ctx_list.py +16 -6
- pygpt_net/ui/layout/toolbox/video.py +14 -6
- pygpt_net/ui/main.py +3 -1
- pygpt_net/ui/widget/calendar/select.py +3 -3
- pygpt_net/ui/widget/filesystem/explorer.py +1082 -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 +1046 -32
- pygpt_net/ui/widget/option/dictionary.py +35 -7
- pygpt_net/ui/widget/option/input.py +3 -1
- {pygpt_net-2.6.66.dist-info → pygpt_net-2.7.0.dist-info}/METADATA +20 -57
- {pygpt_net-2.6.66.dist-info → pygpt_net-2.7.0.dist-info}/RECORD +81 -81
- {pygpt_net-2.6.66.dist-info → pygpt_net-2.7.0.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.66.dist-info → pygpt_net-2.7.0.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.66.dist-info → pygpt_net-2.7.0.dist-info}/entry_points.txt +0 -0
|
@@ -6,12 +6,12 @@
|
|
|
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
|
from PySide6.QtCore import QItemSelectionModel, QPoint
|
|
13
13
|
from PySide6.QtGui import QAction, QIcon, Qt
|
|
14
|
-
from PySide6.QtWidgets import QMenu
|
|
14
|
+
from PySide6.QtWidgets import QMenu, QAbstractItemView
|
|
15
15
|
|
|
16
16
|
from pygpt_net.ui.widget.lists.base import BaseList
|
|
17
17
|
from pygpt_net.utils import trans
|
|
@@ -20,7 +20,7 @@ from pygpt_net.utils import trans
|
|
|
20
20
|
class AssistantList(BaseList):
|
|
21
21
|
def __init__(self, window=None, id=None):
|
|
22
22
|
"""
|
|
23
|
-
|
|
23
|
+
Assistants select menu
|
|
24
24
|
|
|
25
25
|
:param window: main window
|
|
26
26
|
:param id: input id
|
|
@@ -34,7 +34,61 @@ class AssistantList(BaseList):
|
|
|
34
34
|
self.restore_after_ctx_menu = True
|
|
35
35
|
self._backup_selection = None
|
|
36
36
|
|
|
37
|
+
# Enable row-based multi-select with Ctrl/Shift gestures
|
|
38
|
+
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
39
|
+
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
|
40
|
+
|
|
41
|
+
# Virtual multi-select helpers to suppress business click after Ctrl/Shift
|
|
42
|
+
self._suppress_item_click = False
|
|
43
|
+
self._ctrl_multi_active = False
|
|
44
|
+
self._ctrl_multi_index = None
|
|
45
|
+
self._was_shift_click = False
|
|
46
|
+
|
|
47
|
+
# ----------------------------
|
|
48
|
+
# Helpers
|
|
49
|
+
# ----------------------------
|
|
50
|
+
|
|
51
|
+
def _selected_indexes(self):
|
|
52
|
+
"""Return list of selected row indexes (column 0)."""
|
|
53
|
+
try:
|
|
54
|
+
return list(self.selectionModel().selectedRows())
|
|
55
|
+
except Exception:
|
|
56
|
+
return []
|
|
57
|
+
|
|
58
|
+
def _selected_rows(self) -> list[int]:
|
|
59
|
+
"""Return list of selected row numbers."""
|
|
60
|
+
try:
|
|
61
|
+
return [ix.row() for ix in self.selectionModel().selectedRows()]
|
|
62
|
+
except Exception:
|
|
63
|
+
return []
|
|
64
|
+
|
|
65
|
+
def _has_multi_selection(self) -> bool:
|
|
66
|
+
"""Check whether more than one row is selected."""
|
|
67
|
+
try:
|
|
68
|
+
return len(self.selectionModel().selectedRows()) > 1
|
|
69
|
+
except Exception:
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
# ----------------------------
|
|
73
|
+
# Clicks
|
|
74
|
+
# ----------------------------
|
|
75
|
+
|
|
37
76
|
def click(self, val):
|
|
77
|
+
"""
|
|
78
|
+
Row click handler.
|
|
79
|
+
|
|
80
|
+
Suppresses business click when triggered by virtual Ctrl/Shift multi-select
|
|
81
|
+
and when multiple rows are selected.
|
|
82
|
+
"""
|
|
83
|
+
# Skip business click right after Ctrl/Shift selection
|
|
84
|
+
if self._suppress_item_click:
|
|
85
|
+
self._suppress_item_click = False
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# Ignore business click if multiple are selected
|
|
89
|
+
if self._has_multi_selection():
|
|
90
|
+
return
|
|
91
|
+
|
|
38
92
|
self.window.controller.assistant.select(val.row())
|
|
39
93
|
|
|
40
94
|
def dblclick(self, val):
|
|
@@ -43,7 +97,13 @@ class AssistantList(BaseList):
|
|
|
43
97
|
|
|
44
98
|
:param val: double click event
|
|
45
99
|
"""
|
|
46
|
-
|
|
100
|
+
row = val.row()
|
|
101
|
+
if row >= 0:
|
|
102
|
+
self.window.controller.assistant.editor.edit(row)
|
|
103
|
+
|
|
104
|
+
# ----------------------------
|
|
105
|
+
# Context menu
|
|
106
|
+
# ----------------------------
|
|
47
107
|
|
|
48
108
|
def show_context_menu(self, pos: QPoint):
|
|
49
109
|
"""
|
|
@@ -52,24 +112,41 @@ class AssistantList(BaseList):
|
|
|
52
112
|
:param pos: QPoint
|
|
53
113
|
"""
|
|
54
114
|
global_pos = self.viewport().mapToGlobal(pos)
|
|
55
|
-
|
|
115
|
+
index = self.indexAt(pos)
|
|
116
|
+
idx = index.row() if index.isValid() else -1
|
|
117
|
+
|
|
118
|
+
selected_rows = self._selected_rows()
|
|
119
|
+
multi = len(selected_rows) > 1
|
|
120
|
+
|
|
121
|
+
# Allow menu on empty area only when multi-selection is active
|
|
122
|
+
if not index.isValid() and not multi:
|
|
123
|
+
return
|
|
56
124
|
|
|
57
125
|
actions = {}
|
|
58
126
|
actions['edit'] = QAction(QIcon(":/icons/edit.svg"), trans('assistant.action.edit'), self)
|
|
127
|
+
actions['delete'] = QAction(QIcon(":/icons/delete.svg"), trans('assistant.action.delete'), self)
|
|
128
|
+
|
|
129
|
+
# Edit only for single selection
|
|
130
|
+
actions['edit'].setEnabled(idx >= 0 and not multi)
|
|
59
131
|
actions['edit'].triggered.connect(
|
|
60
|
-
lambda checked=False, item=
|
|
132
|
+
lambda checked=False, item=index: self.action_edit(item)
|
|
133
|
+
)
|
|
61
134
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
135
|
+
# Delete for single or multi; pass list when multi
|
|
136
|
+
if multi:
|
|
137
|
+
actions['delete'].triggered.connect(
|
|
138
|
+
lambda checked=False, rows=list(selected_rows): self.action_delete(rows)
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
actions['delete'].triggered.connect(
|
|
142
|
+
lambda checked=False, item=index: self.action_delete(item)
|
|
143
|
+
)
|
|
65
144
|
|
|
66
145
|
menu = QMenu(self)
|
|
67
146
|
menu.addAction(actions['edit'])
|
|
68
147
|
menu.addAction(actions['delete'])
|
|
69
148
|
|
|
70
|
-
idx
|
|
71
|
-
if idx >= 0:
|
|
72
|
-
#self.window.controller.assistant.select(item.row())
|
|
149
|
+
if idx >= 0 or multi:
|
|
73
150
|
menu.exec_(global_pos)
|
|
74
151
|
|
|
75
152
|
# store previous scroll position
|
|
@@ -79,9 +156,9 @@ class AssistantList(BaseList):
|
|
|
79
156
|
if self.restore_after_ctx_menu:
|
|
80
157
|
if self._backup_selection is not None:
|
|
81
158
|
self.selectionModel().clearSelection()
|
|
82
|
-
for
|
|
159
|
+
for i in self._backup_selection:
|
|
83
160
|
self.selectionModel().select(
|
|
84
|
-
|
|
161
|
+
i, QItemSelectionModel.Select | QItemSelectionModel.Rows
|
|
85
162
|
)
|
|
86
163
|
self._backup_selection = None
|
|
87
164
|
|
|
@@ -89,11 +166,15 @@ class AssistantList(BaseList):
|
|
|
89
166
|
self.restore_after_ctx_menu = True
|
|
90
167
|
self.restore_scroll_position()
|
|
91
168
|
|
|
169
|
+
# ----------------------------
|
|
170
|
+
# Context actions
|
|
171
|
+
# ----------------------------
|
|
172
|
+
|
|
92
173
|
def action_edit(self, item):
|
|
93
174
|
"""
|
|
94
175
|
Edit action handler
|
|
95
176
|
|
|
96
|
-
:param item:
|
|
177
|
+
:param item: QModelIndex
|
|
97
178
|
"""
|
|
98
179
|
idx = item.row()
|
|
99
180
|
if idx >= 0:
|
|
@@ -104,35 +185,115 @@ class AssistantList(BaseList):
|
|
|
104
185
|
"""
|
|
105
186
|
Delete action handler
|
|
106
187
|
|
|
107
|
-
:param item: list
|
|
188
|
+
:param item: QModelIndex for single, or list[int] for multi
|
|
108
189
|
"""
|
|
190
|
+
if isinstance(item, (list, tuple)):
|
|
191
|
+
self.restore_after_ctx_menu = False # do not restore selection after context menu
|
|
192
|
+
self.window.controller.assistant.delete(list(item))
|
|
193
|
+
return
|
|
194
|
+
|
|
109
195
|
idx = item.row()
|
|
110
196
|
if idx >= 0:
|
|
111
197
|
self.restore_after_ctx_menu = False # do not restore selection after context menu
|
|
112
198
|
self.window.controller.assistant.delete(idx)
|
|
113
199
|
|
|
200
|
+
# ----------------------------
|
|
201
|
+
# Mouse events (virtual multi-select)
|
|
202
|
+
# ----------------------------
|
|
203
|
+
|
|
114
204
|
def mousePressEvent(self, event):
|
|
205
|
+
# Ctrl+Left: virtual toggle without business click
|
|
206
|
+
if event.button() == Qt.LeftButton and (event.modifiers() & Qt.ControlModifier):
|
|
207
|
+
idx = self.indexAt(event.pos())
|
|
208
|
+
self._suppress_item_click = True
|
|
209
|
+
if idx.isValid():
|
|
210
|
+
self._ctrl_multi_active = True
|
|
211
|
+
self._ctrl_multi_index = idx
|
|
212
|
+
event.accept()
|
|
213
|
+
return
|
|
214
|
+
# Ctrl on empty area -> just suppress business click
|
|
215
|
+
event.accept()
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
# Shift+Left: let Qt perform range selection, suppress business click
|
|
219
|
+
if event.button() == Qt.LeftButton and (event.modifiers() & Qt.ShiftModifier):
|
|
220
|
+
idx = self.indexAt(event.pos())
|
|
221
|
+
self._suppress_item_click = True
|
|
222
|
+
self._was_shift_click = True
|
|
223
|
+
if idx.isValid():
|
|
224
|
+
super().mousePressEvent(event) # default range selection behavior
|
|
225
|
+
else:
|
|
226
|
+
event.accept()
|
|
227
|
+
return
|
|
228
|
+
|
|
115
229
|
if event.button() == Qt.LeftButton:
|
|
116
230
|
index = self.indexAt(event.pos())
|
|
117
|
-
|
|
231
|
+
|
|
232
|
+
# When multiple are selected, a single click clears the multi-selection
|
|
233
|
+
if self._has_multi_selection():
|
|
234
|
+
self.selectionModel().clearSelection()
|
|
235
|
+
if not index.isValid():
|
|
236
|
+
event.accept()
|
|
237
|
+
return
|
|
238
|
+
# continue with default single selection for clicked row
|
|
239
|
+
|
|
240
|
+
# Proceed with default handling
|
|
241
|
+
if index.isValid():
|
|
242
|
+
super().mousePressEvent(event)
|
|
118
243
|
return
|
|
119
|
-
|
|
244
|
+
else:
|
|
245
|
+
# Click on empty area -> clear any single selection
|
|
246
|
+
self.selectionModel().clearSelection()
|
|
247
|
+
event.accept()
|
|
248
|
+
return
|
|
249
|
+
|
|
120
250
|
elif event.button() == Qt.RightButton:
|
|
121
251
|
index = self.indexAt(event.pos())
|
|
252
|
+
sel_model = self.selectionModel()
|
|
253
|
+
selected_rows = [ix.row() for ix in sel_model.selectedRows()]
|
|
254
|
+
multi = len(selected_rows) > 1
|
|
255
|
+
|
|
122
256
|
if index.isValid():
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
257
|
+
if multi and index.row() in selected_rows:
|
|
258
|
+
# Keep existing multi-selection; do not alter selection on right click
|
|
259
|
+
self._backup_selection = None
|
|
260
|
+
else:
|
|
261
|
+
# Select the clicked row temporarily for single or when clicking outside current multi
|
|
262
|
+
self._backup_selection = list(sel_model.selectedIndexes())
|
|
263
|
+
sel_model.clearSelection()
|
|
264
|
+
sel_model.select(index, QItemSelectionModel.Select | QItemSelectionModel.Rows)
|
|
128
265
|
event.accept()
|
|
266
|
+
return
|
|
267
|
+
|
|
129
268
|
else:
|
|
130
269
|
super().mousePressEvent(event)
|
|
131
270
|
|
|
271
|
+
def mouseReleaseEvent(self, event):
|
|
272
|
+
# Finish Shift-range selection: skip business click path
|
|
273
|
+
if event.button() == Qt.LeftButton and self._was_shift_click:
|
|
274
|
+
self._was_shift_click = False
|
|
275
|
+
super().mouseReleaseEvent(event)
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
# Finish "virtual" Ctrl toggle on same row
|
|
279
|
+
if event.button() == Qt.LeftButton and self._ctrl_multi_active:
|
|
280
|
+
try:
|
|
281
|
+
idx = self.indexAt(event.pos())
|
|
282
|
+
if idx.isValid() and self._ctrl_multi_index and idx == self._ctrl_multi_index:
|
|
283
|
+
sel_model = self.selectionModel()
|
|
284
|
+
sel_model.select(idx, QItemSelectionModel.Toggle | QItemSelectionModel.Rows)
|
|
285
|
+
finally:
|
|
286
|
+
self._ctrl_multi_active = False
|
|
287
|
+
self._ctrl_multi_index = None
|
|
288
|
+
event.accept()
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
super().mouseReleaseEvent(event)
|
|
292
|
+
|
|
132
293
|
def selectionCommand(self, index, event=None):
|
|
133
294
|
"""
|
|
134
295
|
Selection command
|
|
135
296
|
:param index: Index
|
|
136
297
|
:param event: Event
|
|
137
298
|
"""
|
|
138
|
-
return super().selectionCommand(index, event)
|
|
299
|
+
return super().selectionCommand(index, event)
|
|
@@ -6,11 +6,12 @@
|
|
|
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.12.27 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtGui import QAction, QIcon
|
|
13
|
-
from PySide6.
|
|
13
|
+
from PySide6.QtCore import Qt, QItemSelectionModel
|
|
14
|
+
from PySide6.QtWidgets import QMenu, QAbstractItemView
|
|
14
15
|
|
|
15
16
|
from pygpt_net.ui.widget.lists.base import BaseList
|
|
16
17
|
from pygpt_net.utils import trans
|
|
@@ -29,9 +30,153 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
29
30
|
self.window = window
|
|
30
31
|
self.id = id
|
|
31
32
|
|
|
33
|
+
# Virtual multi-select helpers
|
|
34
|
+
self._suppress_item_click = False # suppress business click after Ctrl/Shift selection
|
|
35
|
+
self._ctrl_multi_active = False # Ctrl gesture in progress
|
|
36
|
+
self._ctrl_multi_index = None
|
|
37
|
+
self._was_shift_click = False # Shift range gesture
|
|
38
|
+
|
|
39
|
+
# Context menu selection backup (temporary right-click selection)
|
|
40
|
+
self._backup_selection = None
|
|
41
|
+
self.restore_after_ctx_menu = True
|
|
42
|
+
|
|
43
|
+
# Row-based multi-selection behavior
|
|
44
|
+
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
45
|
+
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
|
46
|
+
|
|
47
|
+
# Disable default BaseList click handler; business action is handled manually
|
|
48
|
+
self.clicked.disconnect(self.click)
|
|
49
|
+
|
|
50
|
+
# ----------------------------
|
|
51
|
+
# Selection helpers
|
|
52
|
+
# ----------------------------
|
|
53
|
+
|
|
54
|
+
def _selected_rows(self) -> list[int]:
|
|
55
|
+
"""Return list of selected row numbers."""
|
|
56
|
+
try:
|
|
57
|
+
return sorted([ix.row() for ix in self.selectionModel().selectedRows()])
|
|
58
|
+
except Exception:
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
def _has_multi_selection(self) -> bool:
|
|
62
|
+
"""Check whether more than one row is selected."""
|
|
63
|
+
try:
|
|
64
|
+
return len(self.selectionModel().selectedRows()) > 1
|
|
65
|
+
except Exception:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
# ----------------------------
|
|
69
|
+
# Mouse events (virtual multi-select)
|
|
70
|
+
# ----------------------------
|
|
71
|
+
|
|
72
|
+
def mousePressEvent(self, event):
|
|
73
|
+
"""
|
|
74
|
+
Mouse press event
|
|
75
|
+
|
|
76
|
+
:param event: mouse event
|
|
77
|
+
"""
|
|
78
|
+
# Ctrl+Left: virtual toggle without business click
|
|
79
|
+
if event.button() == Qt.LeftButton and (event.modifiers() & Qt.ControlModifier):
|
|
80
|
+
idx = self.indexAt(event.pos())
|
|
81
|
+
if idx.isValid():
|
|
82
|
+
self._ctrl_multi_active = True
|
|
83
|
+
self._ctrl_multi_index = idx
|
|
84
|
+
self._suppress_item_click = True
|
|
85
|
+
event.accept()
|
|
86
|
+
return
|
|
87
|
+
self._suppress_item_click = True
|
|
88
|
+
event.accept()
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Shift+Left: let Qt perform range selection (anchor->clicked), suppress business click
|
|
92
|
+
if event.button() == Qt.LeftButton and (event.modifiers() & Qt.ShiftModifier):
|
|
93
|
+
idx = self.indexAt(event.pos())
|
|
94
|
+
self._suppress_item_click = True
|
|
95
|
+
self._was_shift_click = True
|
|
96
|
+
if idx.isValid():
|
|
97
|
+
super(AssistantVectorStoreEditorList, self).mousePressEvent(event)
|
|
98
|
+
else:
|
|
99
|
+
event.accept()
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Plain left click
|
|
103
|
+
if event.button() == Qt.LeftButton:
|
|
104
|
+
idx = self.indexAt(event.pos())
|
|
105
|
+
|
|
106
|
+
# When multiple are selected, a single plain click clears the multi-selection.
|
|
107
|
+
if self._has_multi_selection():
|
|
108
|
+
sel_model = self.selectionModel()
|
|
109
|
+
sel_model.clearSelection()
|
|
110
|
+
if not idx.isValid():
|
|
111
|
+
event.accept()
|
|
112
|
+
return
|
|
113
|
+
# continue with default single selection for clicked row
|
|
114
|
+
|
|
115
|
+
super(AssistantVectorStoreEditorList, self).mousePressEvent(event)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# Right click: prepare selection for context menu
|
|
119
|
+
if event.button() == Qt.RightButton:
|
|
120
|
+
idx = self.indexAt(event.pos())
|
|
121
|
+
sel_model = self.selectionModel()
|
|
122
|
+
selected_rows = [ix.row() for ix in sel_model.selectedRows()]
|
|
123
|
+
multi = len(selected_rows) > 1
|
|
124
|
+
|
|
125
|
+
if idx.isValid():
|
|
126
|
+
if multi and idx.row() in selected_rows:
|
|
127
|
+
# Keep existing multi-selection; do not alter selection on right click
|
|
128
|
+
self._backup_selection = None
|
|
129
|
+
else:
|
|
130
|
+
# Temporarily select the clicked row; backup previous selection to restore later
|
|
131
|
+
self._backup_selection = list(sel_model.selectedIndexes())
|
|
132
|
+
sel_model.clearSelection()
|
|
133
|
+
sel_model.select(idx, QItemSelectionModel.Select | QItemSelectionModel.Rows)
|
|
134
|
+
event.accept()
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
super(AssistantVectorStoreEditorList, self).mousePressEvent(event)
|
|
138
|
+
|
|
139
|
+
def mouseReleaseEvent(self, event):
|
|
140
|
+
# If the click was a Shift-based range selection, bypass business click
|
|
141
|
+
if event.button() == Qt.LeftButton and self._was_shift_click:
|
|
142
|
+
self._was_shift_click = False
|
|
143
|
+
self._suppress_item_click = False
|
|
144
|
+
super(AssistantVectorStoreEditorList, self).mouseReleaseEvent(event)
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
# Finish "virtual" Ctrl toggle on same row (no business click)
|
|
148
|
+
if event.button() == Qt.LeftButton and self._ctrl_multi_active:
|
|
149
|
+
try:
|
|
150
|
+
idx = self.indexAt(event.pos())
|
|
151
|
+
if idx.isValid() and self._ctrl_multi_index and idx == self._ctrl_multi_index:
|
|
152
|
+
sel_model = self.selectionModel()
|
|
153
|
+
sel_model.select(idx, QItemSelectionModel.Toggle | QItemSelectionModel.Rows)
|
|
154
|
+
finally:
|
|
155
|
+
self._ctrl_multi_active = False
|
|
156
|
+
self._ctrl_multi_index = None
|
|
157
|
+
self._suppress_item_click = False
|
|
158
|
+
event.accept()
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
# Plain left: perform business selection only for single selection
|
|
162
|
+
if event.button() == Qt.LeftButton:
|
|
163
|
+
idx = self.indexAt(event.pos())
|
|
164
|
+
if not self._has_multi_selection():
|
|
165
|
+
if idx.isValid() and not self._suppress_item_click:
|
|
166
|
+
self.window.controller.assistant.store.select(idx.row())
|
|
167
|
+
self._suppress_item_click = False
|
|
168
|
+
super(AssistantVectorStoreEditorList, self).mouseReleaseEvent(event)
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
super(AssistantVectorStoreEditorList, self).mouseReleaseEvent(event)
|
|
172
|
+
|
|
32
173
|
def click(self, val):
|
|
33
|
-
|
|
34
|
-
|
|
174
|
+
# Not used; single-selection business click is handled in mouseReleaseEvent
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
# ----------------------------
|
|
178
|
+
# Context menu
|
|
179
|
+
# ----------------------------
|
|
35
180
|
|
|
36
181
|
def contextMenuEvent(self, event):
|
|
37
182
|
"""
|
|
@@ -40,24 +185,22 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
40
185
|
:param event: context menu event
|
|
41
186
|
"""
|
|
42
187
|
actions = {}
|
|
43
|
-
actions['refresh'] = QAction(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
188
|
+
actions['refresh'] = QAction(
|
|
189
|
+
QIcon(":/icons/reload.svg"),
|
|
190
|
+
trans('dialog.assistant.store.menu.current.refresh_store'),
|
|
191
|
+
self
|
|
192
|
+
)
|
|
49
193
|
actions['delete'] = QAction(QIcon(":/icons/delete.svg"), trans('action.delete'), self)
|
|
50
|
-
actions['
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
lambda: self.action_truncate(event))
|
|
194
|
+
actions['clear'] = QAction(
|
|
195
|
+
QIcon(":/icons/close.svg"),
|
|
196
|
+
trans('dialog.assistant.store.menu.current.clear_files'),
|
|
197
|
+
self
|
|
198
|
+
)
|
|
199
|
+
actions['truncate'] = QAction(
|
|
200
|
+
QIcon(":/icons/delete.svg"),
|
|
201
|
+
trans('dialog.assistant.store.menu.current.truncate_files'),
|
|
202
|
+
self
|
|
203
|
+
)
|
|
61
204
|
|
|
62
205
|
menu = QMenu(self)
|
|
63
206
|
menu.addAction(actions['refresh'])
|
|
@@ -65,50 +208,110 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
65
208
|
menu.addAction(actions['clear'])
|
|
66
209
|
menu.addAction(actions['truncate'])
|
|
67
210
|
|
|
68
|
-
|
|
69
|
-
idx =
|
|
70
|
-
|
|
71
|
-
|
|
211
|
+
index = self.indexAt(event.pos())
|
|
212
|
+
idx = index.row() if index.isValid() else -1
|
|
213
|
+
|
|
214
|
+
# Selection state for multi / single
|
|
215
|
+
selected_rows = self._selected_rows()
|
|
216
|
+
multi = len(selected_rows) > 1
|
|
72
217
|
|
|
73
|
-
|
|
218
|
+
# Allow menu on empty area only when multi-selection is active
|
|
219
|
+
if not index.isValid() and not multi:
|
|
220
|
+
if self._backup_selection is not None and self.restore_after_ctx_menu:
|
|
221
|
+
sel_model = self.selectionModel()
|
|
222
|
+
sel_model.clearSelection()
|
|
223
|
+
for i in self._backup_selection:
|
|
224
|
+
sel_model.select(i, QItemSelectionModel.Select | QItemSelectionModel.Rows)
|
|
225
|
+
self._backup_selection = None
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
# Route actions: pass list on multi, int on single
|
|
229
|
+
if multi:
|
|
230
|
+
actions['refresh'].triggered.connect(lambda: self.action_refresh(list(selected_rows)))
|
|
231
|
+
actions['delete'].triggered.connect(lambda: self.action_delete(list(selected_rows)))
|
|
232
|
+
actions['clear'].triggered.connect(lambda: self.action_clear(list(selected_rows)))
|
|
233
|
+
actions['truncate'].triggered.connect(lambda: self.action_truncate(list(selected_rows)))
|
|
234
|
+
else:
|
|
235
|
+
actions['refresh'].triggered.connect(lambda: self.action_refresh(idx))
|
|
236
|
+
actions['delete'].triggered.connect(lambda: self.action_delete(idx))
|
|
237
|
+
actions['clear'].triggered.connect(lambda: self.action_clear(idx))
|
|
238
|
+
actions['truncate'].triggered.connect(lambda: self.action_truncate(idx))
|
|
239
|
+
|
|
240
|
+
menu.exec_(event.globalPos())
|
|
241
|
+
|
|
242
|
+
# Restore selection after context menu if it was temporarily changed
|
|
243
|
+
if self.restore_after_ctx_menu and self._backup_selection is not None:
|
|
244
|
+
sel_model = self.selectionModel()
|
|
245
|
+
sel_model.clearSelection()
|
|
246
|
+
for i in self._backup_selection:
|
|
247
|
+
sel_model.select(i, QItemSelectionModel.Select | QItemSelectionModel.Rows)
|
|
248
|
+
self._backup_selection = None
|
|
249
|
+
self.restore_after_ctx_menu = True
|
|
250
|
+
|
|
251
|
+
# ----------------------------
|
|
252
|
+
# Context actions (single or multi)
|
|
253
|
+
# If 'item' is a list/tuple -> pass list of row ints to external code.
|
|
254
|
+
# If 'item' is an int -> pass single row int to external code.
|
|
255
|
+
# ----------------------------
|
|
256
|
+
|
|
257
|
+
def action_delete(self, item):
|
|
74
258
|
"""
|
|
75
259
|
Delete action handler
|
|
76
260
|
|
|
77
|
-
:param
|
|
261
|
+
:param item: int row or list of rows
|
|
78
262
|
"""
|
|
79
|
-
item
|
|
80
|
-
|
|
263
|
+
if isinstance(item, (list, tuple)):
|
|
264
|
+
if item:
|
|
265
|
+
self.restore_after_ctx_menu = False
|
|
266
|
+
self.window.controller.assistant.store.delete_by_idx(list(item))
|
|
267
|
+
return
|
|
268
|
+
idx = int(item)
|
|
81
269
|
if idx >= 0:
|
|
270
|
+
self.restore_after_ctx_menu = False
|
|
82
271
|
self.window.controller.assistant.store.delete_by_idx(idx)
|
|
83
272
|
|
|
84
|
-
def action_clear(self,
|
|
273
|
+
def action_clear(self, item):
|
|
85
274
|
"""
|
|
86
275
|
Clear action handler
|
|
87
276
|
|
|
88
|
-
:param
|
|
277
|
+
:param item: int row or list of rows
|
|
89
278
|
"""
|
|
90
|
-
item
|
|
91
|
-
|
|
279
|
+
if isinstance(item, (list, tuple)):
|
|
280
|
+
if item:
|
|
281
|
+
self.restore_after_ctx_menu = False
|
|
282
|
+
self.window.controller.assistant.batch.clear_store_files_by_idx(list(item))
|
|
283
|
+
return
|
|
284
|
+
idx = int(item)
|
|
92
285
|
if idx >= 0:
|
|
286
|
+
self.restore_after_ctx_menu = False
|
|
93
287
|
self.window.controller.assistant.batch.clear_store_files_by_idx(idx)
|
|
94
288
|
|
|
95
|
-
def action_truncate(self,
|
|
289
|
+
def action_truncate(self, item):
|
|
96
290
|
"""
|
|
97
291
|
Truncate action handler
|
|
98
292
|
|
|
99
|
-
:param
|
|
293
|
+
:param item: int row or list of rows
|
|
100
294
|
"""
|
|
101
|
-
item
|
|
102
|
-
|
|
295
|
+
if isinstance(item, (list, tuple)):
|
|
296
|
+
if item:
|
|
297
|
+
self.restore_after_ctx_menu = False
|
|
298
|
+
self.window.controller.assistant.batch.truncate_store_files_by_idx(list(item))
|
|
299
|
+
return
|
|
300
|
+
idx = int(item)
|
|
103
301
|
if idx >= 0:
|
|
302
|
+
self.restore_after_ctx_menu = False
|
|
104
303
|
self.window.controller.assistant.batch.truncate_store_files_by_idx(idx)
|
|
105
304
|
|
|
106
|
-
def action_refresh(self,
|
|
305
|
+
def action_refresh(self, item):
|
|
107
306
|
"""
|
|
108
307
|
Refresh action handler
|
|
109
308
|
"""
|
|
110
|
-
item
|
|
111
|
-
|
|
309
|
+
if isinstance(item, (list, tuple)):
|
|
310
|
+
if item:
|
|
311
|
+
self.restore_after_ctx_menu = False
|
|
312
|
+
self.window.controller.assistant.store.refresh_by_idx(list(item))
|
|
313
|
+
return
|
|
314
|
+
idx = int(item)
|
|
112
315
|
if idx >= 0:
|
|
113
|
-
self.
|
|
114
|
-
|
|
316
|
+
self.restore_after_ctx_menu = False
|
|
317
|
+
self.window.controller.assistant.store.refresh_by_idx(idx)
|