pygpt-net 2.7.4__py3-none-any.whl → 2.7.6__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 +15 -0
- pygpt_net/__init__.py +4 -4
- pygpt_net/app_core.py +4 -2
- pygpt_net/controller/__init__.py +5 -1
- pygpt_net/controller/assistant/assistant.py +1 -4
- pygpt_net/controller/assistant/batch.py +5 -504
- pygpt_net/controller/assistant/editor.py +5 -5
- pygpt_net/controller/assistant/files.py +16 -16
- pygpt_net/controller/chat/handler/google_stream.py +307 -1
- pygpt_net/controller/chat/handler/worker.py +10 -25
- pygpt_net/controller/chat/handler/xai_stream.py +621 -52
- pygpt_net/controller/chat/image.py +2 -2
- pygpt_net/controller/debug/fixtures.py +3 -2
- pygpt_net/controller/dialogs/confirm.py +73 -101
- pygpt_net/controller/files/files.py +65 -4
- pygpt_net/controller/lang/mapping.py +9 -9
- pygpt_net/controller/painter/capture.py +50 -1
- pygpt_net/controller/presets/presets.py +2 -1
- pygpt_net/controller/remote_store/__init__.py +12 -0
- pygpt_net/{provider/core/assistant_file/db_sqlite → controller/remote_store/google}/__init__.py +2 -2
- pygpt_net/controller/remote_store/google/batch.py +402 -0
- pygpt_net/controller/remote_store/google/store.py +615 -0
- pygpt_net/controller/remote_store/openai/__init__.py +12 -0
- pygpt_net/controller/remote_store/openai/batch.py +524 -0
- pygpt_net/controller/{assistant → remote_store/openai}/store.py +63 -60
- pygpt_net/controller/remote_store/remote_store.py +35 -0
- pygpt_net/controller/ui/ui.py +20 -1
- pygpt_net/core/assistants/assistants.py +3 -15
- pygpt_net/core/db/database.py +5 -3
- pygpt_net/core/filesystem/url.py +4 -1
- pygpt_net/core/locale/placeholder.py +35 -0
- pygpt_net/core/remote_store/__init__.py +12 -0
- pygpt_net/core/remote_store/google/__init__.py +11 -0
- pygpt_net/core/remote_store/google/files.py +224 -0
- pygpt_net/core/remote_store/google/store.py +248 -0
- pygpt_net/core/remote_store/openai/__init__.py +11 -0
- pygpt_net/core/{assistants → remote_store/openai}/files.py +26 -19
- pygpt_net/core/{assistants → remote_store/openai}/store.py +32 -15
- pygpt_net/core/remote_store/remote_store.py +24 -0
- pygpt_net/core/render/web/body.py +3 -2
- pygpt_net/core/types/chunk.py +27 -0
- pygpt_net/data/config/config.json +8 -4
- pygpt_net/data/config/models.json +77 -3
- pygpt_net/data/config/settings.json +45 -0
- pygpt_net/data/js/app/template.js +1 -1
- pygpt_net/data/js/app.min.js +2 -2
- pygpt_net/data/locale/locale.de.ini +44 -41
- pygpt_net/data/locale/locale.en.ini +56 -43
- pygpt_net/data/locale/locale.es.ini +44 -41
- pygpt_net/data/locale/locale.fr.ini +44 -41
- pygpt_net/data/locale/locale.it.ini +44 -41
- pygpt_net/data/locale/locale.pl.ini +45 -42
- pygpt_net/data/locale/locale.uk.ini +44 -41
- pygpt_net/data/locale/locale.zh.ini +44 -41
- pygpt_net/data/locale/plugin.cmd_history.de.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.en.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.es.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.fr.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.it.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.pl.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.uk.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.zh.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_mouse_control.en.ini +14 -0
- pygpt_net/data/locale/plugin.cmd_web.de.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.en.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.es.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.fr.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.it.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.pl.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.uk.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.zh.ini +1 -1
- pygpt_net/data/locale/plugin.idx_llama_index.de.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.en.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.es.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.fr.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.it.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.pl.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.uk.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.zh.ini +2 -2
- pygpt_net/item/assistant.py +1 -211
- pygpt_net/item/ctx.py +3 -3
- pygpt_net/item/store.py +238 -0
- pygpt_net/js_rc.py +2449 -2447
- pygpt_net/migrations/Version20260102190000.py +35 -0
- pygpt_net/migrations/__init__.py +3 -1
- pygpt_net/plugin/cmd_mouse_control/config.py +471 -1
- pygpt_net/plugin/cmd_mouse_control/plugin.py +487 -22
- pygpt_net/plugin/cmd_mouse_control/worker.py +464 -87
- pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +729 -0
- pygpt_net/plugin/idx_llama_index/config.py +2 -2
- pygpt_net/provider/api/anthropic/__init__.py +10 -8
- pygpt_net/provider/api/google/__init__.py +21 -58
- pygpt_net/provider/api/google/chat.py +545 -129
- pygpt_net/provider/api/google/computer.py +190 -0
- pygpt_net/provider/api/google/realtime/realtime.py +2 -2
- pygpt_net/provider/api/google/remote_tools.py +93 -0
- pygpt_net/provider/api/google/store.py +546 -0
- pygpt_net/provider/api/google/worker/__init__.py +0 -0
- pygpt_net/provider/api/google/worker/importer.py +392 -0
- pygpt_net/provider/api/openai/__init__.py +7 -3
- pygpt_net/provider/api/openai/computer.py +10 -1
- pygpt_net/provider/api/openai/responses.py +0 -0
- pygpt_net/provider/api/openai/store.py +6 -6
- pygpt_net/provider/api/openai/worker/importer.py +24 -24
- pygpt_net/provider/api/x_ai/__init__.py +10 -9
- pygpt_net/provider/api/x_ai/chat.py +272 -102
- pygpt_net/provider/core/config/patch.py +16 -1
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +3 -3
- pygpt_net/provider/core/model/patch.py +17 -3
- pygpt_net/provider/core/preset/json_file.py +13 -7
- pygpt_net/provider/core/{assistant_file → remote_file}/__init__.py +1 -1
- pygpt_net/provider/core/{assistant_file → remote_file}/base.py +9 -9
- pygpt_net/provider/core/remote_file/db_sqlite/__init__.py +12 -0
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/patch.py +1 -1
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/provider.py +23 -20
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/storage.py +35 -27
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/utils.py +5 -4
- pygpt_net/provider/core/{assistant_store → remote_store}/__init__.py +1 -1
- pygpt_net/provider/core/{assistant_store → remote_store}/base.py +10 -10
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/__init__.py +1 -1
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/patch.py +1 -1
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/provider.py +16 -15
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/storage.py +30 -23
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/utils.py +5 -4
- pygpt_net/provider/core/{assistant_store → remote_store}/json_file.py +9 -9
- pygpt_net/provider/llms/google.py +2 -2
- pygpt_net/tools/image_viewer/ui/dialogs.py +298 -12
- pygpt_net/tools/text_editor/ui/widgets.py +5 -1
- pygpt_net/ui/base/config_dialog.py +3 -2
- pygpt_net/ui/base/context_menu.py +44 -1
- pygpt_net/ui/dialog/assistant.py +3 -3
- pygpt_net/ui/dialog/plugins.py +3 -1
- pygpt_net/ui/dialog/remote_store_google.py +539 -0
- pygpt_net/ui/dialog/{assistant_store.py → remote_store_openai.py} +95 -95
- pygpt_net/ui/dialogs.py +5 -3
- pygpt_net/ui/layout/chat/attachments_uploaded.py +3 -3
- pygpt_net/ui/layout/toolbox/computer_env.py +26 -8
- pygpt_net/ui/layout/toolbox/indexes.py +22 -19
- pygpt_net/ui/layout/toolbox/model.py +28 -5
- pygpt_net/ui/menu/tools.py +13 -5
- pygpt_net/ui/widget/dialog/remote_store_google.py +56 -0
- pygpt_net/ui/widget/dialog/{assistant_store.py → remote_store_openai.py} +9 -9
- pygpt_net/ui/widget/element/button.py +4 -4
- pygpt_net/ui/widget/image/display.py +25 -8
- pygpt_net/ui/widget/lists/remote_store_google.py +248 -0
- pygpt_net/ui/widget/lists/{assistant_store.py → remote_store_openai.py} +21 -21
- pygpt_net/ui/widget/option/checkbox_list.py +47 -9
- pygpt_net/ui/widget/option/combo.py +39 -3
- pygpt_net/ui/widget/tabs/output.py +9 -1
- pygpt_net/ui/widget/textarea/editor.py +14 -1
- pygpt_net/ui/widget/textarea/input.py +20 -7
- pygpt_net/ui/widget/textarea/notepad.py +24 -1
- pygpt_net/ui/widget/textarea/output.py +23 -1
- pygpt_net/ui/widget/textarea/web.py +16 -1
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/METADATA +41 -2
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/RECORD +158 -132
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/entry_points.txt +0 -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:
|
|
9
|
+
# Updated Date: 2026.01.02 19:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtGui import QAction, QIcon
|
|
@@ -18,7 +18,7 @@ from pygpt_net.utils import trans
|
|
|
18
18
|
import pygpt_net.icons_rc
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class
|
|
21
|
+
class RemoteStoreOpenAIEditorList(BaseList):
|
|
22
22
|
def __init__(self, window=None, id=None):
|
|
23
23
|
"""
|
|
24
24
|
Store select menu (in editor)
|
|
@@ -26,7 +26,7 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
26
26
|
:param window: main window
|
|
27
27
|
:param id: parent id
|
|
28
28
|
"""
|
|
29
|
-
super(
|
|
29
|
+
super(RemoteStoreOpenAIEditorList, self).__init__(window)
|
|
30
30
|
self.window = window
|
|
31
31
|
self.id = id
|
|
32
32
|
|
|
@@ -94,7 +94,7 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
94
94
|
self._suppress_item_click = True
|
|
95
95
|
self._was_shift_click = True
|
|
96
96
|
if idx.isValid():
|
|
97
|
-
super(
|
|
97
|
+
super(RemoteStoreOpenAIEditorList, self).mousePressEvent(event)
|
|
98
98
|
else:
|
|
99
99
|
event.accept()
|
|
100
100
|
return
|
|
@@ -112,7 +112,7 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
112
112
|
return
|
|
113
113
|
# continue with default single selection for clicked row
|
|
114
114
|
|
|
115
|
-
super(
|
|
115
|
+
super(RemoteStoreOpenAIEditorList, self).mousePressEvent(event)
|
|
116
116
|
return
|
|
117
117
|
|
|
118
118
|
# Right click: prepare selection for context menu
|
|
@@ -134,14 +134,14 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
134
134
|
event.accept()
|
|
135
135
|
return
|
|
136
136
|
|
|
137
|
-
super(
|
|
137
|
+
super(RemoteStoreOpenAIEditorList, self).mousePressEvent(event)
|
|
138
138
|
|
|
139
139
|
def mouseReleaseEvent(self, event):
|
|
140
140
|
# If the click was a Shift-based range selection, bypass business click
|
|
141
141
|
if event.button() == Qt.LeftButton and self._was_shift_click:
|
|
142
142
|
self._was_shift_click = False
|
|
143
143
|
self._suppress_item_click = False
|
|
144
|
-
super(
|
|
144
|
+
super(RemoteStoreOpenAIEditorList, self).mouseReleaseEvent(event)
|
|
145
145
|
return
|
|
146
146
|
|
|
147
147
|
# Finish "virtual" Ctrl toggle on same row (no business click)
|
|
@@ -163,12 +163,12 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
163
163
|
idx = self.indexAt(event.pos())
|
|
164
164
|
if not self._has_multi_selection():
|
|
165
165
|
if idx.isValid() and not self._suppress_item_click:
|
|
166
|
-
self.window.controller.
|
|
166
|
+
self.window.controller.remote_store.openai.select(idx.row())
|
|
167
167
|
self._suppress_item_click = False
|
|
168
|
-
super(
|
|
168
|
+
super(RemoteStoreOpenAIEditorList, self).mouseReleaseEvent(event)
|
|
169
169
|
return
|
|
170
170
|
|
|
171
|
-
super(
|
|
171
|
+
super(RemoteStoreOpenAIEditorList, self).mouseReleaseEvent(event)
|
|
172
172
|
|
|
173
173
|
def click(self, val):
|
|
174
174
|
# Not used; single-selection business click is handled in mouseReleaseEvent
|
|
@@ -187,18 +187,18 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
187
187
|
actions = {}
|
|
188
188
|
actions['refresh'] = QAction(
|
|
189
189
|
QIcon(":/icons/reload.svg"),
|
|
190
|
-
trans('dialog.
|
|
190
|
+
trans('dialog.remote_store.menu.current.refresh_store'),
|
|
191
191
|
self
|
|
192
192
|
)
|
|
193
193
|
actions['delete'] = QAction(QIcon(":/icons/delete.svg"), trans('action.delete'), self)
|
|
194
194
|
actions['clear'] = QAction(
|
|
195
195
|
QIcon(":/icons/close.svg"),
|
|
196
|
-
trans('dialog.
|
|
196
|
+
trans('dialog.remote_store.menu.current.clear_files'),
|
|
197
197
|
self
|
|
198
198
|
)
|
|
199
199
|
actions['truncate'] = QAction(
|
|
200
200
|
QIcon(":/icons/delete.svg"),
|
|
201
|
-
trans('dialog.
|
|
201
|
+
trans('dialog.remote_store.menu.current.truncate_files'),
|
|
202
202
|
self
|
|
203
203
|
)
|
|
204
204
|
|
|
@@ -263,12 +263,12 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
263
263
|
if isinstance(item, (list, tuple)):
|
|
264
264
|
if item:
|
|
265
265
|
self.restore_after_ctx_menu = False
|
|
266
|
-
self.window.controller.
|
|
266
|
+
self.window.controller.remote_store.openai.delete_by_idx(list(item))
|
|
267
267
|
return
|
|
268
268
|
idx = int(item)
|
|
269
269
|
if idx >= 0:
|
|
270
270
|
self.restore_after_ctx_menu = False
|
|
271
|
-
self.window.controller.
|
|
271
|
+
self.window.controller.remote_store.openai.delete_by_idx(idx)
|
|
272
272
|
|
|
273
273
|
def action_clear(self, item):
|
|
274
274
|
"""
|
|
@@ -279,12 +279,12 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
279
279
|
if isinstance(item, (list, tuple)):
|
|
280
280
|
if item:
|
|
281
281
|
self.restore_after_ctx_menu = False
|
|
282
|
-
self.window.controller.
|
|
282
|
+
self.window.controller.remote_store.openai.batch.clear_store_files_by_idx(list(item))
|
|
283
283
|
return
|
|
284
284
|
idx = int(item)
|
|
285
285
|
if idx >= 0:
|
|
286
286
|
self.restore_after_ctx_menu = False
|
|
287
|
-
self.window.controller.
|
|
287
|
+
self.window.controller.remote_store.openai.batch.clear_store_files_by_idx(idx)
|
|
288
288
|
|
|
289
289
|
def action_truncate(self, item):
|
|
290
290
|
"""
|
|
@@ -295,12 +295,12 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
295
295
|
if isinstance(item, (list, tuple)):
|
|
296
296
|
if item:
|
|
297
297
|
self.restore_after_ctx_menu = False
|
|
298
|
-
self.window.controller.
|
|
298
|
+
self.window.controller.remote_store.openai.batch.truncate_store_files_by_idx(list(item))
|
|
299
299
|
return
|
|
300
300
|
idx = int(item)
|
|
301
301
|
if idx >= 0:
|
|
302
302
|
self.restore_after_ctx_menu = False
|
|
303
|
-
self.window.controller.
|
|
303
|
+
self.window.controller.remote_store.openai.batch.truncate_store_files_by_idx(idx)
|
|
304
304
|
|
|
305
305
|
def action_refresh(self, item):
|
|
306
306
|
"""
|
|
@@ -309,9 +309,9 @@ class AssistantVectorStoreEditorList(BaseList):
|
|
|
309
309
|
if isinstance(item, (list, tuple)):
|
|
310
310
|
if item:
|
|
311
311
|
self.restore_after_ctx_menu = False
|
|
312
|
-
self.window.controller.
|
|
312
|
+
self.window.controller.remote_store.openai.refresh_by_idx(list(item))
|
|
313
313
|
return
|
|
314
314
|
idx = int(item)
|
|
315
315
|
if idx >= 0:
|
|
316
316
|
self.restore_after_ctx_menu = False
|
|
317
|
-
self.window.controller.
|
|
317
|
+
self.window.controller.remote_store.openai.refresh_by_idx(idx)
|
|
@@ -6,10 +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: 2026.01.01 15:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtWidgets import QCheckBox, QWidget, QPushButton
|
|
13
|
+
from PySide6.QtGui import QIcon
|
|
14
|
+
from PySide6.QtCore import QSize, Qt
|
|
13
15
|
|
|
14
16
|
from pygpt_net.ui.base.flow_layout import FlowLayout
|
|
15
17
|
from pygpt_net.utils import trans
|
|
@@ -43,6 +45,10 @@ class OptionCheckboxList(QWidget):
|
|
|
43
45
|
self.keys = {}
|
|
44
46
|
self.boxes = {}
|
|
45
47
|
|
|
48
|
+
# overlay button config
|
|
49
|
+
self._overlay_margin = 4 # px
|
|
50
|
+
self.btn_select = None
|
|
51
|
+
|
|
46
52
|
# init from option data
|
|
47
53
|
if self.option is not None:
|
|
48
54
|
if "label" in self.option and self.option["label"] is not None \
|
|
@@ -84,20 +90,49 @@ class OptionCheckboxList(QWidget):
|
|
|
84
90
|
for widget in widgets:
|
|
85
91
|
self.layout.addWidget(widget)
|
|
86
92
|
|
|
87
|
-
# select/unselect all button
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
# do not add the select/unselect all button to the flow layout
|
|
94
|
+
# it will be overlaid in the top-right corner to avoid affecting layout flow
|
|
95
|
+
self.layout.setContentsMargins(0, 0, 0, 0)
|
|
96
|
+
self.setLayout(self.layout)
|
|
97
|
+
|
|
98
|
+
# overlay select/unselect all button pinned to top-right corner
|
|
99
|
+
self.btn_select = QPushButton(self)
|
|
100
|
+
self.btn_select.setObjectName("btn_select_all_overlay")
|
|
101
|
+
self.btn_select.setToolTip(trans("action.select_unselect_all"))
|
|
102
|
+
self.btn_select.setIcon(QIcon(":/icons/asterisk.svg"))
|
|
103
|
+
self.btn_select.setIconSize(QSize(16, 16))
|
|
104
|
+
self.btn_select.setFixedSize(22, 22)
|
|
105
|
+
self.btn_select.setFocusPolicy(Qt.NoFocus)
|
|
106
|
+
self.btn_select.setCursor(Qt.PointingHandCursor)
|
|
107
|
+
self.btn_select.setStyleSheet("QPushButton { border: none; padding: 0; background: transparent; }")
|
|
108
|
+
self.btn_select.clicked.connect(
|
|
91
109
|
lambda: self.window.controller.config.checkbox_list.on_select_all(
|
|
92
110
|
self.parent_id,
|
|
93
111
|
self.id,
|
|
94
112
|
self.option
|
|
95
113
|
)
|
|
96
114
|
)
|
|
97
|
-
self.
|
|
115
|
+
self.btn_select.raise_()
|
|
116
|
+
self._place_select_button()
|
|
98
117
|
|
|
99
|
-
|
|
100
|
-
|
|
118
|
+
def _place_select_button(self) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Place the overlay select/unselect button in the top-right corner.
|
|
121
|
+
"""
|
|
122
|
+
if not self.btn_select:
|
|
123
|
+
return
|
|
124
|
+
m = self._overlay_margin
|
|
125
|
+
w = self.btn_select.width()
|
|
126
|
+
x = max(0, self.width() - w - m)
|
|
127
|
+
y = m
|
|
128
|
+
self.btn_select.move(x, y)
|
|
129
|
+
|
|
130
|
+
def resizeEvent(self, event):
|
|
131
|
+
"""
|
|
132
|
+
Keep the overlay button pinned to the top-right on resize.
|
|
133
|
+
"""
|
|
134
|
+
super().resizeEvent(event)
|
|
135
|
+
self._place_select_button()
|
|
101
136
|
|
|
102
137
|
def update_boxes_list(self, keys: dict) -> None:
|
|
103
138
|
"""
|
|
@@ -141,6 +176,9 @@ class OptionCheckboxList(QWidget):
|
|
|
141
176
|
self.boxes[key] = checkbox
|
|
142
177
|
self.layout.addWidget(checkbox)
|
|
143
178
|
|
|
179
|
+
# keep the overlay button in place after list update
|
|
180
|
+
self._place_select_button()
|
|
181
|
+
|
|
144
182
|
def setIcon(self, icon: str):
|
|
145
183
|
"""
|
|
146
184
|
Set icon
|
|
@@ -185,4 +223,4 @@ class OptionCheckboxList(QWidget):
|
|
|
185
223
|
"""
|
|
186
224
|
if key in self.boxes:
|
|
187
225
|
return self.boxes[key].isChecked()
|
|
188
|
-
return False
|
|
226
|
+
return False
|
|
@@ -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: 2026.01.01 15:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
import sys
|
|
12
12
|
|
|
@@ -742,6 +742,11 @@ class SearchableCombo(SeparatorComboBox):
|
|
|
742
742
|
self.hidePopup()
|
|
743
743
|
event.accept()
|
|
744
744
|
return
|
|
745
|
+
if event.key() == Qt.Key_Right:
|
|
746
|
+
# Inject highlighted item's text into the search header
|
|
747
|
+
self._apply_highlighted_to_search_input()
|
|
748
|
+
event.accept()
|
|
749
|
+
return
|
|
745
750
|
super().keyPressEvent(event)
|
|
746
751
|
|
|
747
752
|
# ----- Event filter -----
|
|
@@ -821,6 +826,10 @@ class SearchableCombo(SeparatorComboBox):
|
|
|
821
826
|
# Explicitly close popup from header
|
|
822
827
|
self.hidePopup()
|
|
823
828
|
return True
|
|
829
|
+
if key == Qt.Key_Right:
|
|
830
|
+
# Insert highlighted item's value into the search input
|
|
831
|
+
self._apply_highlighted_to_search_input()
|
|
832
|
+
return True
|
|
824
833
|
if key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Home, Qt.Key_End):
|
|
825
834
|
self._handle_navigation_key(key)
|
|
826
835
|
return True
|
|
@@ -832,13 +841,18 @@ class SearchableCombo(SeparatorComboBox):
|
|
|
832
841
|
view = self.view()
|
|
833
842
|
if view is not None and (obj is view or obj is getattr(view, "viewport", lambda: None)()):
|
|
834
843
|
if event.type() == QEvent.KeyPress:
|
|
835
|
-
|
|
844
|
+
key = event.key()
|
|
845
|
+
if key in (Qt.Key_Return, Qt.Key_Enter):
|
|
836
846
|
self._commit_view_current()
|
|
837
847
|
return True
|
|
838
|
-
if
|
|
848
|
+
if key == Qt.Key_Escape:
|
|
839
849
|
# Close from view/viewport ESC and keep control flow consistent
|
|
840
850
|
self.hidePopup()
|
|
841
851
|
return True
|
|
852
|
+
if key == Qt.Key_Right:
|
|
853
|
+
# Insert highlighted item's value into the search input
|
|
854
|
+
self._apply_highlighted_to_search_input()
|
|
855
|
+
return True
|
|
842
856
|
return False
|
|
843
857
|
|
|
844
858
|
return super().eventFilter(obj, event)
|
|
@@ -1406,6 +1420,28 @@ class SearchableCombo(SeparatorComboBox):
|
|
|
1406
1420
|
except Exception:
|
|
1407
1421
|
pass
|
|
1408
1422
|
|
|
1423
|
+
# ----- Convenience: inject highlighted row text into the search header -----
|
|
1424
|
+
|
|
1425
|
+
def _apply_highlighted_to_search_input(self):
|
|
1426
|
+
"""
|
|
1427
|
+
Put the currently highlighted row's display text into the popup search input.
|
|
1428
|
+
Does nothing if the popup or header is not available. The normal textChanged
|
|
1429
|
+
handler will update scrolling and internal state.
|
|
1430
|
+
"""
|
|
1431
|
+
if not self._popup_open or self._popup_header is None:
|
|
1432
|
+
return
|
|
1433
|
+
view = self.view()
|
|
1434
|
+
if view is None:
|
|
1435
|
+
return
|
|
1436
|
+
idx = view.currentIndex()
|
|
1437
|
+
row = self._sanitize_index(idx.row() if idx.isValid() else -1)
|
|
1438
|
+
if row == -1:
|
|
1439
|
+
return
|
|
1440
|
+
text = self.itemText(row) or ""
|
|
1441
|
+
self._popup_header.setText(text)
|
|
1442
|
+
self._ensure_clear_button_visible(self._popup_header)
|
|
1443
|
+
self._popup_header.setCursorPosition(len(text))
|
|
1444
|
+
|
|
1409
1445
|
|
|
1410
1446
|
class NoScrollCombo(SearchableCombo):
|
|
1411
1447
|
"""A combo box that disables mouse wheel scrolling, extended with optional search support."""
|
|
@@ -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: 2026.01.03 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtWidgets import QTabWidget, QMenu, QPushButton, QToolButton, QTabBar
|
|
@@ -502,6 +502,14 @@ class OutputTabs(QTabWidget):
|
|
|
502
502
|
self.show_tool_menu(idx, column_idx, event.globalPos()) # tool
|
|
503
503
|
else:
|
|
504
504
|
self.show_default_menu(idx, column_idx, event.globalPos()) # default
|
|
505
|
+
|
|
506
|
+
# close on middle click
|
|
507
|
+
elif event.button() == Qt.MiddleButton:
|
|
508
|
+
idx = self.tabBar().tabAt(event.pos())
|
|
509
|
+
column_idx = self.column.get_idx()
|
|
510
|
+
self.window.controller.ui.tabs.close(idx, column_idx)
|
|
511
|
+
event.accept()
|
|
512
|
+
return
|
|
505
513
|
super(OutputTabs, self).mousePressEvent(event)
|
|
506
514
|
|
|
507
515
|
def prepare_menu(self, index: int, column_idx: int) -> QMenu:
|
|
@@ -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: 2026.01.03 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt
|
|
@@ -88,6 +88,10 @@ class BaseCodeEditor(QTextEdit):
|
|
|
88
88
|
lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
|
|
89
89
|
menu.addAction(action)
|
|
90
90
|
|
|
91
|
+
# Add zoom submenu
|
|
92
|
+
zoom_menu = self.window.ui.context_menu.get_zoom_menu(self, "editor", self.value, self.on_zoom_changed)
|
|
93
|
+
menu.addMenu(zoom_menu)
|
|
94
|
+
|
|
91
95
|
action = QAction(self._ICON_SEARCH, trans('text.context_menu.find'), menu)
|
|
92
96
|
action.triggered.connect(self.find_open)
|
|
93
97
|
action.setShortcut(self._FIND_SEQ)
|
|
@@ -137,6 +141,15 @@ class BaseCodeEditor(QTextEdit):
|
|
|
137
141
|
else:
|
|
138
142
|
super().wheelEvent(event)
|
|
139
143
|
|
|
144
|
+
def on_zoom_changed(self, value: int):
|
|
145
|
+
"""
|
|
146
|
+
On font size changed
|
|
147
|
+
|
|
148
|
+
:param value: New font size
|
|
149
|
+
"""
|
|
150
|
+
self.value = value
|
|
151
|
+
self.update_stylesheet(f"QTextEdit {{ font-size: {value}px }};")
|
|
152
|
+
|
|
140
153
|
def focusInEvent(self, e):
|
|
141
154
|
super().focusInEvent(e)
|
|
142
155
|
self.window.controller.finder.focus_in(self.finder)
|
|
@@ -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: 2026.01.03 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Optional
|
|
@@ -394,6 +394,10 @@ class ChatInput(QTextEdit):
|
|
|
394
394
|
action.triggered.connect(lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
|
|
395
395
|
menu.addAction(action)
|
|
396
396
|
|
|
397
|
+
# Add zoom submenu
|
|
398
|
+
zoom_menu = self.window.ui.context_menu.get_zoom_menu(self, "font_size.input", self.value, self.on_zoom_changed)
|
|
399
|
+
menu.addMenu(zoom_menu)
|
|
400
|
+
|
|
397
401
|
try:
|
|
398
402
|
self.window.core.prompt.template.to_menu_options(menu, "input")
|
|
399
403
|
self.window.core.prompt.custom.to_menu_options(menu, "input")
|
|
@@ -458,8 +462,8 @@ class ChatInput(QTextEdit):
|
|
|
458
462
|
:param event: Event
|
|
459
463
|
"""
|
|
460
464
|
if event.modifiers() & Qt.ControlModifier:
|
|
461
|
-
prev = self.value
|
|
462
465
|
dy = event.angleDelta().y()
|
|
466
|
+
prev = self.value
|
|
463
467
|
if dy > 0:
|
|
464
468
|
if self.value < self.max_font_size:
|
|
465
469
|
self.value += 1
|
|
@@ -468,15 +472,24 @@ class ChatInput(QTextEdit):
|
|
|
468
472
|
self.value -= 1
|
|
469
473
|
|
|
470
474
|
if self.value != prev:
|
|
471
|
-
self.
|
|
472
|
-
self.window.core.config.save()
|
|
473
|
-
self.window.controller.ui.update_font_size()
|
|
474
|
-
# Reflow may change number of lines; adjust auto-height next tick
|
|
475
|
-
QTimer.singleShot(0, self._schedule_auto_resize)
|
|
475
|
+
self.on_zoom_changed(self.value)
|
|
476
476
|
event.accept()
|
|
477
477
|
return
|
|
478
478
|
super().wheelEvent(event)
|
|
479
479
|
|
|
480
|
+
def on_zoom_changed(self, value: int):
|
|
481
|
+
"""
|
|
482
|
+
Called when zoom level changes.
|
|
483
|
+
|
|
484
|
+
:param value: new zoom level
|
|
485
|
+
"""
|
|
486
|
+
self.value = value
|
|
487
|
+
self.window.core.config.data['font_size.input'] = value
|
|
488
|
+
self.window.core.config.save()
|
|
489
|
+
self.window.controller.ui.update_font_size()
|
|
490
|
+
# Reflow may change number of lines; adjust auto-height next tick
|
|
491
|
+
QTimer.singleShot(0, self._schedule_auto_resize)
|
|
492
|
+
|
|
480
493
|
def changeEvent(self, event):
|
|
481
494
|
super().changeEvent(event)
|
|
482
495
|
if event.type() == QEvent.FontChange:
|
|
@@ -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: 2026.01.03 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt, QEvent, QTimer
|
|
@@ -214,6 +214,10 @@ class NotepadOutput(QTextEdit):
|
|
|
214
214
|
lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
|
|
215
215
|
menu.addAction(action)
|
|
216
216
|
|
|
217
|
+
# Add zoom submenu
|
|
218
|
+
zoom_menu = self.window.ui.context_menu.get_zoom_menu(self, "font_size", self.value, self.on_zoom_changed)
|
|
219
|
+
menu.addMenu(zoom_menu)
|
|
220
|
+
|
|
217
221
|
action = QAction(self.ICON_SEARCH, trans('text.context_menu.find'), self)
|
|
218
222
|
action.triggered.connect(self.find_open)
|
|
219
223
|
action.setShortcut(QKeySequence("Ctrl+F"))
|
|
@@ -282,6 +286,25 @@ class NotepadOutput(QTextEdit):
|
|
|
282
286
|
super(NotepadOutput, self).wheelEvent(event)
|
|
283
287
|
self.last_scroll_pos = self._vscroll.value()
|
|
284
288
|
|
|
289
|
+
def on_zoom_changed(self, value: int):
|
|
290
|
+
"""
|
|
291
|
+
On font size changed
|
|
292
|
+
|
|
293
|
+
:param value: New font size
|
|
294
|
+
"""
|
|
295
|
+
self.value = value
|
|
296
|
+
self.window.core.config.data['font_size'] = value
|
|
297
|
+
self.window.core.config.save()
|
|
298
|
+
option = self.window.controller.settings.editor.get_option('font_size')
|
|
299
|
+
option['value'] = self.value
|
|
300
|
+
self.window.controller.config.apply(
|
|
301
|
+
parent_id='config',
|
|
302
|
+
key='font_size',
|
|
303
|
+
option=option,
|
|
304
|
+
)
|
|
305
|
+
self.window.controller.ui.update_font_size()
|
|
306
|
+
self.last_scroll_pos = self._vscroll.value()
|
|
307
|
+
|
|
285
308
|
def focusInEvent(self, e):
|
|
286
309
|
"""
|
|
287
310
|
Focus in event
|
|
@@ -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: 2026.01.03 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt, QEvent
|
|
@@ -118,6 +118,10 @@ class ChatOutput(QTextBrowser):
|
|
|
118
118
|
action.triggered.connect(self._save_all_text)
|
|
119
119
|
menu.addAction(action)
|
|
120
120
|
|
|
121
|
+
# Add zoom submenu
|
|
122
|
+
zoom_menu = self.window.ui.context_menu.get_zoom_menu(self, "font_size", self.value, self.on_zoom_changed)
|
|
123
|
+
menu.addMenu(zoom_menu)
|
|
124
|
+
|
|
121
125
|
action = QAction(self.ICON_SEARCH, trans('text.context_menu.find'), self)
|
|
122
126
|
action.triggered.connect(self.find_open)
|
|
123
127
|
action.setShortcut(QKeySequence("Ctrl+F"))
|
|
@@ -193,6 +197,24 @@ class ChatOutput(QTextBrowser):
|
|
|
193
197
|
else:
|
|
194
198
|
super().wheelEvent(event)
|
|
195
199
|
|
|
200
|
+
def on_zoom_changed(self, value: int):
|
|
201
|
+
"""
|
|
202
|
+
On font size changed
|
|
203
|
+
|
|
204
|
+
:param value: New font size
|
|
205
|
+
"""
|
|
206
|
+
self.value = value
|
|
207
|
+
|
|
208
|
+
cfg = self.window.core.config
|
|
209
|
+
cfg.data['font_size'] = value
|
|
210
|
+
cfg.save()
|
|
211
|
+
|
|
212
|
+
ctrl = self.window.controller
|
|
213
|
+
option = ctrl.settings.editor.get_option('font_size')
|
|
214
|
+
option['value'] = value
|
|
215
|
+
ctrl.config.apply(parent_id='config', key='font_size', option=option)
|
|
216
|
+
ctrl.ui.update_font_size()
|
|
217
|
+
|
|
196
218
|
def focusInEvent(self, e):
|
|
197
219
|
"""
|
|
198
220
|
Focus in event
|
|
@@ -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: 2026.01.03 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
from PySide6 import QtCore
|
|
12
12
|
from PySide6.QtCore import Qt, QObject, Signal, Slot, QEvent, QUrl, QCoreApplication, QEventLoop
|
|
@@ -345,12 +345,27 @@ class ChatWebOutput(QWebEngineView):
|
|
|
345
345
|
action.triggered.connect(self._save_as_html)
|
|
346
346
|
menu.addAction(action)
|
|
347
347
|
|
|
348
|
+
# Add zoom submenu
|
|
349
|
+
zoom_menu = self.window.ui.context_menu.get_zoom_menu(self, "zoom", self.get_zoom_value(), self.on_zoom_changed)
|
|
350
|
+
menu.addMenu(zoom_menu)
|
|
351
|
+
|
|
348
352
|
action = QAction(QIcon(":/icons/search.svg"), trans('text.context_menu.find'), self)
|
|
349
353
|
action.triggered.connect(self.find_open)
|
|
350
354
|
menu.addAction(action)
|
|
351
355
|
|
|
352
356
|
menu.exec_(self.mapToGlobal(position))
|
|
353
357
|
|
|
358
|
+
def on_zoom_changed(self, zoom: float):
|
|
359
|
+
"""
|
|
360
|
+
On zoom changed from context menu
|
|
361
|
+
|
|
362
|
+
:param zoom: float - new zoom factor
|
|
363
|
+
"""
|
|
364
|
+
p = self.page()
|
|
365
|
+
if p:
|
|
366
|
+
self.window.core.config.set("zoom", zoom)
|
|
367
|
+
self.update_zoom()
|
|
368
|
+
|
|
354
369
|
@Slot()
|
|
355
370
|
def _save_selected_txt(self):
|
|
356
371
|
"""Save selected content as text file"""
|