pygpt-net 2.7.7__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.
- pygpt_net/CHANGELOG.txt +7 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +5 -1
- pygpt_net/controller/assistant/batch.py +2 -2
- pygpt_net/controller/assistant/files.py +7 -6
- pygpt_net/controller/assistant/threads.py +0 -0
- pygpt_net/controller/chat/command.py +0 -0
- pygpt_net/controller/dialogs/confirm.py +35 -58
- pygpt_net/controller/lang/mapping.py +9 -9
- pygpt_net/controller/remote_store/{google/batch.py → batch.py} +209 -252
- pygpt_net/controller/remote_store/remote_store.py +982 -13
- pygpt_net/core/command/command.py +0 -0
- pygpt_net/core/db/viewer.py +1 -1
- pygpt_net/core/realtime/worker.py +3 -1
- pygpt_net/{controller/remote_store/google → core/remote_store/anthropic}/__init__.py +0 -1
- pygpt_net/core/remote_store/anthropic/files.py +211 -0
- pygpt_net/core/remote_store/anthropic/store.py +208 -0
- pygpt_net/core/remote_store/openai/store.py +5 -4
- pygpt_net/core/remote_store/remote_store.py +5 -1
- pygpt_net/{controller/remote_store/openai → core/remote_store/xai}/__init__.py +0 -1
- pygpt_net/core/remote_store/xai/files.py +225 -0
- pygpt_net/core/remote_store/xai/store.py +219 -0
- pygpt_net/data/config/config.json +9 -6
- pygpt_net/data/config/models.json +5 -4
- pygpt_net/data/config/settings.json +54 -1
- pygpt_net/data/icons/folder_eye.svg +1 -0
- pygpt_net/data/icons/folder_eye_filled.svg +1 -0
- pygpt_net/data/icons/folder_open.svg +1 -0
- pygpt_net/data/icons/folder_open_filled.svg +1 -0
- pygpt_net/data/locale/locale.de.ini +4 -3
- pygpt_net/data/locale/locale.en.ini +14 -4
- pygpt_net/data/locale/locale.es.ini +4 -3
- pygpt_net/data/locale/locale.fr.ini +4 -3
- pygpt_net/data/locale/locale.it.ini +4 -3
- pygpt_net/data/locale/locale.pl.ini +5 -4
- pygpt_net/data/locale/locale.uk.ini +4 -3
- pygpt_net/data/locale/locale.zh.ini +4 -3
- pygpt_net/icons.qrc +4 -0
- pygpt_net/icons_rc.py +282 -138
- pygpt_net/provider/api/anthropic/__init__.py +2 -0
- pygpt_net/provider/api/anthropic/chat.py +84 -1
- pygpt_net/provider/api/anthropic/store.py +307 -0
- pygpt_net/provider/api/anthropic/stream.py +75 -0
- pygpt_net/provider/api/anthropic/worker/__init__.py +0 -0
- pygpt_net/provider/api/anthropic/worker/importer.py +278 -0
- pygpt_net/provider/api/google/chat.py +59 -2
- pygpt_net/provider/api/google/store.py +124 -3
- pygpt_net/provider/api/google/stream.py +91 -24
- pygpt_net/provider/api/google/worker/importer.py +16 -28
- pygpt_net/provider/api/openai/assistants.py +2 -2
- pygpt_net/provider/api/openai/store.py +4 -1
- pygpt_net/provider/api/openai/worker/importer.py +19 -61
- pygpt_net/provider/api/openai/worker/importer_assistants.py +230 -0
- pygpt_net/provider/api/x_ai/__init__.py +30 -6
- pygpt_net/provider/api/x_ai/audio.py +43 -11
- pygpt_net/provider/api/x_ai/chat.py +92 -4
- pygpt_net/provider/api/x_ai/realtime/__init__.py +12 -0
- pygpt_net/provider/api/x_ai/realtime/client.py +1825 -0
- pygpt_net/provider/api/x_ai/realtime/realtime.py +198 -0
- pygpt_net/provider/api/x_ai/remote_tools.py +19 -1
- pygpt_net/provider/api/x_ai/store.py +610 -0
- pygpt_net/provider/api/x_ai/stream.py +30 -9
- pygpt_net/provider/api/x_ai/worker/importer.py +308 -0
- pygpt_net/provider/audio_input/xai_grok_voice.py +390 -0
- pygpt_net/provider/audio_output/xai_tts.py +325 -0
- pygpt_net/provider/core/config/patch.py +18 -3
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
- pygpt_net/provider/core/model/patch.py +13 -0
- pygpt_net/tools/image_viewer/tool.py +334 -34
- pygpt_net/tools/image_viewer/ui/dialogs.py +317 -21
- pygpt_net/ui/dialog/assistant.py +1 -1
- pygpt_net/ui/dialog/plugins.py +13 -5
- pygpt_net/ui/dialog/remote_store.py +552 -0
- pygpt_net/ui/dialogs.py +3 -5
- pygpt_net/ui/layout/ctx/ctx_list.py +58 -7
- pygpt_net/ui/menu/tools.py +6 -13
- pygpt_net/ui/widget/dialog/{remote_store_google.py → remote_store.py} +10 -10
- pygpt_net/ui/widget/element/button.py +4 -4
- pygpt_net/ui/widget/image/display.py +2 -2
- pygpt_net/ui/widget/lists/context.py +2 -2
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/METADATA +9 -2
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/RECORD +82 -70
- pygpt_net/controller/remote_store/google/store.py +0 -615
- pygpt_net/controller/remote_store/openai/batch.py +0 -524
- pygpt_net/controller/remote_store/openai/store.py +0 -699
- pygpt_net/ui/dialog/remote_store_google.py +0 -539
- pygpt_net/ui/dialog/remote_store_openai.py +0 -539
- pygpt_net/ui/widget/dialog/remote_store_openai.py +0 -56
- pygpt_net/ui/widget/lists/remote_store_google.py +0 -248
- pygpt_net/ui/widget/lists/remote_store_openai.py +0 -317
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/entry_points.txt +0 -0
|
@@ -1,699 +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
|
-
import copy
|
|
13
|
-
import json
|
|
14
|
-
from typing import Optional, Union
|
|
15
|
-
|
|
16
|
-
from PySide6.QtWidgets import QApplication
|
|
17
|
-
from PySide6.QtGui import QStandardItem
|
|
18
|
-
from PySide6.QtCore import Qt, QTimer
|
|
19
|
-
|
|
20
|
-
from pygpt_net.item.store import RemoteStoreItem
|
|
21
|
-
from pygpt_net.utils import trans
|
|
22
|
-
|
|
23
|
-
from .batch import Batch
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class OpenAIRemoteStore:
|
|
27
|
-
def __init__(self, window=None):
|
|
28
|
-
"""
|
|
29
|
-
OpenAI vector store editor controller
|
|
30
|
-
|
|
31
|
-
:param window: Window instance
|
|
32
|
-
"""
|
|
33
|
-
self.window = window
|
|
34
|
-
self.batch = Batch(window)
|
|
35
|
-
self.dialog = False
|
|
36
|
-
self.config_initialized = False
|
|
37
|
-
self.current = None
|
|
38
|
-
self.width = 800
|
|
39
|
-
self.height = 500
|
|
40
|
-
self.id = "remote_store.openai"
|
|
41
|
-
self.options = {
|
|
42
|
-
"id": {
|
|
43
|
-
"type": "text",
|
|
44
|
-
"label": "remote_store.id",
|
|
45
|
-
"read_only": True,
|
|
46
|
-
"value": "",
|
|
47
|
-
},
|
|
48
|
-
"name": {
|
|
49
|
-
"type": "text",
|
|
50
|
-
"label": "remote_store.name",
|
|
51
|
-
"value": "",
|
|
52
|
-
},
|
|
53
|
-
"expire_days": {
|
|
54
|
-
"type": "int",
|
|
55
|
-
"label": "remote_store.expire_days",
|
|
56
|
-
"value": 0,
|
|
57
|
-
},
|
|
58
|
-
"status": {
|
|
59
|
-
"type": "textarea",
|
|
60
|
-
"label": "remote_store.status",
|
|
61
|
-
"read_only": True,
|
|
62
|
-
"value": "",
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
# Mapping of current files list rows to file IDs
|
|
66
|
-
self._files_row_to_id = []
|
|
67
|
-
|
|
68
|
-
def get_options(self) -> dict:
|
|
69
|
-
"""
|
|
70
|
-
Get options dict
|
|
71
|
-
|
|
72
|
-
:return: options dict
|
|
73
|
-
"""
|
|
74
|
-
return self.options
|
|
75
|
-
|
|
76
|
-
def get_option(self, key: str) -> Optional[dict]:
|
|
77
|
-
"""
|
|
78
|
-
Get option by key
|
|
79
|
-
|
|
80
|
-
:param key: option key
|
|
81
|
-
:return: option dict
|
|
82
|
-
"""
|
|
83
|
-
if key in self.options:
|
|
84
|
-
return self.options[key]
|
|
85
|
-
|
|
86
|
-
def setup(self):
|
|
87
|
-
"""Set up vector store editor"""
|
|
88
|
-
idx = None
|
|
89
|
-
self.window.remote_store_openai.setup(idx) # widget dialog setup
|
|
90
|
-
|
|
91
|
-
def toggle_editor(self):
|
|
92
|
-
"""Toggle vector store editor dialog"""
|
|
93
|
-
if self.dialog:
|
|
94
|
-
self.close()
|
|
95
|
-
else:
|
|
96
|
-
self.open()
|
|
97
|
-
|
|
98
|
-
def reset(self):
|
|
99
|
-
"""Reset vector store editor"""
|
|
100
|
-
self.current = None
|
|
101
|
-
if self.dialog:
|
|
102
|
-
self.init()
|
|
103
|
-
|
|
104
|
-
def open(self, force: bool = False):
|
|
105
|
-
"""
|
|
106
|
-
Open vector store editor dialog
|
|
107
|
-
|
|
108
|
-
:param force: force open dialog
|
|
109
|
-
"""
|
|
110
|
-
if not self.config_initialized:
|
|
111
|
-
self.setup()
|
|
112
|
-
self.config_initialized = True
|
|
113
|
-
if not self.dialog or force:
|
|
114
|
-
self.current = self.window.controller.assistant.editor.get_selected_store_id()
|
|
115
|
-
self.init()
|
|
116
|
-
self.window.ui.dialogs.open(
|
|
117
|
-
"remote_store.openai",
|
|
118
|
-
width=self.width,
|
|
119
|
-
height=self.height,
|
|
120
|
-
)
|
|
121
|
-
self.dialog = True
|
|
122
|
-
|
|
123
|
-
def close(self):
|
|
124
|
-
"""Close vector store editor dialog"""
|
|
125
|
-
if self.dialog:
|
|
126
|
-
self.window.ui.dialogs.close('remote_store.openai')
|
|
127
|
-
self.dialog = False
|
|
128
|
-
|
|
129
|
-
def init(self):
|
|
130
|
-
"""Initialize vector store editor options"""
|
|
131
|
-
self.reload_items()
|
|
132
|
-
|
|
133
|
-
# select the first store if not selected
|
|
134
|
-
if self.current is None:
|
|
135
|
-
self.current = self.get_first_visible()
|
|
136
|
-
|
|
137
|
-
# assign store to config dialog fields
|
|
138
|
-
options = copy.deepcopy(self.get_options()) # copy options
|
|
139
|
-
if self.current is not None and self.window.core.remote_store.openai.has(self.current):
|
|
140
|
-
store = self.window.core.remote_store.openai.items[self.current]
|
|
141
|
-
data_dict = store.to_dict()
|
|
142
|
-
for key in options:
|
|
143
|
-
if key in data_dict:
|
|
144
|
-
value = data_dict[key]
|
|
145
|
-
options[key]["value"] = value
|
|
146
|
-
if key == "status":
|
|
147
|
-
options[key]["value"] = json.dumps(value, indent=4) # as JSON to textarea
|
|
148
|
-
|
|
149
|
-
self.set_tab_by_id(self.current)
|
|
150
|
-
|
|
151
|
-
# load and apply options to config dialog
|
|
152
|
-
self.window.controller.config.load_options(self.id, options)
|
|
153
|
-
else:
|
|
154
|
-
self.current = None # reset if not exists
|
|
155
|
-
self.window.controller.config.load_options(self.id, options)
|
|
156
|
-
|
|
157
|
-
self.update_files_list()
|
|
158
|
-
|
|
159
|
-
def refresh_status(self):
|
|
160
|
-
"""Reload store status"""
|
|
161
|
-
if self.current is not None: # TODO: reset on profile reload
|
|
162
|
-
if self.window.core.remote_store.openai.has(self.current):
|
|
163
|
-
self.window.update_status(trans('status.sending'))
|
|
164
|
-
QApplication.processEvents()
|
|
165
|
-
store = self.window.core.remote_store.openai.items[self.current]
|
|
166
|
-
self.refresh_store(store)
|
|
167
|
-
self.window.update_status(trans('status.assistant.saved'))
|
|
168
|
-
self.update() # update stores list in assistant dialog
|
|
169
|
-
self.update_files_list()
|
|
170
|
-
|
|
171
|
-
def refresh_store(
|
|
172
|
-
self,
|
|
173
|
-
store: RemoteStoreItem,
|
|
174
|
-
update: bool = True
|
|
175
|
-
):
|
|
176
|
-
"""
|
|
177
|
-
Refresh store by ID
|
|
178
|
-
|
|
179
|
-
:param store : store object
|
|
180
|
-
:param update: update store after refresh
|
|
181
|
-
"""
|
|
182
|
-
# update from API
|
|
183
|
-
self.window.core.remote_store.openai.update_status(store.id)
|
|
184
|
-
self.window.core.remote_store.openai.update(store)
|
|
185
|
-
|
|
186
|
-
if update and store.id == self.current:
|
|
187
|
-
self.update_current()
|
|
188
|
-
|
|
189
|
-
def refresh_by_idx(self, idx: Union[int, list]):
|
|
190
|
-
"""
|
|
191
|
-
Refresh store by idx
|
|
192
|
-
|
|
193
|
-
:param idx: store idx or list of idxs
|
|
194
|
-
"""
|
|
195
|
-
store_ids = []
|
|
196
|
-
ids = idx if isinstance(idx, list) else [idx]
|
|
197
|
-
for i in ids:
|
|
198
|
-
store_id = self.get_by_tab_idx(i)
|
|
199
|
-
if store_id is not None:
|
|
200
|
-
store_ids.append(store_id)
|
|
201
|
-
self.refresh_by_store_id(store_ids)
|
|
202
|
-
|
|
203
|
-
def refresh_by_store_id(self, store_id: Union[str, list]):
|
|
204
|
-
"""
|
|
205
|
-
Refresh store by ID
|
|
206
|
-
|
|
207
|
-
:param store_id: store id
|
|
208
|
-
"""
|
|
209
|
-
ids = store_id if isinstance(store_id, list) else [store_id]
|
|
210
|
-
updated = False
|
|
211
|
-
is_current = False
|
|
212
|
-
for store_id in ids:
|
|
213
|
-
if store_id is not None and store_id in self.window.core.remote_store.openai.items:
|
|
214
|
-
store = self.window.core.remote_store.openai.items[store_id]
|
|
215
|
-
if store is not None:
|
|
216
|
-
self.window.update_status(trans('status.sending'))
|
|
217
|
-
QApplication.processEvents()
|
|
218
|
-
self.refresh_store(store)
|
|
219
|
-
updated = True
|
|
220
|
-
if self.current == store_id:
|
|
221
|
-
is_current = True
|
|
222
|
-
if updated:
|
|
223
|
-
self.window.update_status(trans('status.assistant.saved'))
|
|
224
|
-
self.update()
|
|
225
|
-
if is_current:
|
|
226
|
-
self.update_files_list()
|
|
227
|
-
|
|
228
|
-
def update_current(self):
|
|
229
|
-
"""Update current store"""
|
|
230
|
-
if self.current is not None and self.window.core.remote_store.openai.has(self.current):
|
|
231
|
-
store = self.window.core.remote_store.openai.items[self.current]
|
|
232
|
-
# update textarea
|
|
233
|
-
option = copy.deepcopy(self.get_option("status"))
|
|
234
|
-
option["value"] = json.dumps(store.status, indent=4)
|
|
235
|
-
self.window.controller.config.apply(self.id, "status", option)
|
|
236
|
-
|
|
237
|
-
# update name
|
|
238
|
-
option = copy.deepcopy(self.get_option("name"))
|
|
239
|
-
option["value"] = store.name
|
|
240
|
-
self.window.controller.config.apply(self.id, "name", option)
|
|
241
|
-
|
|
242
|
-
# update expire days
|
|
243
|
-
option = copy.deepcopy(self.get_option("expire_days"))
|
|
244
|
-
option["value"] = store.expire_days
|
|
245
|
-
self.window.controller.config.apply(self.id, "expire_days", option)
|
|
246
|
-
|
|
247
|
-
def save_btn(self):
|
|
248
|
-
"""Save vector store editor and close dialog"""
|
|
249
|
-
self.window.update_status("Saving...")
|
|
250
|
-
self.save()
|
|
251
|
-
self.refresh_status()
|
|
252
|
-
self.window.update_status("Saved.")
|
|
253
|
-
|
|
254
|
-
def save(self, persist: bool = True):
|
|
255
|
-
"""
|
|
256
|
-
Save vector store editor
|
|
257
|
-
|
|
258
|
-
:param persist: persist to file and close dialog
|
|
259
|
-
"""
|
|
260
|
-
if self.current is not None:
|
|
261
|
-
current = self.window.core.remote_store.openai.items[self.current].to_dict()
|
|
262
|
-
options = copy.deepcopy(self.get_options()) # copy options
|
|
263
|
-
data_dict = {}
|
|
264
|
-
for key in options:
|
|
265
|
-
if key == "status":
|
|
266
|
-
data_dict[key] = current[key] # use initial value
|
|
267
|
-
continue # skip status
|
|
268
|
-
value = self.window.controller.config.get_value(
|
|
269
|
-
parent_id="remote_store.openai",
|
|
270
|
-
key=key,
|
|
271
|
-
option=options[key],
|
|
272
|
-
)
|
|
273
|
-
data_dict[key] = value
|
|
274
|
-
self.window.core.remote_store.openai.items[self.current].from_dict(data_dict)
|
|
275
|
-
|
|
276
|
-
# save config
|
|
277
|
-
if persist:
|
|
278
|
-
self.window.update_status(trans('status.sending'))
|
|
279
|
-
QApplication.processEvents()
|
|
280
|
-
if self.current is not None:
|
|
281
|
-
store = self.window.core.remote_store.openai.update(
|
|
282
|
-
self.window.core.remote_store.openai.items[self.current]
|
|
283
|
-
)
|
|
284
|
-
if store is None:
|
|
285
|
-
self.window.update_status(trans('status.error'))
|
|
286
|
-
self.window.ui.dialogs.alert("Failed to save vector store")
|
|
287
|
-
return
|
|
288
|
-
|
|
289
|
-
self.update() # update stores list in assistant dialog
|
|
290
|
-
self.window.update_status(trans("info.settings.saved"))
|
|
291
|
-
self.restore_selection()
|
|
292
|
-
self.update_files_list()
|
|
293
|
-
|
|
294
|
-
def reload_items(self):
|
|
295
|
-
"""Reload list items"""
|
|
296
|
-
items = self.window.core.remote_store.openai.items
|
|
297
|
-
self.window.remote_store_openai.update_list("remote_store.openai.list", items)
|
|
298
|
-
self.restore_selection()
|
|
299
|
-
|
|
300
|
-
def restore_selection(self):
|
|
301
|
-
"""Restore selection"""
|
|
302
|
-
if self.current is not None:
|
|
303
|
-
idx = self.get_tab_by_id(self.current)
|
|
304
|
-
if idx is not None:
|
|
305
|
-
self.set_by_tab(idx)
|
|
306
|
-
|
|
307
|
-
def select(self, idx: int):
|
|
308
|
-
"""
|
|
309
|
-
Select store by idx
|
|
310
|
-
|
|
311
|
-
:param idx: idx on list
|
|
312
|
-
"""
|
|
313
|
-
self.save(persist=False)
|
|
314
|
-
self.current = self.get_by_tab_idx(idx)
|
|
315
|
-
self.init()
|
|
316
|
-
self.update_files_list()
|
|
317
|
-
|
|
318
|
-
def new(self):
|
|
319
|
-
"""Create new vector store"""
|
|
320
|
-
self.window.update_status(trans('status.sending'))
|
|
321
|
-
QApplication.processEvents()
|
|
322
|
-
|
|
323
|
-
store = self.window.core.remote_store.openai.create()
|
|
324
|
-
if store is None:
|
|
325
|
-
self.window.update_status(trans('status.error'))
|
|
326
|
-
self.window.ui.dialogs.alert("Failed to create new vector store")
|
|
327
|
-
return
|
|
328
|
-
|
|
329
|
-
self.window.update_status(trans('status.assistant.saved'))
|
|
330
|
-
|
|
331
|
-
self.window.core.remote_store.openai.update(store)
|
|
332
|
-
self.update() # update stores list in assistant dialog
|
|
333
|
-
|
|
334
|
-
# switch to created store
|
|
335
|
-
self.current = store.id
|
|
336
|
-
idx = self.get_tab_by_id(self.current)
|
|
337
|
-
self.set_by_tab(idx)
|
|
338
|
-
self.init()
|
|
339
|
-
self.restore_selection()
|
|
340
|
-
self.refresh_by_store_id(store.id)
|
|
341
|
-
self.update_files_list()
|
|
342
|
-
|
|
343
|
-
def delete_by_idx(
|
|
344
|
-
self,
|
|
345
|
-
idx: Union[int, list],
|
|
346
|
-
force: bool = False
|
|
347
|
-
):
|
|
348
|
-
"""
|
|
349
|
-
Delete store by idx
|
|
350
|
-
|
|
351
|
-
:param idx: store idx or list of idxs
|
|
352
|
-
:param force: force delete
|
|
353
|
-
"""
|
|
354
|
-
store_ids = []
|
|
355
|
-
ids = idx if isinstance(idx, list) else [idx]
|
|
356
|
-
for i in ids:
|
|
357
|
-
store_id = self.get_by_tab_idx(i)
|
|
358
|
-
if store_id is not None:
|
|
359
|
-
store_ids.append(store_id)
|
|
360
|
-
self.delete(store_ids, force=force)
|
|
361
|
-
|
|
362
|
-
def delete(
|
|
363
|
-
self,
|
|
364
|
-
store_id: Optional[Union[str, list]] = None,
|
|
365
|
-
force: bool = False
|
|
366
|
-
):
|
|
367
|
-
"""
|
|
368
|
-
Delete store by idx
|
|
369
|
-
|
|
370
|
-
:param store_id: store id or list of store ids
|
|
371
|
-
:param force: force delete
|
|
372
|
-
"""
|
|
373
|
-
if not force:
|
|
374
|
-
self.window.ui.dialogs.confirm(
|
|
375
|
-
type="remote_store.openai.delete",
|
|
376
|
-
id=store_id,
|
|
377
|
-
msg=trans("dialog.remote_store.delete.confirm"),
|
|
378
|
-
)
|
|
379
|
-
return
|
|
380
|
-
|
|
381
|
-
if store_id is None:
|
|
382
|
-
self.window.ui.dialogs.alert("Please select vector store first.")
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
self.window.update_status(trans('status.sending'))
|
|
386
|
-
updated = False
|
|
387
|
-
QApplication.processEvents()
|
|
388
|
-
ids = store_id if isinstance(store_id, list) else [store_id]
|
|
389
|
-
for store_id in ids:
|
|
390
|
-
if self.current == store_id:
|
|
391
|
-
self.current = None
|
|
392
|
-
try:
|
|
393
|
-
print("Deleting store: {}".format(store_id))
|
|
394
|
-
if self.window.core.remote_store.openai.delete(store_id):
|
|
395
|
-
self.window.controller.assistant.batch.remove_store_from_assistants(store_id)
|
|
396
|
-
self.window.update_status(trans('status.deleted'))
|
|
397
|
-
self.window.core.remote_store.openai.save()
|
|
398
|
-
updated = True
|
|
399
|
-
else:
|
|
400
|
-
self.window.update_status(trans('status.error'))
|
|
401
|
-
except Exception as e:
|
|
402
|
-
self.window.update_status(trans('status.error'))
|
|
403
|
-
self.window.ui.dialogs.alert(e)
|
|
404
|
-
if updated:
|
|
405
|
-
self.window.controller.assistant.files.update()
|
|
406
|
-
self.update() # update stores list in assistant dialog
|
|
407
|
-
self.init()
|
|
408
|
-
self.restore_selection()
|
|
409
|
-
self.update_files_list()
|
|
410
|
-
|
|
411
|
-
def set_by_tab(self, idx: int):
|
|
412
|
-
"""
|
|
413
|
-
Set current list by tab index
|
|
414
|
-
|
|
415
|
-
:param idx: tab index
|
|
416
|
-
"""
|
|
417
|
-
store_idx = 0
|
|
418
|
-
for id in self.window.core.remote_store.openai.get_ids():
|
|
419
|
-
if self.window.core.remote_store.openai.is_hidden(id):
|
|
420
|
-
continue
|
|
421
|
-
if store_idx == idx:
|
|
422
|
-
self.current = id
|
|
423
|
-
break
|
|
424
|
-
store_idx += 1
|
|
425
|
-
current = self.window.ui.models['remote_store.openai.list'].index(idx, 0)
|
|
426
|
-
self.window.ui.nodes['remote_store.openai.list'].setCurrentIndex(current)
|
|
427
|
-
|
|
428
|
-
def set_tab_by_id(self, store_id: str):
|
|
429
|
-
"""
|
|
430
|
-
Set current list to id
|
|
431
|
-
|
|
432
|
-
:param store_id: store id
|
|
433
|
-
"""
|
|
434
|
-
idx = self.get_tab_idx(store_id)
|
|
435
|
-
current = self.window.ui.models['remote_store.openai.list'].index(idx, 0)
|
|
436
|
-
self.window.ui.nodes['remote_store.openai.list'].setCurrentIndex(current)
|
|
437
|
-
|
|
438
|
-
def get_tab_idx(self, store_id: str) -> int:
|
|
439
|
-
"""
|
|
440
|
-
Get list index (including hidden)
|
|
441
|
-
|
|
442
|
-
:param store_id: model id
|
|
443
|
-
:return: list index
|
|
444
|
-
"""
|
|
445
|
-
store_idx = None
|
|
446
|
-
i = 0
|
|
447
|
-
for id in self.window.core.remote_store.openai.get_ids():
|
|
448
|
-
if self.window.core.remote_store.openai.is_hidden(id):
|
|
449
|
-
continue
|
|
450
|
-
if id == store_id:
|
|
451
|
-
store_idx = i
|
|
452
|
-
break
|
|
453
|
-
i += 1
|
|
454
|
-
return store_idx
|
|
455
|
-
|
|
456
|
-
def get_tab_by_id(self, store_id: str) -> int:
|
|
457
|
-
"""
|
|
458
|
-
Get list index (including hidden)
|
|
459
|
-
|
|
460
|
-
:param store_id: store id
|
|
461
|
-
:return: list index
|
|
462
|
-
"""
|
|
463
|
-
idx = None
|
|
464
|
-
i = 0
|
|
465
|
-
for id in self.window.core.remote_store.openai.get_ids():
|
|
466
|
-
if self.window.core.remote_store.openai.is_hidden(id):
|
|
467
|
-
continue
|
|
468
|
-
if id == store_id:
|
|
469
|
-
idx = i
|
|
470
|
-
break
|
|
471
|
-
i += 1
|
|
472
|
-
return idx
|
|
473
|
-
|
|
474
|
-
def get_by_tab_idx(self, idx: int) -> Optional[str]:
|
|
475
|
-
"""
|
|
476
|
-
Get key by list index (including hidden)
|
|
477
|
-
|
|
478
|
-
:param idx: list index
|
|
479
|
-
:return: store id / key
|
|
480
|
-
"""
|
|
481
|
-
store_idx = 0
|
|
482
|
-
for id in self.window.core.remote_store.openai.get_ids():
|
|
483
|
-
if self.window.core.remote_store.openai.is_hidden(id):
|
|
484
|
-
continue
|
|
485
|
-
if store_idx == idx:
|
|
486
|
-
return id
|
|
487
|
-
store_idx += 1
|
|
488
|
-
return None
|
|
489
|
-
|
|
490
|
-
def get_first_visible(self) -> Optional[str]:
|
|
491
|
-
"""
|
|
492
|
-
Get first visible store ID (including hidden)
|
|
493
|
-
|
|
494
|
-
:return: store id
|
|
495
|
-
"""
|
|
496
|
-
for id in self.window.core.remote_store.openai.get_ids():
|
|
497
|
-
if not self.window.core.remote_store.openai.is_hidden(id):
|
|
498
|
-
return id
|
|
499
|
-
return None
|
|
500
|
-
|
|
501
|
-
def open_by_idx(self, idx: int):
|
|
502
|
-
"""
|
|
503
|
-
Open editor by tab index
|
|
504
|
-
|
|
505
|
-
:param idx: list index
|
|
506
|
-
"""
|
|
507
|
-
store = self.window.core.remote_store.openai.get_by_idx(idx)
|
|
508
|
-
if store is None:
|
|
509
|
-
return
|
|
510
|
-
self.current = store
|
|
511
|
-
self.open(force=True)
|
|
512
|
-
|
|
513
|
-
def update(self):
|
|
514
|
-
"""Update vector store editor"""
|
|
515
|
-
self.reload_items()
|
|
516
|
-
self.window.controller.assistant.editor.update_store_list() # update stores list in assistant dialog
|
|
517
|
-
self.update_files_list()
|
|
518
|
-
|
|
519
|
-
def set_hide_thread(self, state: bool):
|
|
520
|
-
"""
|
|
521
|
-
Toggle show thread stores
|
|
522
|
-
|
|
523
|
-
:param state: state
|
|
524
|
-
"""
|
|
525
|
-
self.window.core.config.set("remote_store.openai.hide_threads", state)
|
|
526
|
-
self.update()
|
|
527
|
-
|
|
528
|
-
# ==================== Files ====================
|
|
529
|
-
|
|
530
|
-
def update_files_list(self):
|
|
531
|
-
"""
|
|
532
|
-
Update files list view for the current store based on local DB.
|
|
533
|
-
This method does not hit the API; it reflects local state.
|
|
534
|
-
"""
|
|
535
|
-
model_id = 'remote_store.openai.files.list'
|
|
536
|
-
if 'remote_store.openai.files.list' not in self.window.ui.models:
|
|
537
|
-
return # files panel not initialized yet
|
|
538
|
-
model = self.window.ui.models[model_id]
|
|
539
|
-
try:
|
|
540
|
-
model.removeRows(0, model.rowCount())
|
|
541
|
-
except Exception:
|
|
542
|
-
pass
|
|
543
|
-
|
|
544
|
-
self._files_row_to_id = []
|
|
545
|
-
|
|
546
|
-
if self.current is None:
|
|
547
|
-
return
|
|
548
|
-
|
|
549
|
-
files_db = self.window.core.remote_store.openai.files
|
|
550
|
-
if files_db is None:
|
|
551
|
-
return
|
|
552
|
-
|
|
553
|
-
# Resolve store files collection from DB
|
|
554
|
-
try:
|
|
555
|
-
store_files = files_db.get_by_store_or_thread(self.current, None) or {}
|
|
556
|
-
except Exception as e:
|
|
557
|
-
self.window.core.debug.log(e)
|
|
558
|
-
store_files = {}
|
|
559
|
-
|
|
560
|
-
i = 0
|
|
561
|
-
for file_id, file_obj in store_files.items():
|
|
562
|
-
if isinstance(file_obj, dict):
|
|
563
|
-
data = file_obj
|
|
564
|
-
else:
|
|
565
|
-
data = {}
|
|
566
|
-
for key in ('id', 'file_id', 'name', 'filename', 'bytes', 'size', 'usage_bytes', 'status'):
|
|
567
|
-
try:
|
|
568
|
-
if hasattr(file_obj, key):
|
|
569
|
-
data[key] = getattr(file_obj, key)
|
|
570
|
-
except Exception:
|
|
571
|
-
pass
|
|
572
|
-
if not data and hasattr(file_obj, 'to_dict'):
|
|
573
|
-
try:
|
|
574
|
-
data = file_obj.to_dict()
|
|
575
|
-
except Exception:
|
|
576
|
-
data = {}
|
|
577
|
-
|
|
578
|
-
# Choose display name
|
|
579
|
-
name = data.get('filename') or data.get('name') or file_id
|
|
580
|
-
# Choose size
|
|
581
|
-
size_val = None
|
|
582
|
-
for k in ('bytes', 'size', 'usage_bytes'):
|
|
583
|
-
if data.get(k) is not None:
|
|
584
|
-
size_val = data.get(k)
|
|
585
|
-
break
|
|
586
|
-
|
|
587
|
-
# Human-readable size if possible
|
|
588
|
-
size_txt = ""
|
|
589
|
-
try:
|
|
590
|
-
if size_val:
|
|
591
|
-
size_txt = self.window.core.filesystem.sizeof_fmt(int(size_val))
|
|
592
|
-
except Exception:
|
|
593
|
-
pass
|
|
594
|
-
|
|
595
|
-
extra = []
|
|
596
|
-
if size_txt:
|
|
597
|
-
extra.append(size_txt)
|
|
598
|
-
if data.get('status'):
|
|
599
|
-
extra.append(str(data.get('status')))
|
|
600
|
-
label = name
|
|
601
|
-
if extra:
|
|
602
|
-
label += " ({})".format(", ".join(extra))
|
|
603
|
-
|
|
604
|
-
item = QStandardItem(label)
|
|
605
|
-
item.setEditable(False)
|
|
606
|
-
item.setData(file_id, Qt.UserRole)
|
|
607
|
-
model.setItem(i, 0, item)
|
|
608
|
-
self._files_row_to_id.append(data['file_id'] if 'file_id' in data else file_id)
|
|
609
|
-
i += 1
|
|
610
|
-
|
|
611
|
-
def delete_file_by_idx(self, idx: int, force: bool = False):
|
|
612
|
-
"""
|
|
613
|
-
Delete a single file from the current store by row index in files list.
|
|
614
|
-
This uses API to remove the file from a remote store, then triggers async re-import
|
|
615
|
-
of files for the current store to keep the local DB in sync.
|
|
616
|
-
|
|
617
|
-
:param idx: row index in files list
|
|
618
|
-
:param force: force delete without confirmation
|
|
619
|
-
"""
|
|
620
|
-
if self.current is None:
|
|
621
|
-
self.window.ui.dialogs.alert("Please select vector store first.")
|
|
622
|
-
return
|
|
623
|
-
|
|
624
|
-
if not force:
|
|
625
|
-
self.window.ui.dialogs.confirm(
|
|
626
|
-
type='remote_store.openai.file.delete',
|
|
627
|
-
id=idx,
|
|
628
|
-
msg=trans('confirm.remote_store.file.delete'),
|
|
629
|
-
)
|
|
630
|
-
return
|
|
631
|
-
|
|
632
|
-
model_id = 'remote_store.openai.files.list'
|
|
633
|
-
if model_id not in self.window.ui.models:
|
|
634
|
-
return
|
|
635
|
-
if idx < 0 or idx >= len(self._files_row_to_id):
|
|
636
|
-
return
|
|
637
|
-
|
|
638
|
-
file_id = self._files_row_to_id[idx]
|
|
639
|
-
if not file_id:
|
|
640
|
-
return
|
|
641
|
-
|
|
642
|
-
# Update UI state
|
|
643
|
-
self.window.update_status(trans('status.sending'))
|
|
644
|
-
QApplication.processEvents()
|
|
645
|
-
|
|
646
|
-
try:
|
|
647
|
-
api = self.window.core.api.openai.store
|
|
648
|
-
removed = False
|
|
649
|
-
|
|
650
|
-
# Prefer store-scoped removal if available
|
|
651
|
-
if hasattr(api, 'remove_store_file'):
|
|
652
|
-
try:
|
|
653
|
-
api.remove_store_file(self.current, file_id)
|
|
654
|
-
removed = True
|
|
655
|
-
except Exception as e:
|
|
656
|
-
self.window.core.debug.log(e)
|
|
657
|
-
|
|
658
|
-
# Fallback: remove by file_id only
|
|
659
|
-
if not removed and hasattr(api, 'remove_file'):
|
|
660
|
-
try:
|
|
661
|
-
api.remove_file(file_id)
|
|
662
|
-
removed = True
|
|
663
|
-
except Exception as e:
|
|
664
|
-
self.window.core.debug.log(e)
|
|
665
|
-
|
|
666
|
-
if not removed:
|
|
667
|
-
raise RuntimeError("Remove file API not available.")
|
|
668
|
-
|
|
669
|
-
# Remove from local DB
|
|
670
|
-
try:
|
|
671
|
-
self.window.core.remote_store.openai.files.delete_by_file_id(file_id)
|
|
672
|
-
except Exception as e:
|
|
673
|
-
self.window.core.debug.log(e)
|
|
674
|
-
|
|
675
|
-
# Optimistic UI update: remove row from the model immediately
|
|
676
|
-
try:
|
|
677
|
-
self.window.ui.models[model_id].removeRow(idx)
|
|
678
|
-
# also update index map
|
|
679
|
-
try:
|
|
680
|
-
del self._files_row_to_id[idx]
|
|
681
|
-
except Exception:
|
|
682
|
-
pass
|
|
683
|
-
except Exception:
|
|
684
|
-
pass
|
|
685
|
-
|
|
686
|
-
# Trigger re-import for the current store to refresh local DB and UI elsewhere
|
|
687
|
-
try:
|
|
688
|
-
self.window.update_status("Refreshing status...")
|
|
689
|
-
QTimer.singleShot(1000, lambda: self.window.controller.remote_store.openai.refresh_status())
|
|
690
|
-
except Exception as e:
|
|
691
|
-
self.window.core.debug.log(e)
|
|
692
|
-
|
|
693
|
-
self.window.update_status(trans('status.deleted'))
|
|
694
|
-
|
|
695
|
-
except Exception as e:
|
|
696
|
-
self.window.update_status(trans('status.error'))
|
|
697
|
-
self.window.ui.dialogs.alert("Failed to delete file: {}".format(e))
|
|
698
|
-
self.window.core.debug.log(e)
|
|
699
|
-
self.update_files_list()
|