pygpt-net 2.7.6__py3-none-any.whl → 2.7.8__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.
Files changed (120) hide show
  1. pygpt_net/CHANGELOG.txt +13 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +5 -1
  4. pygpt_net/controller/assistant/batch.py +2 -2
  5. pygpt_net/controller/assistant/files.py +7 -6
  6. pygpt_net/controller/assistant/threads.py +0 -0
  7. pygpt_net/controller/chat/command.py +0 -0
  8. pygpt_net/controller/chat/remote_tools.py +3 -9
  9. pygpt_net/controller/chat/stream.py +2 -2
  10. pygpt_net/controller/chat/{handler/worker.py → stream_worker.py} +13 -35
  11. pygpt_net/controller/dialogs/confirm.py +35 -58
  12. pygpt_net/controller/lang/mapping.py +9 -9
  13. pygpt_net/controller/remote_store/{google/batch.py → batch.py} +209 -252
  14. pygpt_net/controller/remote_store/remote_store.py +982 -13
  15. pygpt_net/core/command/command.py +0 -0
  16. pygpt_net/core/db/viewer.py +1 -1
  17. pygpt_net/core/debug/models.py +2 -2
  18. pygpt_net/core/realtime/worker.py +3 -1
  19. pygpt_net/{controller/remote_store/google → core/remote_store/anthropic}/__init__.py +0 -1
  20. pygpt_net/core/remote_store/anthropic/files.py +211 -0
  21. pygpt_net/core/remote_store/anthropic/store.py +208 -0
  22. pygpt_net/core/remote_store/openai/store.py +5 -4
  23. pygpt_net/core/remote_store/remote_store.py +5 -1
  24. pygpt_net/{controller/remote_store/openai → core/remote_store/xai}/__init__.py +0 -1
  25. pygpt_net/core/remote_store/xai/files.py +225 -0
  26. pygpt_net/core/remote_store/xai/store.py +219 -0
  27. pygpt_net/data/config/config.json +18 -5
  28. pygpt_net/data/config/models.json +193 -4
  29. pygpt_net/data/config/settings.json +179 -36
  30. pygpt_net/data/icons/folder_eye.svg +1 -0
  31. pygpt_net/data/icons/folder_eye_filled.svg +1 -0
  32. pygpt_net/data/icons/folder_open.svg +1 -0
  33. pygpt_net/data/icons/folder_open_filled.svg +1 -0
  34. pygpt_net/data/locale/locale.de.ini +6 -3
  35. pygpt_net/data/locale/locale.en.ini +46 -12
  36. pygpt_net/data/locale/locale.es.ini +6 -3
  37. pygpt_net/data/locale/locale.fr.ini +6 -3
  38. pygpt_net/data/locale/locale.it.ini +6 -3
  39. pygpt_net/data/locale/locale.pl.ini +7 -4
  40. pygpt_net/data/locale/locale.uk.ini +6 -3
  41. pygpt_net/data/locale/locale.zh.ini +6 -3
  42. pygpt_net/icons.qrc +4 -0
  43. pygpt_net/icons_rc.py +282 -138
  44. pygpt_net/plugin/cmd_mouse_control/worker.py +2 -1
  45. pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +2 -1
  46. pygpt_net/provider/api/anthropic/__init__.py +10 -3
  47. pygpt_net/provider/api/anthropic/chat.py +342 -11
  48. pygpt_net/provider/api/anthropic/computer.py +844 -0
  49. pygpt_net/provider/api/anthropic/remote_tools.py +172 -0
  50. pygpt_net/provider/api/anthropic/store.py +307 -0
  51. pygpt_net/{controller/chat/handler/anthropic_stream.py → provider/api/anthropic/stream.py} +99 -10
  52. pygpt_net/provider/api/anthropic/tools.py +32 -77
  53. pygpt_net/provider/api/anthropic/utils.py +30 -0
  54. pygpt_net/{controller/chat/handler → provider/api/anthropic/worker}/__init__.py +0 -0
  55. pygpt_net/provider/api/anthropic/worker/importer.py +278 -0
  56. pygpt_net/provider/api/google/chat.py +62 -9
  57. pygpt_net/provider/api/google/store.py +124 -3
  58. pygpt_net/{controller/chat/handler/google_stream.py → provider/api/google/stream.py} +92 -25
  59. pygpt_net/provider/api/google/utils.py +185 -0
  60. pygpt_net/provider/api/google/worker/importer.py +16 -28
  61. pygpt_net/provider/api/langchain/__init__.py +0 -0
  62. pygpt_net/{controller/chat/handler/langchain_stream.py → provider/api/langchain/stream.py} +1 -1
  63. pygpt_net/provider/api/llama_index/__init__.py +0 -0
  64. pygpt_net/{controller/chat/handler/llamaindex_stream.py → provider/api/llama_index/stream.py} +1 -1
  65. pygpt_net/provider/api/openai/assistants.py +2 -2
  66. pygpt_net/provider/api/openai/image.py +2 -2
  67. pygpt_net/provider/api/openai/store.py +4 -1
  68. pygpt_net/{controller/chat/handler/openai_stream.py → provider/api/openai/stream.py} +1 -1
  69. pygpt_net/provider/api/openai/utils.py +69 -3
  70. pygpt_net/provider/api/openai/worker/importer.py +19 -61
  71. pygpt_net/provider/api/openai/worker/importer_assistants.py +230 -0
  72. pygpt_net/provider/api/x_ai/__init__.py +138 -15
  73. pygpt_net/provider/api/x_ai/audio.py +43 -11
  74. pygpt_net/provider/api/x_ai/chat.py +92 -4
  75. pygpt_net/provider/api/x_ai/image.py +149 -47
  76. pygpt_net/provider/api/x_ai/realtime/__init__.py +12 -0
  77. pygpt_net/provider/api/x_ai/realtime/client.py +1825 -0
  78. pygpt_net/provider/api/x_ai/realtime/realtime.py +198 -0
  79. pygpt_net/provider/api/x_ai/{remote.py → remote_tools.py} +183 -70
  80. pygpt_net/provider/api/x_ai/responses.py +507 -0
  81. pygpt_net/provider/api/x_ai/store.py +610 -0
  82. pygpt_net/{controller/chat/handler/xai_stream.py → provider/api/x_ai/stream.py} +42 -10
  83. pygpt_net/provider/api/x_ai/tools.py +59 -8
  84. pygpt_net/{controller/chat/handler → provider/api/x_ai}/utils.py +1 -2
  85. pygpt_net/provider/api/x_ai/vision.py +1 -4
  86. pygpt_net/provider/api/x_ai/worker/importer.py +308 -0
  87. pygpt_net/provider/audio_input/xai_grok_voice.py +390 -0
  88. pygpt_net/provider/audio_output/xai_tts.py +325 -0
  89. pygpt_net/provider/core/config/patch.py +39 -3
  90. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
  91. pygpt_net/provider/core/model/patch.py +39 -1
  92. pygpt_net/tools/image_viewer/tool.py +334 -34
  93. pygpt_net/tools/image_viewer/ui/dialogs.py +319 -22
  94. pygpt_net/tools/text_editor/ui/dialogs.py +3 -2
  95. pygpt_net/tools/text_editor/ui/widgets.py +0 -0
  96. pygpt_net/ui/dialog/assistant.py +1 -1
  97. pygpt_net/ui/dialog/plugins.py +13 -5
  98. pygpt_net/ui/dialog/remote_store.py +552 -0
  99. pygpt_net/ui/dialogs.py +3 -5
  100. pygpt_net/ui/layout/ctx/ctx_list.py +58 -7
  101. pygpt_net/ui/menu/tools.py +6 -13
  102. pygpt_net/ui/widget/dialog/base.py +16 -5
  103. pygpt_net/ui/widget/dialog/{remote_store_google.py → remote_store.py} +10 -10
  104. pygpt_net/ui/widget/element/button.py +4 -4
  105. pygpt_net/ui/widget/image/display.py +2 -2
  106. pygpt_net/ui/widget/lists/context.py +2 -2
  107. pygpt_net/ui/widget/textarea/editor.py +0 -0
  108. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/METADATA +15 -2
  109. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/RECORD +107 -89
  110. pygpt_net/controller/remote_store/google/store.py +0 -615
  111. pygpt_net/controller/remote_store/openai/batch.py +0 -524
  112. pygpt_net/controller/remote_store/openai/store.py +0 -699
  113. pygpt_net/ui/dialog/remote_store_google.py +0 -539
  114. pygpt_net/ui/dialog/remote_store_openai.py +0 -539
  115. pygpt_net/ui/widget/dialog/remote_store_openai.py +0 -56
  116. pygpt_net/ui/widget/lists/remote_store_google.py +0 -248
  117. pygpt_net/ui/widget/lists/remote_store_openai.py +0 -317
  118. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/LICENSE +0 -0
  119. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/WHEEL +0 -0
  120. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/entry_points.txt +0 -0
@@ -1,248 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # ================================================== #
4
- # This file is a part of PYGPT package #
5
- # Website: https://pygpt.net #
6
- # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
- # MIT License #
8
- # Created By : Marcin Szczygliński #
9
- # Updated Date: 2026.01.02 20:00:00 #
10
- # ================================================== #
11
-
12
- from PySide6.QtGui import QAction, QIcon
13
- from PySide6.QtCore import Qt, QItemSelectionModel
14
- from PySide6.QtWidgets import QMenu, QAbstractItemView
15
-
16
- from pygpt_net.ui.widget.lists.base import BaseList
17
- from pygpt_net.utils import trans
18
- import pygpt_net.icons_rc
19
-
20
-
21
- class RemoteStoreGoogleEditorList(BaseList):
22
- def __init__(self, window=None, id=None):
23
- """
24
- Store select menu (in editor) - Google File Search
25
-
26
- :param window: main window
27
- :param id: parent id
28
- """
29
- super(RemoteStoreGoogleEditorList, self).__init__(window)
30
- self.window = window
31
- self.id = id
32
-
33
- self._suppress_item_click = False
34
- self._ctrl_multi_active = False
35
- self._ctrl_multi_index = None
36
- self._was_shift_click = False
37
-
38
- self._backup_selection = None
39
- self.restore_after_ctx_menu = True
40
-
41
- self.setSelectionBehavior(QAbstractItemView.SelectRows)
42
- self.setSelectionMode(QAbstractItemView.ExtendedSelection)
43
-
44
- # Disable BaseList click handler; business action handled manually
45
- self.clicked.disconnect(self.click)
46
-
47
- def _selected_rows(self) -> list[int]:
48
- try:
49
- return sorted([ix.row() for ix in self.selectionModel().selectedRows()])
50
- except Exception:
51
- return []
52
-
53
- def _has_multi_selection(self) -> bool:
54
- try:
55
- return len(self.selectionModel().selectedRows()) > 1
56
- except Exception:
57
- return False
58
-
59
- def mousePressEvent(self, event):
60
- if event.button() == Qt.LeftButton and (event.modifiers() & Qt.ControlModifier):
61
- idx = self.indexAt(event.pos())
62
- if idx.isValid():
63
- self._ctrl_multi_active = True
64
- self._ctrl_multi_index = idx
65
- self._suppress_item_click = True
66
- event.accept()
67
- return
68
- self._suppress_item_click = True
69
- event.accept()
70
- return
71
-
72
- if event.button() == Qt.LeftButton and (event.modifiers() & Qt.ShiftModifier):
73
- idx = self.indexAt(event.pos())
74
- self._suppress_item_click = True
75
- self._was_shift_click = True
76
- if idx.isValid():
77
- super(RemoteStoreGoogleEditorList, self).mousePressEvent(event)
78
- else:
79
- event.accept()
80
- return
81
-
82
- if event.button() == Qt.LeftButton:
83
- idx = self.indexAt(event.pos())
84
- if self._has_multi_selection():
85
- sel_model = self.selectionModel()
86
- sel_model.clearSelection()
87
- if not idx.isValid():
88
- event.accept()
89
- return
90
- super(RemoteStoreGoogleEditorList, self).mousePressEvent(event)
91
- return
92
-
93
- if event.button() == Qt.RightButton:
94
- idx = self.indexAt(event.pos())
95
- sel_model = self.selectionModel()
96
- selected_rows = [ix.row() for ix in sel_model.selectedRows()]
97
- multi = len(selected_rows) > 1
98
-
99
- if idx.isValid():
100
- if multi and idx.row() in selected_rows:
101
- self._backup_selection = None
102
- else:
103
- self._backup_selection = list(sel_model.selectedIndexes())
104
- sel_model.clearSelection()
105
- sel_model.select(idx, QItemSelectionModel.Select | QItemSelectionModel.Rows)
106
- event.accept()
107
- return
108
-
109
- super(RemoteStoreGoogleEditorList, self).mousePressEvent(event)
110
-
111
- def mouseReleaseEvent(self, event):
112
- if event.button() == Qt.LeftButton and self._was_shift_click:
113
- self._was_shift_click = False
114
- self._suppress_item_click = False
115
- super(RemoteStoreGoogleEditorList, self).mouseReleaseEvent(event)
116
- return
117
-
118
- if event.button() == Qt.LeftButton and self._ctrl_multi_active:
119
- try:
120
- idx = self.indexAt(event.pos())
121
- if idx.isValid() and self._ctrl_multi_index and idx == self._ctrl_multi_index:
122
- sel_model = self.selectionModel()
123
- sel_model.select(idx, QItemSelectionModel.Toggle | QItemSelectionModel.Rows)
124
- finally:
125
- self._ctrl_multi_active = False
126
- self._ctrl_multi_index = None
127
- self._suppress_item_click = False
128
- event.accept()
129
- return
130
-
131
- if event.button() == Qt.LeftButton:
132
- idx = self.indexAt(event.pos())
133
- if not self._has_multi_selection():
134
- if idx.isValid() and not self._suppress_item_click:
135
- self.window.controller.remote_store.google.select(idx.row())
136
- self._suppress_item_click = False
137
- super(RemoteStoreGoogleEditorList, self).mouseReleaseEvent(event)
138
- return
139
-
140
- super(RemoteStoreGoogleEditorList, self).mouseReleaseEvent(event)
141
-
142
- def click(self, val):
143
- pass
144
-
145
- def contextMenuEvent(self, event):
146
- actions = {}
147
- actions['refresh'] = QAction(
148
- QIcon(":/icons/reload.svg"),
149
- trans('dialog.remote_store.menu.current.refresh_store'),
150
- self
151
- )
152
- actions['delete'] = QAction(QIcon(":/icons/delete.svg"), trans('action.delete'), self)
153
- actions['clear'] = QAction(
154
- QIcon(":/icons/close.svg"),
155
- trans('dialog.remote_store.menu.current.clear_files'),
156
- self
157
- )
158
- actions['truncate'] = QAction(
159
- QIcon(":/icons/delete.svg"),
160
- trans('dialog.remote_store.menu.current.truncate_files'),
161
- self
162
- )
163
-
164
- menu = QMenu(self)
165
- menu.addAction(actions['refresh'])
166
- menu.addAction(actions['delete'])
167
- menu.addAction(actions['clear'])
168
- menu.addAction(actions['truncate'])
169
-
170
- index = self.indexAt(event.pos())
171
- idx = index.row() if index.isValid() else -1
172
-
173
- selected_rows = self._selected_rows()
174
- multi = len(selected_rows) > 1
175
-
176
- if not index.isValid() and not multi:
177
- if self._backup_selection is not None and self.restore_after_ctx_menu:
178
- sel_model = self.selectionModel()
179
- sel_model.clearSelection()
180
- for i in self._backup_selection:
181
- sel_model.select(i, QItemSelectionModel.Select | QItemSelectionModel.Rows)
182
- self._backup_selection = None
183
- return
184
-
185
- if multi:
186
- actions['refresh'].triggered.connect(lambda: self.action_refresh(list(selected_rows)))
187
- actions['delete'].triggered.connect(lambda: self.action_delete(list(selected_rows)))
188
- actions['clear'].triggered.connect(lambda: self.action_clear(list(selected_rows)))
189
- actions['truncate'].triggered.connect(lambda: self.action_truncate(list(selected_rows)))
190
- else:
191
- actions['refresh'].triggered.connect(lambda: self.action_refresh(idx))
192
- actions['delete'].triggered.connect(lambda: self.action_delete(idx))
193
- actions['clear'].triggered.connect(lambda: self.action_clear(idx))
194
- actions['truncate'].triggered.connect(lambda: self.action_truncate(idx))
195
-
196
- menu.exec_(event.globalPos())
197
-
198
- if self.restore_after_ctx_menu and self._backup_selection is not None:
199
- sel_model = self.selectionModel()
200
- sel_model.clearSelection()
201
- for i in self._backup_selection:
202
- sel_model.select(i, QItemSelectionModel.Select | QItemSelectionModel.Rows)
203
- self._backup_selection = None
204
- self.restore_after_ctx_menu = True
205
-
206
- def action_delete(self, item):
207
- if isinstance(item, (list, tuple)):
208
- if item:
209
- self.restore_after_ctx_menu = False
210
- self.window.controller.remote_store.google.delete_by_idx(list(item))
211
- return
212
- idx = int(item)
213
- if idx >= 0:
214
- self.restore_after_ctx_menu = False
215
- self.window.controller.remote_store.google.delete_by_idx(idx)
216
-
217
- def action_clear(self, item):
218
- if isinstance(item, (list, tuple)):
219
- if item:
220
- self.restore_after_ctx_menu = False
221
- self.window.controller.remote_store.google.batch.clear_store_files_by_idx(list(item))
222
- return
223
- idx = int(item)
224
- if idx >= 0:
225
- self.restore_after_ctx_menu = False
226
- self.window.controller.remote_store.google.batch.clear_store_files_by_idx(idx)
227
-
228
- def action_truncate(self, item):
229
- if isinstance(item, (list, tuple)):
230
- if item:
231
- self.restore_after_ctx_menu = False
232
- self.window.controller.remote_store.google.batch.truncate_store_files_by_idx(list(item))
233
- return
234
- idx = int(item)
235
- if idx >= 0:
236
- self.restore_after_ctx_menu = False
237
- self.window.controller.remote_store.google.batch.truncate_store_files_by_idx(idx)
238
-
239
- def action_refresh(self, item):
240
- if isinstance(item, (list, tuple)):
241
- if item:
242
- self.restore_after_ctx_menu = False
243
- self.window.controller.remote_store.google.refresh_by_idx(list(item))
244
- return
245
- idx = int(item)
246
- if idx >= 0:
247
- self.restore_after_ctx_menu = False
248
- self.window.controller.remote_store.google.refresh_by_idx(idx)
@@ -1,317 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # ================================================== #
4
- # This file is a part of PYGPT package #
5
- # Website: https://pygpt.net #
6
- # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
- # MIT License #
8
- # Created By : Marcin Szczygliński #
9
- # Updated Date: 2026.01.02 19:00:00 #
10
- # ================================================== #
11
-
12
- from PySide6.QtGui import QAction, QIcon
13
- from PySide6.QtCore import Qt, QItemSelectionModel
14
- from PySide6.QtWidgets import QMenu, QAbstractItemView
15
-
16
- from pygpt_net.ui.widget.lists.base import BaseList
17
- from pygpt_net.utils import trans
18
- import pygpt_net.icons_rc
19
-
20
-
21
- class RemoteStoreOpenAIEditorList(BaseList):
22
- def __init__(self, window=None, id=None):
23
- """
24
- Store select menu (in editor)
25
-
26
- :param window: main window
27
- :param id: parent id
28
- """
29
- super(RemoteStoreOpenAIEditorList, self).__init__(window)
30
- self.window = window
31
- self.id = id
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(RemoteStoreOpenAIEditorList, 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(RemoteStoreOpenAIEditorList, 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(RemoteStoreOpenAIEditorList, 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(RemoteStoreOpenAIEditorList, 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.remote_store.openai.select(idx.row())
167
- self._suppress_item_click = False
168
- super(RemoteStoreOpenAIEditorList, self).mouseReleaseEvent(event)
169
- return
170
-
171
- super(RemoteStoreOpenAIEditorList, self).mouseReleaseEvent(event)
172
-
173
- def click(self, val):
174
- # Not used; single-selection business click is handled in mouseReleaseEvent
175
- pass
176
-
177
- # ----------------------------
178
- # Context menu
179
- # ----------------------------
180
-
181
- def contextMenuEvent(self, event):
182
- """
183
- Context menu event
184
-
185
- :param event: context menu event
186
- """
187
- actions = {}
188
- actions['refresh'] = QAction(
189
- QIcon(":/icons/reload.svg"),
190
- trans('dialog.remote_store.menu.current.refresh_store'),
191
- self
192
- )
193
- actions['delete'] = QAction(QIcon(":/icons/delete.svg"), trans('action.delete'), self)
194
- actions['clear'] = QAction(
195
- QIcon(":/icons/close.svg"),
196
- trans('dialog.remote_store.menu.current.clear_files'),
197
- self
198
- )
199
- actions['truncate'] = QAction(
200
- QIcon(":/icons/delete.svg"),
201
- trans('dialog.remote_store.menu.current.truncate_files'),
202
- self
203
- )
204
-
205
- menu = QMenu(self)
206
- menu.addAction(actions['refresh'])
207
- menu.addAction(actions['delete'])
208
- menu.addAction(actions['clear'])
209
- menu.addAction(actions['truncate'])
210
-
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
217
-
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):
258
- """
259
- Delete action handler
260
-
261
- :param item: int row or list of rows
262
- """
263
- if isinstance(item, (list, tuple)):
264
- if item:
265
- self.restore_after_ctx_menu = False
266
- self.window.controller.remote_store.openai.delete_by_idx(list(item))
267
- return
268
- idx = int(item)
269
- if idx >= 0:
270
- self.restore_after_ctx_menu = False
271
- self.window.controller.remote_store.openai.delete_by_idx(idx)
272
-
273
- def action_clear(self, item):
274
- """
275
- Clear action handler
276
-
277
- :param item: int row or list of rows
278
- """
279
- if isinstance(item, (list, tuple)):
280
- if item:
281
- self.restore_after_ctx_menu = False
282
- self.window.controller.remote_store.openai.batch.clear_store_files_by_idx(list(item))
283
- return
284
- idx = int(item)
285
- if idx >= 0:
286
- self.restore_after_ctx_menu = False
287
- self.window.controller.remote_store.openai.batch.clear_store_files_by_idx(idx)
288
-
289
- def action_truncate(self, item):
290
- """
291
- Truncate action handler
292
-
293
- :param item: int row or list of rows
294
- """
295
- if isinstance(item, (list, tuple)):
296
- if item:
297
- self.restore_after_ctx_menu = False
298
- self.window.controller.remote_store.openai.batch.truncate_store_files_by_idx(list(item))
299
- return
300
- idx = int(item)
301
- if idx >= 0:
302
- self.restore_after_ctx_menu = False
303
- self.window.controller.remote_store.openai.batch.truncate_store_files_by_idx(idx)
304
-
305
- def action_refresh(self, item):
306
- """
307
- Refresh action handler
308
- """
309
- if isinstance(item, (list, tuple)):
310
- if item:
311
- self.restore_after_ctx_menu = False
312
- self.window.controller.remote_store.openai.refresh_by_idx(list(item))
313
- return
314
- idx = int(item)
315
- if idx >= 0:
316
- self.restore_after_ctx_menu = False
317
- self.window.controller.remote_store.openai.refresh_by_idx(idx)