pygpt-net 2.7.6__py3-none-any.whl → 2.7.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. pygpt_net/CHANGELOG.txt +13 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +5 -1
  4. pygpt_net/controller/assistant/batch.py +2 -2
  5. pygpt_net/controller/assistant/files.py +7 -6
  6. pygpt_net/controller/assistant/threads.py +0 -0
  7. pygpt_net/controller/chat/command.py +0 -0
  8. pygpt_net/controller/chat/remote_tools.py +3 -9
  9. pygpt_net/controller/chat/stream.py +2 -2
  10. pygpt_net/controller/chat/{handler/worker.py → stream_worker.py} +13 -35
  11. pygpt_net/controller/dialogs/confirm.py +35 -58
  12. pygpt_net/controller/lang/mapping.py +9 -9
  13. pygpt_net/controller/remote_store/{google/batch.py → batch.py} +209 -252
  14. pygpt_net/controller/remote_store/remote_store.py +982 -13
  15. pygpt_net/core/command/command.py +0 -0
  16. pygpt_net/core/db/viewer.py +1 -1
  17. pygpt_net/core/debug/models.py +2 -2
  18. pygpt_net/core/realtime/worker.py +3 -1
  19. pygpt_net/{controller/remote_store/google → core/remote_store/anthropic}/__init__.py +0 -1
  20. pygpt_net/core/remote_store/anthropic/files.py +211 -0
  21. pygpt_net/core/remote_store/anthropic/store.py +208 -0
  22. pygpt_net/core/remote_store/openai/store.py +5 -4
  23. pygpt_net/core/remote_store/remote_store.py +5 -1
  24. pygpt_net/{controller/remote_store/openai → core/remote_store/xai}/__init__.py +0 -1
  25. pygpt_net/core/remote_store/xai/files.py +225 -0
  26. pygpt_net/core/remote_store/xai/store.py +219 -0
  27. pygpt_net/data/config/config.json +18 -5
  28. pygpt_net/data/config/models.json +193 -4
  29. pygpt_net/data/config/settings.json +179 -36
  30. pygpt_net/data/icons/folder_eye.svg +1 -0
  31. pygpt_net/data/icons/folder_eye_filled.svg +1 -0
  32. pygpt_net/data/icons/folder_open.svg +1 -0
  33. pygpt_net/data/icons/folder_open_filled.svg +1 -0
  34. pygpt_net/data/locale/locale.de.ini +6 -3
  35. pygpt_net/data/locale/locale.en.ini +46 -12
  36. pygpt_net/data/locale/locale.es.ini +6 -3
  37. pygpt_net/data/locale/locale.fr.ini +6 -3
  38. pygpt_net/data/locale/locale.it.ini +6 -3
  39. pygpt_net/data/locale/locale.pl.ini +7 -4
  40. pygpt_net/data/locale/locale.uk.ini +6 -3
  41. pygpt_net/data/locale/locale.zh.ini +6 -3
  42. pygpt_net/icons.qrc +4 -0
  43. pygpt_net/icons_rc.py +282 -138
  44. pygpt_net/plugin/cmd_mouse_control/worker.py +2 -1
  45. pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +2 -1
  46. pygpt_net/provider/api/anthropic/__init__.py +10 -3
  47. pygpt_net/provider/api/anthropic/chat.py +342 -11
  48. pygpt_net/provider/api/anthropic/computer.py +844 -0
  49. pygpt_net/provider/api/anthropic/remote_tools.py +172 -0
  50. pygpt_net/provider/api/anthropic/store.py +307 -0
  51. pygpt_net/{controller/chat/handler/anthropic_stream.py → provider/api/anthropic/stream.py} +99 -10
  52. pygpt_net/provider/api/anthropic/tools.py +32 -77
  53. pygpt_net/provider/api/anthropic/utils.py +30 -0
  54. pygpt_net/{controller/chat/handler → provider/api/anthropic/worker}/__init__.py +0 -0
  55. pygpt_net/provider/api/anthropic/worker/importer.py +278 -0
  56. pygpt_net/provider/api/google/chat.py +62 -9
  57. pygpt_net/provider/api/google/store.py +124 -3
  58. pygpt_net/{controller/chat/handler/google_stream.py → provider/api/google/stream.py} +92 -25
  59. pygpt_net/provider/api/google/utils.py +185 -0
  60. pygpt_net/provider/api/google/worker/importer.py +16 -28
  61. pygpt_net/provider/api/langchain/__init__.py +0 -0
  62. pygpt_net/{controller/chat/handler/langchain_stream.py → provider/api/langchain/stream.py} +1 -1
  63. pygpt_net/provider/api/llama_index/__init__.py +0 -0
  64. pygpt_net/{controller/chat/handler/llamaindex_stream.py → provider/api/llama_index/stream.py} +1 -1
  65. pygpt_net/provider/api/openai/assistants.py +2 -2
  66. pygpt_net/provider/api/openai/image.py +2 -2
  67. pygpt_net/provider/api/openai/store.py +4 -1
  68. pygpt_net/{controller/chat/handler/openai_stream.py → provider/api/openai/stream.py} +1 -1
  69. pygpt_net/provider/api/openai/utils.py +69 -3
  70. pygpt_net/provider/api/openai/worker/importer.py +19 -61
  71. pygpt_net/provider/api/openai/worker/importer_assistants.py +230 -0
  72. pygpt_net/provider/api/x_ai/__init__.py +138 -15
  73. pygpt_net/provider/api/x_ai/audio.py +43 -11
  74. pygpt_net/provider/api/x_ai/chat.py +92 -4
  75. pygpt_net/provider/api/x_ai/image.py +149 -47
  76. pygpt_net/provider/api/x_ai/realtime/__init__.py +12 -0
  77. pygpt_net/provider/api/x_ai/realtime/client.py +1825 -0
  78. pygpt_net/provider/api/x_ai/realtime/realtime.py +198 -0
  79. pygpt_net/provider/api/x_ai/{remote.py → remote_tools.py} +183 -70
  80. pygpt_net/provider/api/x_ai/responses.py +507 -0
  81. pygpt_net/provider/api/x_ai/store.py +610 -0
  82. pygpt_net/{controller/chat/handler/xai_stream.py → provider/api/x_ai/stream.py} +42 -10
  83. pygpt_net/provider/api/x_ai/tools.py +59 -8
  84. pygpt_net/{controller/chat/handler → provider/api/x_ai}/utils.py +1 -2
  85. pygpt_net/provider/api/x_ai/vision.py +1 -4
  86. pygpt_net/provider/api/x_ai/worker/importer.py +308 -0
  87. pygpt_net/provider/audio_input/xai_grok_voice.py +390 -0
  88. pygpt_net/provider/audio_output/xai_tts.py +325 -0
  89. pygpt_net/provider/core/config/patch.py +39 -3
  90. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
  91. pygpt_net/provider/core/model/patch.py +39 -1
  92. pygpt_net/tools/image_viewer/tool.py +334 -34
  93. pygpt_net/tools/image_viewer/ui/dialogs.py +319 -22
  94. pygpt_net/tools/text_editor/ui/dialogs.py +3 -2
  95. pygpt_net/tools/text_editor/ui/widgets.py +0 -0
  96. pygpt_net/ui/dialog/assistant.py +1 -1
  97. pygpt_net/ui/dialog/plugins.py +13 -5
  98. pygpt_net/ui/dialog/remote_store.py +552 -0
  99. pygpt_net/ui/dialogs.py +3 -5
  100. pygpt_net/ui/layout/ctx/ctx_list.py +58 -7
  101. pygpt_net/ui/menu/tools.py +6 -13
  102. pygpt_net/ui/widget/dialog/base.py +16 -5
  103. pygpt_net/ui/widget/dialog/{remote_store_google.py → remote_store.py} +10 -10
  104. pygpt_net/ui/widget/element/button.py +4 -4
  105. pygpt_net/ui/widget/image/display.py +2 -2
  106. pygpt_net/ui/widget/lists/context.py +2 -2
  107. pygpt_net/ui/widget/textarea/editor.py +0 -0
  108. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/METADATA +15 -2
  109. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/RECORD +107 -89
  110. pygpt_net/controller/remote_store/google/store.py +0 -615
  111. pygpt_net/controller/remote_store/openai/batch.py +0 -524
  112. pygpt_net/controller/remote_store/openai/store.py +0 -699
  113. pygpt_net/ui/dialog/remote_store_google.py +0 -539
  114. pygpt_net/ui/dialog/remote_store_openai.py +0 -539
  115. pygpt_net/ui/widget/dialog/remote_store_openai.py +0 -56
  116. pygpt_net/ui/widget/lists/remote_store_google.py +0 -248
  117. pygpt_net/ui/widget/lists/remote_store_openai.py +0 -317
  118. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/LICENSE +0 -0
  119. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/WHEEL +0 -0
  120. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/entry_points.txt +0 -0
@@ -1,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()