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.
- pygpt_net/CHANGELOG.txt +12 -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/realtime/realtime.py +13 -1
- 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 +10 -6
- pygpt_net/data/config/models.json +38 -22
- 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/realtime/client.py +70 -24
- pygpt_net/provider/api/google/realtime/realtime.py +48 -12
- 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/realtime/realtime.py +26 -6
- 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 +27 -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 +1864 -0
- pygpt_net/provider/api/x_ai/realtime/realtime.py +213 -0
- pygpt_net/provider/api/x_ai/remote_tools.py +102 -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/tools.py +51 -0
- 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 +29 -3
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
- pygpt_net/provider/core/model/patch.py +49 -1
- 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.9.dist-info}/METADATA +14 -2
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/RECORD +87 -75
- 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.9.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/entry_points.txt +0 -0
|
@@ -6,30 +6,999 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2026.01.06 18:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
import copy
|
|
13
|
+
import json
|
|
14
|
+
from typing import Optional, Union, Any, List
|
|
15
|
+
|
|
16
|
+
from PySide6.QtCore import QTimer
|
|
17
|
+
from PySide6.QtWidgets import QApplication, QMessageBox
|
|
18
|
+
from PySide6.QtGui import QStandardItem
|
|
19
|
+
|
|
20
|
+
from pygpt_net.item.store import RemoteStoreItem
|
|
21
|
+
from pygpt_net.utils import trans
|
|
22
|
+
|
|
23
|
+
from .batch import Batch
|
|
24
|
+
|
|
14
25
|
|
|
15
26
|
class RemoteStore:
|
|
27
|
+
|
|
28
|
+
DEFAULT_PROVIDER = "openai"
|
|
29
|
+
PROVIDERS = {
|
|
30
|
+
"openai": "OpenAI",
|
|
31
|
+
"google": "Google",
|
|
32
|
+
# "anthropic": "Anthropic", # TODO: enable when SDK fixed
|
|
33
|
+
"xai": "xAI (Collections)",
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
def __init__(self, window=None):
|
|
17
37
|
"""
|
|
18
|
-
|
|
38
|
+
Unified Remote Store controller
|
|
19
39
|
|
|
20
40
|
:param window: Window instance
|
|
21
41
|
"""
|
|
22
42
|
self.window = window
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
|
|
44
|
+
# Unified UI state
|
|
45
|
+
self.dialog = False
|
|
46
|
+
self.config_initialized = False
|
|
47
|
+
self.current: Optional[str] = None
|
|
48
|
+
self.width = 900
|
|
49
|
+
self.height = 560
|
|
50
|
+
self.id = "remote_store"
|
|
51
|
+
self.initialized = False
|
|
52
|
+
|
|
53
|
+
# Active provider: 'google' | 'openai' | 'anthropic' | 'xai'
|
|
54
|
+
self.provider_key: Optional[str] = None
|
|
55
|
+
|
|
56
|
+
# Mapping row -> ids for current stores/files list
|
|
57
|
+
self._stores_row_to_id: List[str] = []
|
|
58
|
+
self._files_row_to_id: List[str] = []
|
|
59
|
+
|
|
60
|
+
# Upload queue
|
|
61
|
+
self.files_to_upload: List[str] = []
|
|
62
|
+
|
|
63
|
+
# Unified options schema; UI hides provider-specific fields
|
|
64
|
+
self.options = {
|
|
65
|
+
"id": {
|
|
66
|
+
"type": "text",
|
|
67
|
+
"label": "remote_store.id",
|
|
68
|
+
"description": "remote_store.id.description",
|
|
69
|
+
"read_only": True,
|
|
70
|
+
"value": "",
|
|
71
|
+
},
|
|
72
|
+
"name": {
|
|
73
|
+
"type": "text",
|
|
74
|
+
"label": "remote_store.name",
|
|
75
|
+
"value": "",
|
|
76
|
+
},
|
|
77
|
+
"expire_days": {
|
|
78
|
+
"type": "int",
|
|
79
|
+
"label": "remote_store.expire_days",
|
|
80
|
+
"value": 0, # OpenAI only; hidden for Google/Anthropic/xAI
|
|
81
|
+
"advanced": False,
|
|
82
|
+
},
|
|
83
|
+
"status": {
|
|
84
|
+
"type": "textarea",
|
|
85
|
+
"label": "remote_store.status",
|
|
86
|
+
"read_only": True,
|
|
87
|
+
"value": "",
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
self.batch = Batch(self)
|
|
92
|
+
|
|
93
|
+
# ======================== Provider helpers ========================
|
|
94
|
+
|
|
95
|
+
def get_providers(self) -> dict:
|
|
96
|
+
"""Return available providers."""
|
|
97
|
+
return self.PROVIDERS
|
|
98
|
+
|
|
99
|
+
def get_provider_keys(self) -> List[str]:
|
|
100
|
+
"""Return available provider keys."""
|
|
101
|
+
return list(self.PROVIDERS.keys())
|
|
102
|
+
|
|
103
|
+
def _get_provider(self) -> str:
|
|
104
|
+
"""Return current provider key."""
|
|
105
|
+
if self.provider_key in self.get_provider_keys():
|
|
106
|
+
return self.provider_key
|
|
107
|
+
key = self.window.core.config.get("remote_store.provider")
|
|
108
|
+
if key not in self.get_provider_keys():
|
|
109
|
+
key = self.DEFAULT_PROVIDER
|
|
110
|
+
try:
|
|
111
|
+
self.window.core.config.set("remote_store.provider", key)
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
self.provider_key = key
|
|
115
|
+
return key
|
|
116
|
+
|
|
117
|
+
def set_provider(self, key: str):
|
|
118
|
+
"""Set active provider and re-init UI."""
|
|
119
|
+
if key not in self.get_provider_keys():
|
|
120
|
+
return
|
|
121
|
+
self.provider_key = key
|
|
122
|
+
try:
|
|
123
|
+
self.window.core.config.set("remote_store.provider", key)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
# Force selecting the first visible store for the new provider and refresh files list
|
|
127
|
+
self.init(select_first=True)
|
|
128
|
+
|
|
129
|
+
def _core_for(self, provider: Optional[str] = None):
|
|
130
|
+
key = provider or self._get_provider()
|
|
131
|
+
if hasattr(self.window.core.remote_store, key):
|
|
132
|
+
return getattr(self.window.core.remote_store, key)
|
|
133
|
+
|
|
134
|
+
def _files_core_for(self, provider: Optional[str] = None):
|
|
135
|
+
return self._core_for(provider).files
|
|
136
|
+
|
|
137
|
+
def _api_store_for(self, provider: Optional[str] = None):
|
|
138
|
+
key = provider or self._get_provider()
|
|
139
|
+
if hasattr(self.window.core.api, key):
|
|
140
|
+
return getattr(self.window.core.api, key).store
|
|
141
|
+
|
|
142
|
+
# ======================== Provider hooks ========================
|
|
143
|
+
|
|
144
|
+
def after_create(self, provider: str, store: RemoteStoreItem):
|
|
145
|
+
"""Hook: called after store creation (per provider)."""
|
|
146
|
+
if provider == "openai":
|
|
147
|
+
self.window.controller.assistant.editor.update_store_list() # update stores list in assistant dialog
|
|
148
|
+
|
|
149
|
+
def after_delete(self, provider: str, store_id: str):
|
|
150
|
+
"""Hook: called after store deletion (per provider)."""
|
|
151
|
+
if provider == "openai":
|
|
152
|
+
self.window.controller.assistant.editor.update_store_list() # update stores list in assistant dialog
|
|
153
|
+
|
|
154
|
+
def after_update(self, provider: str, store: RemoteStoreItem):
|
|
155
|
+
"""Hook: called after store update (per provider)."""
|
|
156
|
+
if provider == "openai":
|
|
157
|
+
self.window.controller.assistant.editor.update_store_list()
|
|
158
|
+
|
|
159
|
+
def after_truncated_stores(self, provider: str):
|
|
160
|
+
"""Hook: called after truncating all stores (per provider)."""
|
|
161
|
+
if provider == "openai":
|
|
162
|
+
try:
|
|
163
|
+
self.window.controller.assistant.batch.remove_all_stores_from_assistants()
|
|
164
|
+
self.window.controller.assistant.editor.update_store_list() # update stores list in assistant dialog
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
def after_imported_stores(self, provider: str):
|
|
169
|
+
"""Hook: called after importing stores (per provider)."""
|
|
170
|
+
if provider == "openai":
|
|
171
|
+
try:
|
|
172
|
+
self.window.controller.assistant.files.update()
|
|
173
|
+
self.window.controller.assistant.editor.update_store_list() # update stores list in assistant dialog
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
# ======================== Options ========================
|
|
178
|
+
|
|
179
|
+
def get_options(self) -> dict:
|
|
180
|
+
return self.options
|
|
181
|
+
|
|
182
|
+
def get_option(self, key: str) -> Optional[dict]:
|
|
183
|
+
if key in self.options:
|
|
184
|
+
return self.options[key]
|
|
185
|
+
|
|
186
|
+
# ======================== Lifecycle ========================
|
|
25
187
|
|
|
26
188
|
def setup(self):
|
|
27
|
-
"""Setup
|
|
28
|
-
|
|
29
|
-
|
|
189
|
+
"""Setup caches and build the unified dialog."""
|
|
190
|
+
try:
|
|
191
|
+
self.window.core.remote_store.openai.load_all()
|
|
192
|
+
self.window.core.remote_store.google.load_all()
|
|
193
|
+
self.window.core.remote_store.anthropic.load_all()
|
|
194
|
+
self.window.core.remote_store.xai.load_all()
|
|
195
|
+
except Exception as e:
|
|
196
|
+
self.window.core.debug.log(e)
|
|
30
197
|
|
|
31
198
|
def reload(self):
|
|
32
|
-
"""Reload
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
199
|
+
"""Reload provider data and refresh unified view."""
|
|
200
|
+
try:
|
|
201
|
+
self.window.core.remote_store.openai.load_all()
|
|
202
|
+
self.window.core.remote_store.google.load_all()
|
|
203
|
+
self.window.core.remote_store.anthropic.load_all()
|
|
204
|
+
self.window.core.remote_store.xai.load_all()
|
|
205
|
+
except Exception as e:
|
|
206
|
+
self.window.core.debug.log(e)
|
|
207
|
+
self.reset()
|
|
208
|
+
|
|
209
|
+
def reset(self):
|
|
210
|
+
"""Reset current selection."""
|
|
211
|
+
self.current = None
|
|
212
|
+
if self.dialog:
|
|
213
|
+
self.init()
|
|
214
|
+
|
|
215
|
+
def toggle_editor(self, provider: Optional[str] = None):
|
|
216
|
+
"""Toggle unified dialog."""
|
|
217
|
+
if provider is not None:
|
|
218
|
+
self.set_provider(provider)
|
|
219
|
+
if self.dialog:
|
|
220
|
+
self.close()
|
|
221
|
+
else:
|
|
222
|
+
self.open()
|
|
223
|
+
|
|
224
|
+
def open(self, force: bool = False):
|
|
225
|
+
"""Open dialog."""
|
|
226
|
+
if not self.config_initialized:
|
|
227
|
+
self.setup()
|
|
228
|
+
self.config_initialized = True
|
|
229
|
+
if not self.dialog or force:
|
|
230
|
+
# Force selecting the first visible store on dialog open
|
|
231
|
+
self.init(select_first=True)
|
|
232
|
+
self.window.ui.dialogs.open(
|
|
233
|
+
"remote_store",
|
|
234
|
+
width=self.width,
|
|
235
|
+
height=self.height,
|
|
236
|
+
)
|
|
237
|
+
self.dialog = True
|
|
238
|
+
|
|
239
|
+
def close(self):
|
|
240
|
+
"""Close dialog."""
|
|
241
|
+
if self.dialog:
|
|
242
|
+
self.window.ui.dialogs.close("remote_store")
|
|
243
|
+
self.dialog = False
|
|
244
|
+
|
|
245
|
+
# ======================== Initialize / Refresh ========================
|
|
246
|
+
|
|
247
|
+
def init(self, select_first: bool = False):
|
|
248
|
+
"""Initialize UI state for the active provider."""
|
|
249
|
+
if not self.initialized:
|
|
250
|
+
# Setup dialog UI
|
|
251
|
+
self.window.remote_store.setup()
|
|
252
|
+
|
|
253
|
+
# Ensure provider combobox is synced at first load
|
|
254
|
+
try:
|
|
255
|
+
self.window.remote_store.set_provider_in_ui(self._get_provider())
|
|
256
|
+
except Exception:
|
|
257
|
+
pass
|
|
258
|
+
self.initialized = True
|
|
259
|
+
|
|
260
|
+
self.reload_items()
|
|
261
|
+
|
|
262
|
+
# Ensure a valid selection, optionally forcing the first visible store
|
|
263
|
+
core = self._core_for()
|
|
264
|
+
if select_first:
|
|
265
|
+
# Reset current to enforce picking the first visible store below
|
|
266
|
+
self.current = None
|
|
267
|
+
if self.current is None or not core.has(self.current):
|
|
268
|
+
self.current = self.get_first_visible()
|
|
269
|
+
|
|
270
|
+
options = copy.deepcopy(self.get_options())
|
|
271
|
+
if self.current is not None and core.has(self.current):
|
|
272
|
+
store = core.items[self.current]
|
|
273
|
+
data_dict = store.to_dict()
|
|
274
|
+
for key in options:
|
|
275
|
+
if key in data_dict:
|
|
276
|
+
value = data_dict[key]
|
|
277
|
+
if key == "status":
|
|
278
|
+
options[key]["value"] = json.dumps(value, indent=4)
|
|
279
|
+
else:
|
|
280
|
+
options[key]["value"] = value
|
|
281
|
+
# Reflect selection in the left list
|
|
282
|
+
self.set_tab_by_id(self.current)
|
|
283
|
+
self.window.controller.config.load_options(self.id, options)
|
|
284
|
+
else:
|
|
285
|
+
self.current = None
|
|
286
|
+
self.window.controller.config.load_options(self.id, options)
|
|
287
|
+
|
|
288
|
+
# Provider-specific visibility
|
|
289
|
+
try:
|
|
290
|
+
self.window.remote_store.sync_provider_dependent_ui(self._get_provider())
|
|
291
|
+
except Exception:
|
|
292
|
+
pass
|
|
293
|
+
|
|
294
|
+
# Always refresh files list on provider/selection changes (also clears when empty)
|
|
295
|
+
self.update_files_list()
|
|
296
|
+
try:
|
|
297
|
+
self.window.remote_store.sync_hide_threads_checkbox(self._get_provider())
|
|
298
|
+
except Exception:
|
|
299
|
+
pass
|
|
300
|
+
|
|
301
|
+
# Update dialog title
|
|
302
|
+
try:
|
|
303
|
+
self.window.remote_store.update_title(self._dialog_title_for_provider(self._get_provider()))
|
|
304
|
+
except Exception:
|
|
305
|
+
pass
|
|
306
|
+
|
|
307
|
+
def refresh_status(self):
|
|
308
|
+
"""Reload current store status from API and refresh UI."""
|
|
309
|
+
if self.current is None:
|
|
310
|
+
return
|
|
311
|
+
core = self._core_for()
|
|
312
|
+
if not core.has(self.current):
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
self.window.update_status(trans('status.sending'))
|
|
316
|
+
QApplication.processEvents()
|
|
317
|
+
try:
|
|
318
|
+
core.update_status(self.current)
|
|
319
|
+
store = core.items[self.current]
|
|
320
|
+
core.update(store)
|
|
321
|
+
self.window.update_status(trans('status.finished'))
|
|
322
|
+
except Exception as e:
|
|
323
|
+
self.window.core.debug.log(e)
|
|
324
|
+
self.window.update_status(trans('status.error'))
|
|
325
|
+
|
|
326
|
+
self.update_current()
|
|
327
|
+
self.update()
|
|
328
|
+
self.update_files_list()
|
|
329
|
+
|
|
330
|
+
def refresh_store(self, store: RemoteStoreItem, update: bool = True, provider: Optional[str] = None):
|
|
331
|
+
"""Refresh a store by item."""
|
|
332
|
+
if provider:
|
|
333
|
+
if self._get_provider() != provider:
|
|
334
|
+
return
|
|
335
|
+
provider = self._get_provider()
|
|
336
|
+
core = self._core_for(provider)
|
|
337
|
+
core.update_status(store.id)
|
|
338
|
+
core.update(store)
|
|
339
|
+
if update and store.id == self.current:
|
|
340
|
+
self.update_current()
|
|
341
|
+
|
|
342
|
+
def refresh_by_idx(self, idx: Union[int, list]):
|
|
343
|
+
"""Refresh store(s) by list index or list of indexes."""
|
|
344
|
+
store_ids = []
|
|
345
|
+
ids = idx if isinstance(idx, list) else [idx]
|
|
346
|
+
for i in ids:
|
|
347
|
+
store_id = self.get_by_tab_idx(i)
|
|
348
|
+
if store_id is not None:
|
|
349
|
+
store_ids.append(store_id)
|
|
350
|
+
self.refresh_by_store_id(store_ids)
|
|
351
|
+
|
|
352
|
+
def refresh_by_store_id(self, store_id: Union[str, list], provider: Optional[str] = None):
|
|
353
|
+
"""Refresh store(s) by ID."""
|
|
354
|
+
if provider:
|
|
355
|
+
if self._get_provider() != provider:
|
|
356
|
+
return
|
|
357
|
+
ids = store_id if isinstance(store_id, list) else [store_id]
|
|
358
|
+
updated = False
|
|
359
|
+
is_current = False
|
|
360
|
+
core = self._core_for()
|
|
361
|
+
for sid in ids:
|
|
362
|
+
if sid is not None and core.has(sid):
|
|
363
|
+
store = core.items[sid]
|
|
364
|
+
if store is not None:
|
|
365
|
+
self.window.update_status(trans('status.sending'))
|
|
366
|
+
QApplication.processEvents()
|
|
367
|
+
self.refresh_store(store)
|
|
368
|
+
updated = True
|
|
369
|
+
if self.current == sid:
|
|
370
|
+
is_current = True
|
|
371
|
+
if updated:
|
|
372
|
+
self.window.update_status(trans('status.finished'))
|
|
373
|
+
self.update()
|
|
374
|
+
if is_current:
|
|
375
|
+
self.update_files_list()
|
|
376
|
+
|
|
377
|
+
def refresh_by_store_id_provider(self, provider: str, store_id: Optional[str]):
|
|
378
|
+
"""Refresh status for a specific provider+store (used by proxies after async)."""
|
|
379
|
+
if store_id is None:
|
|
380
|
+
return
|
|
381
|
+
core = self._core_for(provider)
|
|
382
|
+
if store_id not in core.items:
|
|
383
|
+
return
|
|
384
|
+
store = core.items[store_id]
|
|
385
|
+
try:
|
|
386
|
+
core.update_status(store_id)
|
|
387
|
+
core.update(store)
|
|
388
|
+
except Exception as e:
|
|
389
|
+
self.window.core.debug.log(e)
|
|
390
|
+
|
|
391
|
+
if provider == self._get_provider() and self.current == store_id:
|
|
392
|
+
self.update_current()
|
|
393
|
+
self.update()
|
|
394
|
+
self.update_files_list()
|
|
395
|
+
|
|
396
|
+
# ======================== UI update helpers ========================
|
|
397
|
+
|
|
398
|
+
def update_current(self):
|
|
399
|
+
"""Update current store values in right panel."""
|
|
400
|
+
if self.current is None:
|
|
401
|
+
return
|
|
402
|
+
core = self._core_for()
|
|
403
|
+
if not core.has(self.current):
|
|
404
|
+
return
|
|
405
|
+
store = core.items[self.current]
|
|
406
|
+
|
|
407
|
+
option = copy.deepcopy(self.get_option("status"))
|
|
408
|
+
option["value"] = json.dumps(store.status, indent=4)
|
|
409
|
+
self.window.controller.config.apply(self.id, "status", option)
|
|
410
|
+
|
|
411
|
+
option = copy.deepcopy(self.get_option("name"))
|
|
412
|
+
option["value"] = store.name
|
|
413
|
+
self.window.controller.config.apply(self.id, "name", option)
|
|
414
|
+
|
|
415
|
+
option = copy.deepcopy(self.get_option("id"))
|
|
416
|
+
option["value"] = store.id
|
|
417
|
+
self.window.controller.config.apply(self.id, "id", option)
|
|
418
|
+
|
|
419
|
+
if self._get_provider() == "openai":
|
|
420
|
+
try:
|
|
421
|
+
option = copy.deepcopy(self.get_option("expire_days"))
|
|
422
|
+
option["value"] = int(getattr(store, "expire_days", 0) or 0)
|
|
423
|
+
self.window.controller.config.apply(self.id, "expire_days", option)
|
|
424
|
+
except Exception:
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
def update(self):
|
|
428
|
+
"""Refresh lists and auxiliary widgets."""
|
|
429
|
+
self.reload_items()
|
|
430
|
+
try:
|
|
431
|
+
self.after_update(self._get_provider())
|
|
432
|
+
except Exception:
|
|
433
|
+
pass
|
|
434
|
+
self.update_files_list()
|
|
435
|
+
|
|
436
|
+
def reload_items(self):
|
|
437
|
+
"""Reload left stores list for current provider."""
|
|
438
|
+
core = self._core_for()
|
|
439
|
+
items = core.get_all()
|
|
440
|
+
hide_threads = bool(self._get_hide_threads_config(self._get_provider()))
|
|
441
|
+
suffix = trans("remote_store.files.suffix")
|
|
442
|
+
|
|
443
|
+
pairs: List[tuple[str, str]] = []
|
|
444
|
+
for sid, store in items.items():
|
|
445
|
+
if core.is_hidden(sid) or (hide_threads and (store.name is None or store.name == "")):
|
|
446
|
+
continue
|
|
447
|
+
num_files = store.get_file_count()
|
|
448
|
+
name = store.name or store.id
|
|
449
|
+
extras = []
|
|
450
|
+
if num_files > 0:
|
|
451
|
+
extras.append(f"{num_files} {suffix}")
|
|
452
|
+
if getattr(store, "usage_bytes", 0) > 0:
|
|
453
|
+
try:
|
|
454
|
+
extras.append(self.window.core.filesystem.sizeof_fmt(store.usage_bytes))
|
|
455
|
+
except Exception:
|
|
456
|
+
pass
|
|
457
|
+
if extras:
|
|
458
|
+
name = f"{name} ({' - '.join(extras)})"
|
|
459
|
+
pairs.append((sid, name))
|
|
460
|
+
|
|
461
|
+
self._stores_row_to_id = [sid for sid, _ in pairs]
|
|
462
|
+
try:
|
|
463
|
+
self.window.remote_store.update_list_pairs("remote_store.list", pairs)
|
|
464
|
+
except Exception:
|
|
465
|
+
pass
|
|
466
|
+
|
|
467
|
+
self.restore_selection()
|
|
468
|
+
|
|
469
|
+
def restore_selection(self):
|
|
470
|
+
"""Restore left selection to self.current."""
|
|
471
|
+
if self.current is not None:
|
|
472
|
+
idx = self.get_tab_by_id(self.current)
|
|
473
|
+
if idx is not None:
|
|
474
|
+
self.set_by_tab(idx)
|
|
475
|
+
|
|
476
|
+
# ======================== Selection ========================
|
|
477
|
+
|
|
478
|
+
def select(self, idx: int):
|
|
479
|
+
"""Select store by index in the left list."""
|
|
480
|
+
self.save(persist=False)
|
|
481
|
+
sid = self.get_by_tab_idx(idx)
|
|
482
|
+
if sid is None:
|
|
483
|
+
return
|
|
484
|
+
self.current = sid
|
|
485
|
+
self.init()
|
|
486
|
+
self.update_files_list()
|
|
487
|
+
|
|
488
|
+
def set_by_tab(self, idx: int):
|
|
489
|
+
"""Select row (left list) by index."""
|
|
490
|
+
try:
|
|
491
|
+
self.window.remote_store.set_current_row("remote_store.list", idx)
|
|
492
|
+
except Exception:
|
|
493
|
+
pass
|
|
494
|
+
|
|
495
|
+
def set_tab_by_id(self, store_id: str):
|
|
496
|
+
"""Select row by store id."""
|
|
497
|
+
idx = self.get_tab_idx(store_id)
|
|
498
|
+
if idx is None:
|
|
499
|
+
return
|
|
500
|
+
self.set_by_tab(idx)
|
|
501
|
+
|
|
502
|
+
def get_tab_idx(self, store_id: str) -> Optional[int]:
|
|
503
|
+
"""Return row index for a given store id."""
|
|
504
|
+
try:
|
|
505
|
+
return self._stores_row_to_id.index(store_id)
|
|
506
|
+
except Exception:
|
|
507
|
+
return None
|
|
508
|
+
|
|
509
|
+
def get_tab_by_id(self, store_id: str) -> Optional[int]:
|
|
510
|
+
return self.get_tab_idx(store_id)
|
|
511
|
+
|
|
512
|
+
def get_by_tab_idx(self, idx: int) -> Optional[str]:
|
|
513
|
+
"""Return store id by row index."""
|
|
514
|
+
if idx < 0 or idx >= len(self._stores_row_to_id):
|
|
515
|
+
return None
|
|
516
|
+
return self._stores_row_to_id[idx]
|
|
517
|
+
|
|
518
|
+
def get_first_visible(self) -> Optional[str]:
|
|
519
|
+
"""Return first visible store id for current provider (respects current UI filters)."""
|
|
520
|
+
# Prefer what is actually visible in the left list
|
|
521
|
+
if self._stores_row_to_id:
|
|
522
|
+
return self._stores_row_to_id[0]
|
|
523
|
+
# Fallback to provider core when left list is not initialized
|
|
524
|
+
core = self._core_for()
|
|
525
|
+
for sid in core.get_ids():
|
|
526
|
+
if not core.is_hidden(sid):
|
|
527
|
+
return sid
|
|
528
|
+
return None
|
|
529
|
+
|
|
530
|
+
# ======================== CRUD: stores ========================
|
|
531
|
+
|
|
532
|
+
def save_btn(self):
|
|
533
|
+
"""Save and refresh status."""
|
|
534
|
+
self.window.update_status("Saving...")
|
|
535
|
+
self.save()
|
|
536
|
+
self.refresh_status()
|
|
537
|
+
self.window.update_status("Saved.")
|
|
538
|
+
|
|
539
|
+
def save(self, persist: bool = True):
|
|
540
|
+
"""Persist right panel data to store and remote if supported."""
|
|
541
|
+
if self.current is not None and self._core_for().has(self.current):
|
|
542
|
+
current = self._core_for().items[self.current].to_dict()
|
|
543
|
+
options = copy.deepcopy(self.get_options())
|
|
544
|
+
data_dict = current
|
|
545
|
+
for key in options:
|
|
546
|
+
if key == "status":
|
|
547
|
+
data_dict[key] = current.get("status", {})
|
|
548
|
+
continue
|
|
549
|
+
value = self.window.controller.config.get_value(
|
|
550
|
+
parent_id="remote_store",
|
|
551
|
+
key=key,
|
|
552
|
+
option=options[key],
|
|
553
|
+
)
|
|
554
|
+
data_dict[key] = value
|
|
555
|
+
self._core_for().items[self.current].from_dict(data_dict)
|
|
556
|
+
|
|
557
|
+
if persist:
|
|
558
|
+
self.window.update_status(trans('status.sending'))
|
|
559
|
+
QApplication.processEvents()
|
|
560
|
+
if self.current is not None:
|
|
561
|
+
store = self._core_for().update(self._core_for().items[self.current])
|
|
562
|
+
if store is None:
|
|
563
|
+
self.window.update_status(trans('status.error'))
|
|
564
|
+
self.window.ui.dialogs.alert("Failed to save store")
|
|
565
|
+
return
|
|
566
|
+
self.update()
|
|
567
|
+
self.window.update_status(trans("info.settings.saved"))
|
|
568
|
+
self.restore_selection()
|
|
569
|
+
self.update_files_list()
|
|
570
|
+
|
|
571
|
+
def new(self, name: str = "", force: bool = False):
|
|
572
|
+
"""Create a new store for active provider."""
|
|
573
|
+
if not force:
|
|
574
|
+
self.window.ui.dialog['create'].id = 'remote_store.new'
|
|
575
|
+
self.window.ui.dialog['create'].input.setText("New vector store")
|
|
576
|
+
self.window.ui.dialog['create'].current = "New vector store"
|
|
577
|
+
self.window.ui.dialog['create'].show()
|
|
578
|
+
return
|
|
579
|
+
|
|
580
|
+
self.window.ui.dialog['create'].close()
|
|
581
|
+
self.window.update_status(trans('status.sending'))
|
|
582
|
+
QApplication.processEvents()
|
|
583
|
+
try:
|
|
584
|
+
core = self._core_for()
|
|
585
|
+
display_name = name or "New vector store"
|
|
586
|
+
store = core.create(display_name)
|
|
587
|
+
if store is None:
|
|
588
|
+
raise RuntimeError("Failed to create new store")
|
|
589
|
+
core.update(store)
|
|
590
|
+
|
|
591
|
+
self.after_create(self._get_provider(), store)
|
|
592
|
+
self.window.update_status(trans('status.finished'))
|
|
593
|
+
|
|
594
|
+
self.current = store.id
|
|
595
|
+
self.reload_items()
|
|
596
|
+
self.set_tab_by_id(self.current)
|
|
597
|
+
self.init()
|
|
598
|
+
self.restore_selection()
|
|
599
|
+
self.refresh_by_store_id(store.id)
|
|
600
|
+
self.update_files_list()
|
|
601
|
+
|
|
602
|
+
except Exception as e:
|
|
603
|
+
self.window.core.debug.log(e)
|
|
604
|
+
self.window.ui.dialogs.alert(str(e))
|
|
605
|
+
self.window.update_status(trans('status.error'))
|
|
606
|
+
|
|
607
|
+
def delete_by_idx(self, idx: Union[int, list], force: bool = False):
|
|
608
|
+
"""Delete store(s) by index(es)."""
|
|
609
|
+
store_ids = []
|
|
610
|
+
ids = idx if isinstance(idx, list) else [idx]
|
|
611
|
+
for i in ids:
|
|
612
|
+
store_id = self.get_by_tab_idx(i)
|
|
613
|
+
if store_id is not None:
|
|
614
|
+
store_ids.append(store_id)
|
|
615
|
+
self.delete(store_ids, force)
|
|
616
|
+
|
|
617
|
+
def delete(self, store_id: Optional[Union[str, list]] = None, force: bool = False):
|
|
618
|
+
"""Delete store(s) by id."""
|
|
619
|
+
if store_id is None:
|
|
620
|
+
self.window.ui.dialogs.alert("Please select store first.")
|
|
621
|
+
return
|
|
622
|
+
|
|
623
|
+
if not force:
|
|
624
|
+
t = "remote_store.delete"
|
|
625
|
+
self.window.ui.dialogs.confirm(
|
|
626
|
+
type=t,
|
|
627
|
+
id=store_id,
|
|
628
|
+
msg=trans("dialog.remote_store.delete.confirm"),
|
|
629
|
+
)
|
|
630
|
+
return
|
|
631
|
+
|
|
632
|
+
self.window.update_status(trans('status.sending'))
|
|
633
|
+
updated = False
|
|
634
|
+
QApplication.processEvents()
|
|
635
|
+
core = self._core_for()
|
|
636
|
+
ids = store_id if isinstance(store_id, list) else [store_id]
|
|
637
|
+
for sid in ids:
|
|
638
|
+
if self.current == sid:
|
|
639
|
+
self.current = None
|
|
640
|
+
try:
|
|
641
|
+
if core.delete(sid):
|
|
642
|
+
try:
|
|
643
|
+
self.window.controller.assistant.batch.remove_store_from_assistants(sid)
|
|
644
|
+
except Exception:
|
|
645
|
+
pass
|
|
646
|
+
self.window.update_status(trans('status.deleted'))
|
|
647
|
+
core.save()
|
|
648
|
+
self.after_delete(self._get_provider(), sid)
|
|
649
|
+
updated = True
|
|
650
|
+
else:
|
|
651
|
+
self.window.update_status(trans('status.error'))
|
|
652
|
+
except Exception as e:
|
|
653
|
+
self.window.update_status(trans('status.error'))
|
|
654
|
+
self.window.ui.dialogs.alert(e)
|
|
655
|
+
if updated:
|
|
656
|
+
self.update()
|
|
657
|
+
self.init()
|
|
658
|
+
self.restore_selection()
|
|
659
|
+
self.update_files_list()
|
|
660
|
+
|
|
661
|
+
# ======================== Files (documents) ========================
|
|
662
|
+
|
|
663
|
+
def update_files_list(self):
|
|
664
|
+
"""Populate files list (documents) for the current store from local DB."""
|
|
665
|
+
model_id = 'remote_store.files.list'
|
|
666
|
+
if model_id not in self.window.ui.models:
|
|
667
|
+
return
|
|
668
|
+
model = self.window.ui.models[model_id]
|
|
669
|
+
try:
|
|
670
|
+
model.removeRows(0, model.rowCount())
|
|
671
|
+
except Exception:
|
|
672
|
+
pass
|
|
673
|
+
|
|
674
|
+
self._files_row_to_id = []
|
|
675
|
+
|
|
676
|
+
if self.current is None:
|
|
677
|
+
return
|
|
678
|
+
|
|
679
|
+
files_db = self._files_core_for()
|
|
680
|
+
if files_db is None:
|
|
681
|
+
return
|
|
682
|
+
|
|
683
|
+
try:
|
|
684
|
+
store_files = files_db.get_by_store_or_thread(self.current, None) or {}
|
|
685
|
+
except Exception as e:
|
|
686
|
+
self.window.core.debug.log(e)
|
|
687
|
+
store_files = {}
|
|
688
|
+
|
|
689
|
+
i = 0
|
|
690
|
+
for file_id, file_obj in store_files.items():
|
|
691
|
+
if isinstance(file_obj, dict):
|
|
692
|
+
data = file_obj
|
|
693
|
+
else:
|
|
694
|
+
data = {}
|
|
695
|
+
for key in ('id', 'file_id', 'name', 'filename', 'bytes', 'size', 'usage_bytes', 'status'):
|
|
696
|
+
try:
|
|
697
|
+
if hasattr(file_obj, key):
|
|
698
|
+
data[key] = getattr(file_obj, key)
|
|
699
|
+
except Exception:
|
|
700
|
+
pass
|
|
701
|
+
if not data and hasattr(file_obj, 'to_dict'):
|
|
702
|
+
try:
|
|
703
|
+
data = file_obj.to_dict()
|
|
704
|
+
except Exception:
|
|
705
|
+
data = {}
|
|
706
|
+
|
|
707
|
+
name = data.get('name') or data.get('filename') or file_id
|
|
708
|
+
size_val = None
|
|
709
|
+
for k in ('bytes', 'size', 'usage_bytes'):
|
|
710
|
+
if data.get(k) is not None:
|
|
711
|
+
size_val = data.get(k)
|
|
712
|
+
break
|
|
713
|
+
|
|
714
|
+
size_txt = ""
|
|
715
|
+
try:
|
|
716
|
+
if size_val:
|
|
717
|
+
size_txt = self.window.core.filesystem.sizeof_fmt(int(size_val))
|
|
718
|
+
except Exception:
|
|
719
|
+
pass
|
|
720
|
+
|
|
721
|
+
extra = []
|
|
722
|
+
if size_txt:
|
|
723
|
+
extra.append(size_txt)
|
|
724
|
+
if data.get('status'):
|
|
725
|
+
extra.append(str(data.get('status')))
|
|
726
|
+
label = name
|
|
727
|
+
if extra:
|
|
728
|
+
label += " ({})".format(", ".join(extra))
|
|
729
|
+
|
|
730
|
+
item = QStandardItem(label)
|
|
731
|
+
item.setEditable(False)
|
|
732
|
+
model.setItem(i, 0, item)
|
|
733
|
+
self._files_row_to_id.append(data['file_id'] if 'file_id' in data else file_id)
|
|
734
|
+
i += 1
|
|
735
|
+
|
|
736
|
+
def delete_file_by_idx(self, idx: Union[int, list], force: bool = False):
|
|
737
|
+
"""
|
|
738
|
+
Delete a single document from the current store.
|
|
739
|
+
Uses inline confirmation if force is False.
|
|
740
|
+
"""
|
|
741
|
+
if self.current is None:
|
|
742
|
+
self.window.ui.dialogs.alert("Please select store first.")
|
|
743
|
+
return
|
|
744
|
+
|
|
745
|
+
model_id = 'remote_store.files.list'
|
|
746
|
+
if model_id not in self.window.ui.models:
|
|
747
|
+
return
|
|
748
|
+
|
|
749
|
+
if not isinstance(idx, list):
|
|
750
|
+
idx = [idx]
|
|
751
|
+
idx_list = []
|
|
752
|
+
for i in idx:
|
|
753
|
+
if i < 0 or i >= len(self._files_row_to_id):
|
|
754
|
+
continue
|
|
755
|
+
idx_list.append(i)
|
|
756
|
+
|
|
757
|
+
file_ids = [self._files_row_to_id[i] for i in idx_list if self._files_row_to_id[i]]
|
|
758
|
+
if not file_ids:
|
|
759
|
+
return
|
|
760
|
+
|
|
761
|
+
if not force:
|
|
762
|
+
m = QMessageBox(self.window)
|
|
763
|
+
m.setIcon(QMessageBox.Warning)
|
|
764
|
+
m.setWindowTitle(trans("remote_store.menu.file.delete"))
|
|
765
|
+
m.setText(trans('confirm.remote_store.file.delete'))
|
|
766
|
+
m.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
767
|
+
m.setDefaultButton(QMessageBox.No)
|
|
768
|
+
if m.exec() != QMessageBox.Yes:
|
|
769
|
+
return
|
|
770
|
+
|
|
771
|
+
self._delete_file_by_idx_provider(idx, self._get_provider())
|
|
772
|
+
|
|
773
|
+
def _delete_file_by_idx_provider(self, idx: Union[int, list], provider: str, file_id: Optional[str] = None):
|
|
774
|
+
"""Provider-explicit file delete (used by proxies/confirm)."""
|
|
775
|
+
if self.current is None and provider == self._get_provider():
|
|
776
|
+
self.window.ui.dialogs.alert(trans("dialog.remote_store.alert.select"))
|
|
777
|
+
return
|
|
778
|
+
|
|
779
|
+
model_id = 'remote_store.files.list'
|
|
780
|
+
if model_id not in self.window.ui.models and provider == self._get_provider():
|
|
781
|
+
return
|
|
782
|
+
|
|
783
|
+
idx_list = []
|
|
784
|
+
if file_id is None:
|
|
785
|
+
if not isinstance(idx, list):
|
|
786
|
+
idx = [idx]
|
|
787
|
+
for i in idx:
|
|
788
|
+
if i < 0 or i >= len(self._files_row_to_id):
|
|
789
|
+
continue
|
|
790
|
+
idx_list.append(i)
|
|
791
|
+
|
|
792
|
+
if not idx_list:
|
|
793
|
+
return
|
|
794
|
+
|
|
795
|
+
file_ids = [self._files_row_to_id[i] for i in idx_list if self._files_row_to_id[i]]
|
|
796
|
+
if not file_ids:
|
|
797
|
+
return
|
|
798
|
+
else:
|
|
799
|
+
file_ids = [file_id]
|
|
800
|
+
idx_list = [idx] if isinstance(idx, int) else idx
|
|
801
|
+
|
|
802
|
+
# sort indexes descending to avoid shifting issues when removing multiple
|
|
803
|
+
idx_list.sort(reverse=True)
|
|
804
|
+
|
|
805
|
+
self.window.update_status(trans('status.sending'))
|
|
806
|
+
QApplication.processEvents()
|
|
807
|
+
try:
|
|
808
|
+
api = self._api_store_for(provider)
|
|
809
|
+
removed = False
|
|
810
|
+
|
|
811
|
+
for file_id in file_ids:
|
|
812
|
+
if hasattr(api, 'remove_store_file'):
|
|
813
|
+
try:
|
|
814
|
+
api.remove_store_file(self.current if provider == self._get_provider() else "", file_id)
|
|
815
|
+
removed = True
|
|
816
|
+
except Exception as e:
|
|
817
|
+
self.window.core.debug.log(e)
|
|
818
|
+
|
|
819
|
+
if not removed and hasattr(api, 'remove_file'):
|
|
820
|
+
try:
|
|
821
|
+
api.remove_file(file_id)
|
|
822
|
+
removed = True
|
|
823
|
+
except Exception as e:
|
|
824
|
+
self.window.core.debug.log(e)
|
|
825
|
+
|
|
826
|
+
if not removed:
|
|
827
|
+
raise RuntimeError("Remove file API not available.")
|
|
828
|
+
|
|
829
|
+
for file_id in file_ids:
|
|
830
|
+
try:
|
|
831
|
+
self._files_core_for(provider).delete_by_file_id(file_id)
|
|
832
|
+
except Exception as e:
|
|
833
|
+
self.window.core.debug.log(e)
|
|
834
|
+
|
|
835
|
+
if provider == self._get_provider():
|
|
836
|
+
try:
|
|
837
|
+
for i in idx_list:
|
|
838
|
+
self.window.ui.models[model_id].removeRow(i)
|
|
839
|
+
try:
|
|
840
|
+
del self._files_row_to_id[i]
|
|
841
|
+
except Exception:
|
|
842
|
+
pass
|
|
843
|
+
except Exception:
|
|
844
|
+
pass
|
|
845
|
+
|
|
846
|
+
QTimer.singleShot(1200, self.refresh_status)
|
|
847
|
+
self.window.update_status(trans('status.deleted'))
|
|
848
|
+
|
|
849
|
+
except Exception as e:
|
|
850
|
+
self.window.core.debug.log(e)
|
|
851
|
+
self.window.ui.dialogs.alert("Failed to delete file: {}".format(e))
|
|
852
|
+
self.window.update_status(trans('status.error'))
|
|
853
|
+
if provider == self._get_provider():
|
|
854
|
+
self.update_files_list()
|
|
855
|
+
|
|
856
|
+
# ======================== Hide threads ========================
|
|
857
|
+
|
|
858
|
+
def set_hide_thread(self, state: bool):
|
|
859
|
+
"""Toggle hide thread stores for current provider."""
|
|
860
|
+
self._set_hide_threads_config(self._get_provider(), state)
|
|
861
|
+
self.update()
|
|
862
|
+
|
|
863
|
+
def _get_hide_threads_config(self, provider: str) -> Any:
|
|
864
|
+
key = "remote_store.hide_threads"
|
|
865
|
+
return self.window.core.config.get(key)
|
|
866
|
+
|
|
867
|
+
def _set_hide_threads_config(self, provider: str, state: bool):
|
|
868
|
+
key = "remote_store.hide_threads"
|
|
869
|
+
self.window.core.config.set(key, bool(state))
|
|
870
|
+
|
|
871
|
+
# ======================== Title ========================
|
|
872
|
+
|
|
873
|
+
def _dialog_title_for_provider(self, provider: str) -> str:
|
|
874
|
+
return trans('dialog.remote_store')
|
|
875
|
+
|
|
876
|
+
# ======================== Unified batch entry points (UI menu) ========================
|
|
877
|
+
|
|
878
|
+
def import_stores(self, force: bool = False):
|
|
879
|
+
if not force:
|
|
880
|
+
t = 'remote_store.import'
|
|
881
|
+
self.window.ui.dialogs.confirm(type=t, id='', msg=trans('confirm.remote_store.import'))
|
|
882
|
+
return
|
|
883
|
+
imp = self._importer_for(self._get_provider())
|
|
884
|
+
self._core_for().truncate()
|
|
885
|
+
imp.import_vector_stores()
|
|
886
|
+
|
|
887
|
+
def refresh_stores(self, force: bool = False):
|
|
888
|
+
if not force:
|
|
889
|
+
t = 'remote_store.refresh'
|
|
890
|
+
self.window.ui.dialogs.confirm(type=t, id='', msg=trans('confirm.remote_store.refresh'))
|
|
891
|
+
return
|
|
892
|
+
imp = self._importer_for(self._get_provider())
|
|
893
|
+
imp.refresh_vector_stores()
|
|
894
|
+
|
|
895
|
+
def truncate_stores(self, force: bool = False):
|
|
896
|
+
if not force:
|
|
897
|
+
t = 'remote_store.truncate'
|
|
898
|
+
self.window.ui.dialogs.confirm(type=t, id='', msg=trans('confirm.remote_store.truncate'))
|
|
899
|
+
return
|
|
900
|
+
imp = self._importer_for(self._get_provider())
|
|
901
|
+
self._core_for().truncate()
|
|
902
|
+
imp.truncate_vector_stores()
|
|
903
|
+
|
|
904
|
+
def import_files(self, force: bool = False):
|
|
905
|
+
if not force:
|
|
906
|
+
t = 'remote_store.files.import.all'
|
|
907
|
+
self.window.ui.dialogs.confirm(type=t, id='', msg=trans('confirm.remote_store.import_files'))
|
|
908
|
+
return
|
|
909
|
+
imp = self._importer_for(self._get_provider())
|
|
910
|
+
imp.import_files()
|
|
911
|
+
|
|
912
|
+
def import_store_files(self, store_id: str, force: bool = False):
|
|
913
|
+
if store_id is None:
|
|
914
|
+
self.window.ui.dialogs.alert(trans("dialog.remote_store.alert.select"))
|
|
915
|
+
return
|
|
916
|
+
if not force:
|
|
917
|
+
t = 'remote_store.files.import.store'
|
|
918
|
+
self.window.ui.dialogs.confirm(type=t, id=store_id, msg=trans('confirm.remote_store.import_files.store'))
|
|
919
|
+
return
|
|
920
|
+
imp = self._importer_for(self._get_provider())
|
|
921
|
+
self.window.update_status("Importing files...please wait...")
|
|
922
|
+
imp.import_files(store_id)
|
|
923
|
+
|
|
924
|
+
def truncate_files(self, force: bool = False):
|
|
925
|
+
if not force:
|
|
926
|
+
t = 'remote_store.files.truncate'
|
|
927
|
+
key = 'confirm.remote_store.files.truncate'
|
|
928
|
+
self.window.ui.dialogs.confirm(type=t, id='', msg=trans(key))
|
|
929
|
+
return
|
|
930
|
+
imp = self._importer_for(self._get_provider())
|
|
931
|
+
imp.truncate_files()
|
|
932
|
+
|
|
933
|
+
def truncate_store_files(self, store_id: Union[str, list], force: bool = False):
|
|
934
|
+
if store_id is None:
|
|
935
|
+
self.window.ui.dialogs.alert(trans("dialog.remote_store.alert.select"))
|
|
936
|
+
return
|
|
937
|
+
if not force:
|
|
938
|
+
t = 'remote_store.files.truncate.store'
|
|
939
|
+
key = 'confirm.remote_store.files.truncate.store'
|
|
940
|
+
self.window.ui.dialogs.confirm(type=t, id=store_id, msg=trans(key))
|
|
941
|
+
return
|
|
942
|
+
imp = self._importer_for(self._get_provider())
|
|
943
|
+
ids = store_id if isinstance(store_id, list) else [store_id]
|
|
944
|
+
for sid in ids:
|
|
945
|
+
imp.truncate_files(sid)
|
|
946
|
+
|
|
947
|
+
def clear_files(self, force: bool = False):
|
|
948
|
+
if not force:
|
|
949
|
+
t = 'remote_store.files.clear.all'
|
|
950
|
+
self.window.ui.dialogs.confirm(type=t, id='', msg=trans('confirm.assistant.files.clear'))
|
|
951
|
+
return
|
|
952
|
+
self.window.update_status("Clearing documents...please wait...")
|
|
953
|
+
QApplication.processEvents()
|
|
954
|
+
try:
|
|
955
|
+
self._files_core_for().truncate_local()
|
|
956
|
+
except Exception as e:
|
|
957
|
+
self.window.core.debug.log(e)
|
|
958
|
+
self.update()
|
|
959
|
+
self.window.update_status("OK. All documents cleared.")
|
|
960
|
+
self.window.ui.dialogs.alert(trans("status.finished"))
|
|
961
|
+
|
|
962
|
+
def clear_store_files(self, store_id: Optional[Union[str, list]] = None, force: bool = False):
|
|
963
|
+
if store_id is None:
|
|
964
|
+
self.window.ui.dialogs.alert(trans("dialog.remote_store.alert.select"))
|
|
965
|
+
return
|
|
966
|
+
if not force:
|
|
967
|
+
t = 'remote_store.files.clear.store'
|
|
968
|
+
self.window.ui.dialogs.confirm(type=t, id=store_id, msg=trans('confirm.assistant.files.clear'))
|
|
969
|
+
return
|
|
970
|
+
self.window.update_status("Clearing store documents...please wait...")
|
|
971
|
+
QApplication.processEvents()
|
|
972
|
+
ids = store_id if isinstance(store_id, list) else [store_id]
|
|
973
|
+
for sid in ids:
|
|
974
|
+
try:
|
|
975
|
+
self._files_core_for().truncate_local(sid)
|
|
976
|
+
except Exception as e:
|
|
977
|
+
self.window.core.debug.log(e)
|
|
978
|
+
self.update()
|
|
979
|
+
self.window.update_status("OK. Store documents cleared.")
|
|
980
|
+
self.window.ui.dialogs.alert(trans("status.finished"))
|
|
981
|
+
|
|
982
|
+
def clear_stores(self, force: bool = False):
|
|
983
|
+
if not force:
|
|
984
|
+
t = 'remote_store.clear'
|
|
985
|
+
self.window.ui.dialogs.confirm(type=t, id='', msg=trans('confirm.remote_store.clear'))
|
|
986
|
+
return
|
|
987
|
+
self.window.update_status("Clearing stores...please wait...")
|
|
988
|
+
QApplication.processEvents()
|
|
989
|
+
try:
|
|
990
|
+
self._core_for().truncate()
|
|
991
|
+
except Exception as e:
|
|
992
|
+
self.window.core.debug.log(e)
|
|
993
|
+
self.update()
|
|
994
|
+
self.current = None
|
|
995
|
+
self.init()
|
|
996
|
+
self.window.update_status("OK. All stores cleared.")
|
|
997
|
+
|
|
998
|
+
# ======================== Utilities ========================
|
|
999
|
+
|
|
1000
|
+
def _importer_for(self, provider: str):
|
|
1001
|
+
if (hasattr(self.window.core.api, provider)
|
|
1002
|
+
and hasattr(getattr(self.window.core.api, provider), 'store')
|
|
1003
|
+
and hasattr(getattr(self.window.core.api, provider).store, 'importer')):
|
|
1004
|
+
return getattr(self.window.core.api, provider).store.importer
|