pygpt-net 2.7.7__py3-none-any.whl → 2.7.9__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 (98) hide show
  1. pygpt_net/CHANGELOG.txt +12 -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/dialogs/confirm.py +35 -58
  9. pygpt_net/controller/lang/mapping.py +9 -9
  10. pygpt_net/controller/realtime/realtime.py +13 -1
  11. pygpt_net/controller/remote_store/{google/batch.py → batch.py} +209 -252
  12. pygpt_net/controller/remote_store/remote_store.py +982 -13
  13. pygpt_net/core/command/command.py +0 -0
  14. pygpt_net/core/db/viewer.py +1 -1
  15. pygpt_net/core/realtime/worker.py +3 -1
  16. pygpt_net/{controller/remote_store/google → core/remote_store/anthropic}/__init__.py +0 -1
  17. pygpt_net/core/remote_store/anthropic/files.py +211 -0
  18. pygpt_net/core/remote_store/anthropic/store.py +208 -0
  19. pygpt_net/core/remote_store/openai/store.py +5 -4
  20. pygpt_net/core/remote_store/remote_store.py +5 -1
  21. pygpt_net/{controller/remote_store/openai → core/remote_store/xai}/__init__.py +0 -1
  22. pygpt_net/core/remote_store/xai/files.py +225 -0
  23. pygpt_net/core/remote_store/xai/store.py +219 -0
  24. pygpt_net/data/config/config.json +10 -6
  25. pygpt_net/data/config/models.json +38 -22
  26. pygpt_net/data/config/settings.json +54 -1
  27. pygpt_net/data/icons/folder_eye.svg +1 -0
  28. pygpt_net/data/icons/folder_eye_filled.svg +1 -0
  29. pygpt_net/data/icons/folder_open.svg +1 -0
  30. pygpt_net/data/icons/folder_open_filled.svg +1 -0
  31. pygpt_net/data/locale/locale.de.ini +4 -3
  32. pygpt_net/data/locale/locale.en.ini +14 -4
  33. pygpt_net/data/locale/locale.es.ini +4 -3
  34. pygpt_net/data/locale/locale.fr.ini +4 -3
  35. pygpt_net/data/locale/locale.it.ini +4 -3
  36. pygpt_net/data/locale/locale.pl.ini +5 -4
  37. pygpt_net/data/locale/locale.uk.ini +4 -3
  38. pygpt_net/data/locale/locale.zh.ini +4 -3
  39. pygpt_net/icons.qrc +4 -0
  40. pygpt_net/icons_rc.py +282 -138
  41. pygpt_net/provider/api/anthropic/__init__.py +2 -0
  42. pygpt_net/provider/api/anthropic/chat.py +84 -1
  43. pygpt_net/provider/api/anthropic/store.py +307 -0
  44. pygpt_net/provider/api/anthropic/stream.py +75 -0
  45. pygpt_net/provider/api/anthropic/worker/__init__.py +0 -0
  46. pygpt_net/provider/api/anthropic/worker/importer.py +278 -0
  47. pygpt_net/provider/api/google/chat.py +59 -2
  48. pygpt_net/provider/api/google/realtime/client.py +70 -24
  49. pygpt_net/provider/api/google/realtime/realtime.py +48 -12
  50. pygpt_net/provider/api/google/store.py +124 -3
  51. pygpt_net/provider/api/google/stream.py +91 -24
  52. pygpt_net/provider/api/google/worker/importer.py +16 -28
  53. pygpt_net/provider/api/openai/assistants.py +2 -2
  54. pygpt_net/provider/api/openai/realtime/realtime.py +26 -6
  55. pygpt_net/provider/api/openai/store.py +4 -1
  56. pygpt_net/provider/api/openai/worker/importer.py +19 -61
  57. pygpt_net/provider/api/openai/worker/importer_assistants.py +230 -0
  58. pygpt_net/provider/api/x_ai/__init__.py +27 -6
  59. pygpt_net/provider/api/x_ai/audio.py +43 -11
  60. pygpt_net/provider/api/x_ai/chat.py +92 -4
  61. pygpt_net/provider/api/x_ai/realtime/__init__.py +12 -0
  62. pygpt_net/provider/api/x_ai/realtime/client.py +1864 -0
  63. pygpt_net/provider/api/x_ai/realtime/realtime.py +213 -0
  64. pygpt_net/provider/api/x_ai/remote_tools.py +102 -1
  65. pygpt_net/provider/api/x_ai/store.py +610 -0
  66. pygpt_net/provider/api/x_ai/stream.py +30 -9
  67. pygpt_net/provider/api/x_ai/tools.py +51 -0
  68. pygpt_net/provider/api/x_ai/worker/importer.py +308 -0
  69. pygpt_net/provider/audio_input/xai_grok_voice.py +390 -0
  70. pygpt_net/provider/audio_output/xai_tts.py +325 -0
  71. pygpt_net/provider/core/config/patch.py +29 -3
  72. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
  73. pygpt_net/provider/core/model/patch.py +49 -1
  74. pygpt_net/tools/image_viewer/tool.py +334 -34
  75. pygpt_net/tools/image_viewer/ui/dialogs.py +317 -21
  76. pygpt_net/ui/dialog/assistant.py +1 -1
  77. pygpt_net/ui/dialog/plugins.py +13 -5
  78. pygpt_net/ui/dialog/remote_store.py +552 -0
  79. pygpt_net/ui/dialogs.py +3 -5
  80. pygpt_net/ui/layout/ctx/ctx_list.py +58 -7
  81. pygpt_net/ui/menu/tools.py +6 -13
  82. pygpt_net/ui/widget/dialog/{remote_store_google.py → remote_store.py} +10 -10
  83. pygpt_net/ui/widget/element/button.py +4 -4
  84. pygpt_net/ui/widget/image/display.py +2 -2
  85. pygpt_net/ui/widget/lists/context.py +2 -2
  86. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/METADATA +14 -2
  87. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/RECORD +87 -75
  88. pygpt_net/controller/remote_store/google/store.py +0 -615
  89. pygpt_net/controller/remote_store/openai/batch.py +0 -524
  90. pygpt_net/controller/remote_store/openai/store.py +0 -699
  91. pygpt_net/ui/dialog/remote_store_google.py +0 -539
  92. pygpt_net/ui/dialog/remote_store_openai.py +0 -539
  93. pygpt_net/ui/widget/dialog/remote_store_openai.py +0 -56
  94. pygpt_net/ui/widget/lists/remote_store_google.py +0 -248
  95. pygpt_net/ui/widget/lists/remote_store_openai.py +0 -317
  96. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/LICENSE +0 -0
  97. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/WHEEL +0 -0
  98. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,552 @@
1
+ # ui/dialog/remote_store.py
2
+ #!/usr/bin/env python3
3
+ # -*- coding: utf-8 -*-
4
+ # ================================================== #
5
+ # This file is a part of PYGPT package #
6
+ # Website: https://pygpt.net #
7
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
8
+ # MIT License #
9
+ # Created By : Marcin Szczygliński #
10
+ # Updated Date: 2026.01.06 18:00:00 #
11
+ # ================================================== #
12
+
13
+ import copy
14
+
15
+ from PySide6.QtCore import Qt, QPoint
16
+ from PySide6.QtGui import QStandardItemModel, QAction, QIcon
17
+ from PySide6.QtWidgets import (
18
+ QPushButton, QHBoxLayout, QLabel, QVBoxLayout, QScrollArea, QWidget,
19
+ QFrame, QSplitter, QSizePolicy, QMenuBar, QCheckBox, QMenu, QListView
20
+ )
21
+
22
+ from pygpt_net.ui.widget.dialog.remote_store import RemoteStoreDialog
23
+ from pygpt_net.ui.widget.element.group import CollapsedGroup
24
+ from pygpt_net.ui.widget.element.labels import UrlLabel, DescLabel
25
+ from pygpt_net.ui.widget.option.checkbox import OptionCheckbox
26
+ from pygpt_net.ui.widget.option.checkbox_list import OptionCheckboxList
27
+ from pygpt_net.ui.widget.option.combo import OptionCombo, NoScrollCombo
28
+ from pygpt_net.ui.widget.option.dictionary import OptionDict
29
+ from pygpt_net.ui.widget.option.input import OptionInput, PasswordInput
30
+ from pygpt_net.ui.widget.option.slider import OptionSlider
31
+ from pygpt_net.ui.widget.option.textarea import OptionTextarea
32
+ from pygpt_net.utils import trans
33
+
34
+
35
+ class RemoteStore:
36
+ def __init__(self, window=None):
37
+ """
38
+ Remote Store editor dialog
39
+
40
+ :param window: Window instance
41
+ """
42
+ self.window = window
43
+ self.dialog_id = "remote_store"
44
+
45
+ # ======================== Build UI ========================
46
+
47
+ def setup(self):
48
+ ui = self.window.ui
49
+ nodes = ui.nodes
50
+ models = ui.models
51
+ splitters = ui.splitters
52
+ controller = self.window.controller
53
+
54
+ nodes['remote_store.btn.new'] = QPushButton(trans("dialog.remote_store.btn.new"))
55
+ nodes['remote_store.btn.save'] = QPushButton(trans("dialog.remote_store.btn.save"))
56
+ nodes['remote_store.btn.refresh_status'] = QPushButton(QIcon(":/icons/reload.svg"), "")
57
+ nodes['remote_store.btn.close'] = QPushButton(trans("dialog.remote_store.btn.close"))
58
+
59
+ nodes['remote_store.btn.refresh_status'].setToolTip(
60
+ trans("dialog.remote_store.btn.refresh_status")
61
+ )
62
+ nodes['remote_store.btn.refresh_status'].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
63
+
64
+ nodes['remote_store.btn.upload.files'] = QPushButton(trans("dialog.remote_store.btn.upload.files"))
65
+ nodes['remote_store.btn.upload.dir'] = QPushButton(trans("dialog.remote_store.btn.upload.dir"))
66
+
67
+ # Provider selector
68
+ providers = controller.remote_store.get_providers()
69
+ nodes['remote_store.provider.label'] = QLabel(trans("remote_store.provider"))
70
+ nodes['remote_store.provider.desc'] = DescLabel(trans("remote_store.provider.desc"))
71
+ nodes['remote_store.provider.desc'].setWordWrap(True)
72
+ nodes['remote_store.provider.desc'].setMaximumHeight(40)
73
+ nodes['remote_store.provider.desc'].setStyleSheet("font-size: 10px;")
74
+ nodes['remote_store.provider.combo'] = NoScrollCombo()
75
+ for key in providers:
76
+ provider = providers[key]
77
+ nodes['remote_store.provider.combo'].addItem(provider, key)
78
+
79
+ nodes['remote_store.hide_thread'] = QCheckBox(trans("remote_store.hide_threads"))
80
+
81
+ # Bindings (unified controller only; no per-provider refs)
82
+ nodes['remote_store.btn.new'].clicked.connect(controller.remote_store.new)
83
+ nodes['remote_store.btn.save'].clicked.connect(controller.remote_store.save_btn)
84
+ nodes['remote_store.btn.close'].clicked.connect(controller.remote_store.close)
85
+ nodes['remote_store.btn.refresh_status'].clicked.connect(controller.remote_store.refresh_status)
86
+ nodes['remote_store.btn.upload.files'].clicked.connect(controller.remote_store.batch.open_upload_files)
87
+ nodes['remote_store.btn.upload.dir'].clicked.connect(controller.remote_store.batch.open_upload_dir)
88
+
89
+ nodes['remote_store.provider.combo'].currentIndexChanged.connect(
90
+ lambda i: controller.remote_store.set_provider(nodes['remote_store.provider.combo'].itemData(i))
91
+ )
92
+ nodes['remote_store.hide_thread'].toggled.connect(controller.remote_store.set_hide_thread)
93
+
94
+ nodes['remote_store.btn.new'].setAutoDefault(False)
95
+ nodes['remote_store.btn.refresh_status'].setAutoDefault(False)
96
+ nodes['remote_store.btn.save'].setAutoDefault(True)
97
+
98
+ footer = QHBoxLayout()
99
+ footer.addWidget(nodes['remote_store.btn.close'])
100
+ footer.addWidget(nodes['remote_store.btn.save'])
101
+
102
+ # Options panel (right)
103
+ parent_id = "remote_store"
104
+ scroll = QScrollArea()
105
+ scroll.setWidgetResizable(True)
106
+ content = QVBoxLayout()
107
+
108
+ if parent_id not in ui.config:
109
+ ui.config[parent_id] = {}
110
+
111
+ options = copy.deepcopy(controller.remote_store.get_options())
112
+ widgets = self.build_widgets(options)
113
+ advanced_keys = [k for k, v in options.items() if v.get('advanced')]
114
+
115
+ for key, w in widgets.items():
116
+ ui.config[parent_id][key] = w
117
+
118
+ # Provider bar
119
+ provider_bar = QHBoxLayout()
120
+ provider_bar.addWidget(nodes['remote_store.provider.label'], 0)
121
+ provider_bar.addWidget(nodes['remote_store.provider.combo'], 1)
122
+ provider_bar.setContentsMargins(5, 5, 5, 5)
123
+ content.addLayout(provider_bar)
124
+ content.addWidget(nodes['remote_store.provider.desc'])
125
+
126
+ for key in widgets:
127
+ if key in advanced_keys:
128
+ continue
129
+ content.addLayout(self.add_option(widgets[key], options[key]))
130
+
131
+ if advanced_keys:
132
+ group_id = 'remote_store.advanced'
133
+ ui.groups[group_id] = CollapsedGroup(self.window, group_id, None, False, None)
134
+ ui.groups[group_id].box.setText(trans('settings.advanced.collapse'))
135
+ for key in widgets:
136
+ if key not in advanced_keys:
137
+ continue
138
+ option = self.add_option(widgets[key], options[key])
139
+ ui.groups[group_id].add_layout(option)
140
+ content.addWidget(ui.groups[group_id])
141
+
142
+ content.setContentsMargins(0, 0, 0, 0)
143
+
144
+ scroll_widget = QWidget()
145
+ scroll_widget.setLayout(content)
146
+ scroll.setWidget(scroll_widget)
147
+
148
+ area = QVBoxLayout()
149
+ area.addWidget(scroll)
150
+ area.setContentsMargins(0, 0, 0, 0)
151
+
152
+ area_widget = QWidget()
153
+ area_widget.setLayout(area)
154
+
155
+ # Left: stores list
156
+ list_id = 'remote_store.list'
157
+ nodes[list_id] = QListView()
158
+ models[list_id] = self.create_model(self.window)
159
+ nodes[list_id].setModel(models[list_id])
160
+ nodes[list_id].setMinimumWidth(260)
161
+ nodes[list_id].setSelectionMode(QListView.ExtendedSelection)
162
+ nodes[list_id].setEditTriggers(QListView.NoEditTriggers)
163
+ nodes[list_id].clicked.connect(lambda ix: controller.remote_store.select(ix.row()))
164
+ nodes[list_id].setContextMenuPolicy(Qt.CustomContextMenu)
165
+ nodes[list_id].customContextMenuRequested.connect(self.on_stores_context_menu)
166
+
167
+ left_layout = QVBoxLayout()
168
+ left_layout.addWidget(nodes['remote_store.btn.new'])
169
+ left_layout.addWidget(nodes[list_id])
170
+ left_layout.addWidget(nodes['remote_store.hide_thread'])
171
+ left_layout.setContentsMargins(0, 0, 0, 0)
172
+ left_widget = QWidget()
173
+ left_widget.setLayout(left_layout)
174
+
175
+ # Files panel
176
+ files_panel = QVBoxLayout()
177
+ files_label = QLabel("Files")
178
+ files_label.setStyleSheet("font-weight: bold;")
179
+ files_panel.addWidget(files_label)
180
+
181
+ files_list_id = 'remote_store.files.list'
182
+ nodes[files_list_id] = QListView()
183
+ nodes[files_list_id].setEditTriggers(QListView.NoEditTriggers)
184
+ nodes[files_list_id].setSelectionMode(QListView.ExtendedSelection)
185
+ nodes[files_list_id].setContextMenuPolicy(Qt.CustomContextMenu)
186
+ nodes[files_list_id].customContextMenuRequested.connect(self.on_files_context_menu)
187
+
188
+ models[files_list_id] = self.create_model(self.window)
189
+ nodes[files_list_id].setModel(models[files_list_id])
190
+ nodes[files_list_id].setMinimumWidth(300)
191
+
192
+ files_bottom = QHBoxLayout()
193
+ files_bottom.setContentsMargins(0, 0, 0, 0)
194
+ files_bottom.addWidget(nodes['remote_store.btn.upload.files'])
195
+ files_bottom.addWidget(nodes['remote_store.btn.upload.dir'])
196
+ files_bottom.addWidget(nodes['remote_store.btn.refresh_status'])
197
+
198
+ files_panel.addWidget(nodes[files_list_id])
199
+ files_panel.setContentsMargins(0, 0, 0, 5)
200
+ files_panel.addLayout(files_bottom)
201
+ files_widget = QWidget()
202
+ files_widget.setLayout(files_panel)
203
+
204
+ # Splitters
205
+ splitters['dialog.remote_store'] = QSplitter(Qt.Horizontal)
206
+ splitters['dialog.remote_store'].addWidget(left_widget)
207
+
208
+ splitters['dialog.remote_store.right'] = QSplitter(Qt.Horizontal)
209
+ splitters['dialog.remote_store.right'].addWidget(files_widget)
210
+ splitters['dialog.remote_store.right'].addWidget(area_widget)
211
+ splitters['dialog.remote_store.right'].setStretchFactor(1, 7)
212
+ splitters['dialog.remote_store.right'].setStretchFactor(0, 3)
213
+
214
+ splitters['dialog.remote_store'].addWidget(splitters['dialog.remote_store.right'])
215
+ splitters['dialog.remote_store'].setStretchFactor(0, 2)
216
+ splitters['dialog.remote_store'].setStretchFactor(1, 8)
217
+ splitters['dialog.remote_store'].setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
218
+
219
+ main_layout = QHBoxLayout()
220
+ main_layout.addWidget(splitters['dialog.remote_store'])
221
+ main_layout.setContentsMargins(0, 0, 0, 0)
222
+
223
+ layout = QVBoxLayout()
224
+ layout.addLayout(main_layout)
225
+ layout.addLayout(footer)
226
+ layout.setMenuBar(self.setup_menu())
227
+
228
+ ui.dialog[self.dialog_id] = RemoteStoreDialog(self.window, self.dialog_id) # TODO: change to store dialog
229
+ ui.dialog[self.dialog_id].setLayout(layout)
230
+ ui.dialog[self.dialog_id].setWindowTitle(trans('dialog.remote_store'))
231
+
232
+ # Initial sync
233
+ self.sync_hide_threads_checkbox(self._provider_from_cfg())
234
+
235
+ # ======================== Menu bar ========================
236
+
237
+ def setup_menu(self) -> QMenuBar:
238
+ controller = self.window.controller
239
+ self.actions = {}
240
+ self.menu_bar = QMenuBar()
241
+
242
+ self.menu = {}
243
+ self.menu["current"] = self.menu_bar.addMenu(trans("dialog.remote_store.menu.current"))
244
+ self.menu["all"] = self.menu_bar.addMenu(trans("dialog.remote_store.menu.all"))
245
+
246
+ # Current
247
+ self.actions["current.import_files"] = QAction(QIcon(":/icons/download.svg"),
248
+ trans("dialog.remote_store.menu.current.import_files"),
249
+ self.menu_bar)
250
+ self.actions["current.import_files"].triggered.connect(
251
+ lambda: controller.remote_store.import_store_files(controller.remote_store.current)
252
+ )
253
+
254
+ self.actions["current.refresh_store"] = QAction(QIcon(":/icons/reload.svg"),
255
+ trans("dialog.remote_store.menu.current.refresh_store"),
256
+ self.menu_bar)
257
+ self.actions["current.refresh_store"].triggered.connect(controller.remote_store.refresh_status)
258
+
259
+ self.actions["current.clear_files"] = QAction(QIcon(":/icons/close.svg"),
260
+ trans("dialog.remote_store.menu.current.clear_files"),
261
+ self.menu_bar)
262
+ self.actions["current.clear_files"].triggered.connect(
263
+ lambda: controller.remote_store.clear_store_files(controller.remote_store.current)
264
+ )
265
+
266
+ self.actions["current.truncate_files"] = QAction(QIcon(":/icons/delete.svg"),
267
+ trans("dialog.remote_store.menu.current.truncate_files"),
268
+ self.menu_bar)
269
+ self.actions["current.truncate_files"].triggered.connect(
270
+ lambda: controller.remote_store.truncate_store_files(controller.remote_store.current)
271
+ )
272
+
273
+ self.actions["current.delete"] = QAction(QIcon(":/icons/delete.svg"),
274
+ trans("dialog.remote_store.menu.current.delete"),
275
+ self.menu_bar)
276
+ self.actions["current.delete"].triggered.connect(
277
+ lambda: controller.remote_store.delete(controller.remote_store.current)
278
+ )
279
+
280
+ # All
281
+ self.actions["all.import_all"] = QAction(QIcon(":/icons/download.svg"),
282
+ trans("dialog.remote_store.menu.all.import_all"),
283
+ self.menu_bar)
284
+ self.actions["all.import_all"].triggered.connect(controller.remote_store.import_stores)
285
+
286
+ self.actions["all.import_files"] = QAction(QIcon(":/icons/download.svg"),
287
+ trans("dialog.remote_store.menu.all.import_files"),
288
+ self.menu_bar)
289
+ self.actions["all.import_files"].triggered.connect(controller.remote_store.import_files)
290
+
291
+ self.actions["all.refresh_stores"] = QAction(QIcon(":/icons/reload.svg"),
292
+ trans("dialog.remote_store.menu.all.refresh_store"),
293
+ self.menu_bar)
294
+ self.actions["all.refresh_stores"].triggered.connect(controller.remote_store.refresh_stores)
295
+
296
+ self.actions["all.clear_stores"] = QAction(QIcon(":/icons/close.svg"),
297
+ trans("dialog.remote_store.menu.all.clear_store"),
298
+ self.menu_bar)
299
+ self.actions["all.clear_stores"].triggered.connect(controller.remote_store.clear_stores)
300
+
301
+ self.actions["all.clear_files"] = QAction(QIcon(":/icons/close.svg"),
302
+ trans("dialog.remote_store.menu.all.clear_files"),
303
+ self.menu_bar)
304
+ self.actions["all.clear_files"].triggered.connect(controller.remote_store.clear_files)
305
+
306
+ self.actions["all.truncate_stores"] = QAction(QIcon(":/icons/delete.svg"),
307
+ trans("dialog.remote_store.menu.all.truncate_store"),
308
+ self.menu_bar)
309
+ self.actions["all.truncate_stores"].triggered.connect(controller.remote_store.truncate_stores)
310
+
311
+ self.actions["all.truncate_files"] = QAction(QIcon(":/icons/delete.svg"),
312
+ trans("dialog.remote_store.menu.all.truncate_files"),
313
+ self.menu_bar)
314
+ self.actions["all.truncate_files"].triggered.connect(controller.remote_store.truncate_files)
315
+
316
+ self.menu["current"].addAction(self.actions["current.import_files"])
317
+ self.menu["current"].addAction(self.actions["current.refresh_store"])
318
+ self.menu["current"].addAction(self.actions["current.clear_files"])
319
+ self.menu["current"].addAction(self.actions["current.truncate_files"])
320
+ self.menu["current"].addAction(self.actions["current.delete"])
321
+
322
+ self.menu["all"].addAction(self.actions["all.import_all"])
323
+ self.menu["all"].addAction(self.actions["all.import_files"])
324
+ self.menu["all"].addAction(self.actions["all.refresh_stores"])
325
+ self.menu["all"].addAction(self.actions["all.clear_stores"])
326
+ self.menu["all"].addAction(self.actions["all.clear_files"])
327
+ self.menu["all"].addAction(self.actions["all.truncate_stores"])
328
+ self.menu["all"].addAction(self.actions["all.truncate_files"])
329
+
330
+ return self.menu_bar
331
+
332
+ # ======================== Helpers ========================
333
+
334
+ def build_widgets(self, options: dict) -> dict:
335
+ parent = "model"
336
+ widgets = {}
337
+ for key in options:
338
+ option = options[key]
339
+ option['id'] = key
340
+ if option['type'] in ('text', 'int', 'float'):
341
+ if option.get('slider') and option['type'] in ('int', 'float'):
342
+ widgets[key] = OptionSlider(self.window, parent, key, option)
343
+ else:
344
+ if option.get('secret'):
345
+ widgets[key] = PasswordInput(self.window, parent, key, option)
346
+ else:
347
+ widgets[key] = OptionInput(self.window, parent, key, option)
348
+ elif option['type'] == 'textarea':
349
+ widgets[key] = OptionTextarea(self.window, parent, key, option)
350
+ elif option['type'] == 'bool':
351
+ widgets[key] = OptionCheckbox(self.window, parent, key, option)
352
+ elif option['type'] == 'bool_list':
353
+ self.window.controller.config.placeholder.apply(option)
354
+ widgets[key] = OptionCheckboxList(self.window, parent, key, option)
355
+ elif option['type'] == 'dict':
356
+ self.window.controller.config.placeholder.apply(option)
357
+ widgets[key] = OptionDict(self.window, parent, key, option)
358
+ widgets[key].setMinimumHeight(200)
359
+ elif option['type'] == 'combo':
360
+ self.window.controller.config.placeholder.apply(option)
361
+ widgets[key] = OptionCombo(self.window, parent, key, option)
362
+ return widgets
363
+
364
+ def add_line(self) -> QFrame:
365
+ line = QFrame()
366
+ line.setFrameShape(QFrame.HLine)
367
+ line.setFrameShadow(QFrame.Sunken)
368
+ return line
369
+
370
+ def add_urls(self, urls: dict) -> QWidget:
371
+ layout = QVBoxLayout()
372
+ for name in urls:
373
+ url = urls[name]
374
+ label = UrlLabel(name, url)
375
+ layout.addWidget(label)
376
+ widget = QWidget()
377
+ widget.setLayout(layout)
378
+ return widget
379
+
380
+ def add_option(self, widget: QWidget, option: dict) -> QVBoxLayout:
381
+ one_column_types = ['textarea', 'dict', 'bool']
382
+ key = option['id']
383
+ label = trans(option['label'])
384
+ label_key = 'model.' + key + '.label'
385
+ desc = None
386
+ desc_key = None
387
+
388
+ if option['type'] != 'bool':
389
+ self.window.ui.nodes[label_key] = QLabel(label)
390
+ self.window.ui.nodes[label_key].setStyleSheet("font-weight: bold;")
391
+
392
+ if "description" in option:
393
+ desc = trans(option['description'])
394
+ desc_key = 'model.' + key + '.desc'
395
+
396
+ if option['type'] not in one_column_types:
397
+ cols = QHBoxLayout()
398
+ cols.addWidget(self.window.ui.nodes[label_key])
399
+ cols.addWidget(widget)
400
+
401
+ cols_widget = QWidget()
402
+ cols_widget.setLayout(cols)
403
+ cols_widget.setMaximumHeight(90)
404
+
405
+ layout = QVBoxLayout()
406
+ layout.addWidget(cols_widget)
407
+ else:
408
+ layout = QVBoxLayout()
409
+ if option['type'] != 'bool':
410
+ layout.addWidget(self.window.ui.nodes[label_key])
411
+ else:
412
+ widget.box.setText(label)
413
+ layout.addWidget(widget)
414
+
415
+ if desc:
416
+ self.window.ui.nodes[desc_key] = DescLabel(desc)
417
+ self.window.ui.nodes[desc_key].setWordWrap(True)
418
+ self.window.ui.nodes[desc_key].setMaximumHeight(40)
419
+ self.window.ui.nodes[desc_key].setStyleSheet("font-size: 10px;")
420
+ layout.addWidget(self.window.ui.nodes[desc_key])
421
+
422
+ line = self.add_line()
423
+ layout.addWidget(line)
424
+ return layout
425
+
426
+ def create_model(self, parent) -> QStandardItemModel:
427
+ return QStandardItemModel(0, 1, parent)
428
+
429
+ def update_list_pairs(self, id: str, pairs: list[tuple[str, str]]):
430
+ models = self.window.ui.models
431
+ if id not in models:
432
+ return
433
+ model = models[id]
434
+ model.setRowCount(len(pairs))
435
+ for i, (_, label) in enumerate(pairs):
436
+ model.setData(model.index(i, 0), label)
437
+
438
+ def set_current_row(self, list_id: str, row: int):
439
+ if list_id not in self.window.ui.nodes or list_id not in self.window.ui.models:
440
+ return
441
+ current = self.window.ui.models[list_id].index(row, 0)
442
+ self.window.ui.nodes[list_id].setCurrentIndex(current)
443
+
444
+ def set_provider_in_ui(self, provider: str):
445
+ combo = self.window.ui.nodes.get('remote_store.provider.combo')
446
+ if not combo:
447
+ return
448
+ idx = combo.findData(provider)
449
+ if idx >= 0:
450
+ blocked = combo.blockSignals(True)
451
+ combo.setCurrentIndex(idx)
452
+ combo.blockSignals(blocked)
453
+
454
+ def sync_provider_dependent_ui(self, provider: str):
455
+ w = self.window.ui.config.get('remote_store', {}).get('expire_days')
456
+ if w:
457
+ w.setVisible(provider == "openai")
458
+
459
+ def sync_hide_threads_checkbox(self, provider: str):
460
+ nodes = self.window.ui.nodes
461
+ state = bool(self.window.core.config.get("remote_store.hide_threads"))
462
+ nodes['remote_store.hide_thread'].setChecked(state)
463
+
464
+ def _provider_from_cfg(self) -> str:
465
+ key = self.window.core.config.get("remote_store.provider")
466
+ return key if key in self.window.controller.remote_store.get_provider_keys() \
467
+ else self.window.controller.remote_store.DEFAULT_PROVIDER
468
+
469
+ def update_title(self, text: str):
470
+ dlg = self.window.ui.dialog.get(self.dialog_id)
471
+ if dlg:
472
+ dlg.setWindowTitle(text)
473
+
474
+ # ======================== Context menus ========================
475
+
476
+ def on_stores_context_menu(self, pos: QPoint):
477
+ controller = self.window.controller
478
+ view_id = 'remote_store.list'
479
+ if view_id not in self.window.ui.nodes:
480
+ return
481
+ view = self.window.ui.nodes[view_id]
482
+ index = view.indexAt(pos)
483
+ if not index.isValid():
484
+ return
485
+
486
+ sel_model = view.selectionModel()
487
+ selected_rows = sorted([ix.row() for ix in sel_model.selectedRows()]) if sel_model else []
488
+ multi = len(selected_rows) > 1
489
+
490
+ row = index.row()
491
+ menu = QMenu(view)
492
+ act_refresh = QAction(QIcon(":/icons/reload.svg"), trans("dialog.remote_store.menu.current.refresh_store"), view)
493
+ act_delete = QAction(QIcon(":/icons/delete.svg"), trans("action.delete"), view)
494
+ act_clear = QAction(QIcon(":/icons/close.svg"), trans("dialog.remote_store.menu.current.clear_files"), view)
495
+ act_truncate = QAction(QIcon(":/icons/delete.svg"), trans("dialog.remote_store.menu.current.truncate_files"), view)
496
+
497
+ if multi:
498
+ act_refresh.triggered.connect(lambda: controller.remote_store.refresh_by_idx(list(selected_rows)))
499
+ act_delete.triggered.connect(lambda: controller.remote_store.delete_by_idx(list(selected_rows)))
500
+ act_clear.triggered.connect(lambda: controller.remote_store.clear_store_files(
501
+ [controller.remote_store.get_by_tab_idx(r) for r in selected_rows]
502
+ ))
503
+ act_truncate.triggered.connect(lambda: controller.remote_store.truncate_store_files(
504
+ [controller.remote_store.get_by_tab_idx(r) for r in selected_rows]
505
+ ))
506
+ else:
507
+ act_refresh.triggered.connect(lambda: controller.remote_store.refresh_by_idx(row))
508
+ act_delete.triggered.connect(lambda: controller.remote_store.delete_by_idx(row))
509
+ act_clear.triggered.connect(lambda: controller.remote_store.clear_store_files(
510
+ controller.remote_store.get_by_tab_idx(row)
511
+ ))
512
+ act_truncate.triggered.connect(lambda: controller.remote_store.truncate_store_files(
513
+ controller.remote_store.get_by_tab_idx(row)
514
+ ))
515
+
516
+ menu.addAction(act_refresh)
517
+ menu.addAction(act_delete)
518
+ menu.addAction(act_clear)
519
+ menu.addAction(act_truncate)
520
+ menu.exec(view.mapToGlobal(pos))
521
+
522
+ def on_files_context_menu(self, pos: QPoint):
523
+ controller = self.window.controller
524
+ view_id = 'remote_store.files.list'
525
+ if view_id not in self.window.ui.nodes:
526
+ return
527
+ view = self.window.ui.nodes[view_id]
528
+ index = view.indexAt(pos)
529
+ if not index.isValid():
530
+ return
531
+
532
+ sel_model = view.selectionModel()
533
+ selected_rows = sorted([ix.row() for ix in sel_model.selectedRows()]) if sel_model else []
534
+ multi = len(selected_rows) > 1
535
+
536
+ row = index.row()
537
+ menu = QMenu(view)
538
+ act_delete = QAction(QIcon(":/icons/delete.svg"),
539
+ trans("remote_store.menu.file.delete") if hasattr(self.window, 'tr') else "Delete",
540
+ view)
541
+
542
+ if multi:
543
+ act_delete.triggered.connect(
544
+ lambda checked=False, rows=list(selected_rows): controller.remote_store.delete_file_by_idx(rows)
545
+ )
546
+ else:
547
+ act_delete.triggered.connect(
548
+ lambda checked=False, r=row: controller.remote_store.delete_file_by_idx(r)
549
+ )
550
+
551
+ menu.addAction(act_delete)
552
+ menu.exec(view.mapToGlobal(pos))
pygpt_net/ui/dialogs.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: 2026.01.02 19:00:00 #
9
+ # Updated Date: 2026.01.06 06:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import threading
@@ -16,8 +16,6 @@ from PySide6.QtCore import Qt
16
16
  from pygpt_net.ui.dialog.about import About
17
17
  from pygpt_net.ui.dialog.applog import AppLog
18
18
  from pygpt_net.ui.dialog.assistant import Assistant
19
- from pygpt_net.ui.dialog.remote_store_openai import RemoteStoreOpenAI
20
- from pygpt_net.ui.dialog.remote_store_google import RemoteStoreGoogle
21
19
  from pygpt_net.ui.dialog.changelog import Changelog
22
20
  from pygpt_net.ui.dialog.create import Create
23
21
  from pygpt_net.ui.dialog.db import Database
@@ -34,6 +32,7 @@ from pygpt_net.ui.dialog.plugins import Plugins
34
32
  from pygpt_net.ui.dialog.preset import Preset
35
33
  from pygpt_net.ui.dialog.preset_plugins import PresetPlugins
36
34
  from pygpt_net.ui.dialog.profile import Profile, ProfileEdit
35
+ from pygpt_net.ui.dialog.remote_store import RemoteStore
37
36
  from pygpt_net.ui.dialog.rename import Rename
38
37
  from pygpt_net.ui.dialog.settings import Settings
39
38
  from pygpt_net.ui.dialog.snap import Snap
@@ -106,8 +105,7 @@ class Dialogs:
106
105
  self.window.plugin_presets = PresetPlugins(self.window)
107
106
  self.window.model_settings = Models(self.window)
108
107
  self.window.model_importer = ModelsImporter(self.window)
109
- self.window.remote_store_google = RemoteStoreGoogle(self.window)
110
- self.window.remote_store_openai = RemoteStoreOpenAI(self.window)
108
+ self.window.remote_store = RemoteStore(self.window)
111
109
 
112
110
  self.window.ui.dialog['alert'] = AlertDialog(self.window)
113
111
  self.window.ui.dialog['confirm'] = ConfirmDialog(self.window)