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
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.12.31 16:00:00 #
9
+ # Updated Date: 2026.01.05 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import base64
@@ -177,7 +177,7 @@ class ImageWorker(QRunnable):
177
177
 
178
178
  def _is_gpt_image_model(self, model_id: Optional[str] = None) -> bool:
179
179
  mid = (model_id or self.model or "").lower()
180
- return mid.startswith("gpt-image-1")
180
+ return mid.startswith("gpt-image-1") or mid.startswith("chatgpt-image")
181
181
 
182
182
  def _is_dalle2(self, model_id: Optional[str] = None) -> bool:
183
183
  mid = (model_id or self.model or "").lower()
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2026.01.02 20:00:00 #
9
+ # Updated Date: 2026.01.05 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -14,6 +14,8 @@ from typing import Optional, List
14
14
 
15
15
  from pygpt_net.item.store import RemoteStoreItem
16
16
 
17
+ from .worker.importer import Importer
18
+
17
19
 
18
20
  class Store:
19
21
  def __init__(self, window=None):
@@ -23,6 +25,7 @@ class Store:
23
25
  :param window: Window instance
24
26
  """
25
27
  self.window = window
28
+ self.importer = Importer(window)
26
29
 
27
30
  def get_client(self):
28
31
  """
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.12.26 00:00:00 #
9
+ # Updated Date: 2026.01.05 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import base64
@@ -6,11 +6,11 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.26 19:00:00 #
9
+ # Updated Date: 2026.01.05 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
13
-
13
+ from typing import Optional, Any
14
14
 
15
15
  def sanitize_name(name: str) -> str:
16
16
  """
@@ -24,4 +24,70 @@ def sanitize_name(name: str) -> str:
24
24
  # allowed characters: a-z, A-Z, 0-9, _, and -
25
25
  name = name.strip().lower()
26
26
  sanitized_name = re.sub(r'[^a-z0-9_-]', '_', name)
27
- return sanitized_name[:64] # limit to 64 characters
27
+ return sanitized_name[:64] # limit to 64 characters
28
+
29
+
30
+ def capture_openai_usage(state, u_obj: Any):
31
+ """
32
+ Extract usage for OpenAI/xAI-compatible chunks.
33
+
34
+ :param state: Chat state
35
+ :param u_obj: Usage object/dict
36
+ """
37
+ if not u_obj:
38
+ return
39
+ state.usage_vendor = "openai"
40
+ in_tok = as_int(safe_get(u_obj, "input_tokens")) or as_int(safe_get(u_obj, "prompt_tokens"))
41
+ out_tok = as_int(safe_get(u_obj, "output_tokens")) or as_int(safe_get(u_obj, "completion_tokens"))
42
+ total = as_int(safe_get(u_obj, "total_tokens"))
43
+ reasoning = (
44
+ as_int(safe_get(u_obj, "output_tokens_details.reasoning_tokens")) or
45
+ as_int(safe_get(u_obj, "completion_tokens_details.reasoning_tokens")) or
46
+ as_int(safe_get(u_obj, "reasoning_tokens")) or
47
+ 0
48
+ )
49
+ out_with_reason = (out_tok or 0) + (reasoning or 0)
50
+ state.usage_payload = {"in": in_tok, "out": out_with_reason, "reasoning": reasoning or 0, "total": total}
51
+
52
+ def safe_get(obj: Any, path: str) -> Any:
53
+ """
54
+ Dot-path getter for dicts and objects.
55
+
56
+ :param obj: Source object or dict
57
+ :param path: Dot-separated path, e.g. 'a.b.0.c'
58
+ :return: Value at path or None
59
+ """
60
+ cur = obj
61
+ for seg in path.split("."):
62
+ if cur is None:
63
+ return None
64
+ if isinstance(cur, dict):
65
+ cur = cur.get(seg)
66
+ else:
67
+ if seg.isdigit() and isinstance(cur, (list, tuple)):
68
+ idx = int(seg)
69
+ if 0 <= idx < len(cur):
70
+ cur = cur[idx]
71
+ else:
72
+ return None
73
+ else:
74
+ cur = getattr(cur, seg, None)
75
+ return cur
76
+
77
+
78
+ def as_int(val: Any) -> Optional[int]:
79
+ """
80
+ Coerce to int if possible, else None.
81
+
82
+ :param val: Input value
83
+ :return: int or None
84
+ """
85
+ if val is None:
86
+ return None
87
+ try:
88
+ return int(val)
89
+ except Exception:
90
+ try:
91
+ return int(float(val))
92
+ except Exception:
93
+ return None
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2026.01.02 19:00:00 #
9
+ # Updated Date: 2026.01.05 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -33,20 +33,19 @@ class Importer(QObject):
33
33
  :param mode: mode
34
34
  :param err: error message
35
35
  """
36
- if mode == "assistants":
37
- self.window.controller.assistant.batch.handle_imported_assistants_failed(err)
38
- elif mode == "import_files":
39
- self.window.controller.remote_store.openai.batch.handle_imported_files_failed(err)
36
+ batch = self.window.controller.remote_store.batch
37
+ if mode == "import_files":
38
+ batch.handle_imported_files_failed(err)
40
39
  elif mode == "truncate_files":
41
- self.window.controller.remote_store.openai.batch.handle_truncated_files_failed(err)
40
+ batch.handle_truncated_files_failed(err)
42
41
  elif mode == "upload_files":
43
- self.window.controller.remote_store.openai.batch.handle_uploaded_files_failed(err)
42
+ batch.handle_uploaded_files_failed(err)
44
43
  elif mode in "vector_stores":
45
- self.window.controller.remote_store.openai.batch.handle_imported_stores_failed(err)
44
+ batch.handle_imported_stores_failed(err)
46
45
  elif mode in "truncate_vector_stores":
47
- self.window.controller.remote_store.openai.batch.handle_truncated_stores_failed(err)
46
+ batch.handle_truncated_stores_failed(err)
48
47
  elif mode in "refresh_vector_stores":
49
- self.window.controller.remote_store.openai.batch.handle_refreshed_stores_failed(err)
48
+ batch.handle_refreshed_stores_failed(err)
50
49
 
51
50
  @Slot(str, str, int)
52
51
  def handle_finished(self, mode: str, store_id: str = None, num: int = 0):
@@ -57,20 +56,19 @@ class Importer(QObject):
57
56
  :param store_id: store ID
58
57
  :param num: number of affected items
59
58
  """
60
- if mode == "assistants":
61
- self.window.controller.assistant.batch.handle_imported_assistants(num)
62
- elif mode == "import_files":
63
- self.window.controller.remote_store.openai.batch.handle_imported_files(num)
59
+ batch = self.window.controller.remote_store.batch
60
+ if mode == "import_files":
61
+ batch.handle_imported_files(num)
64
62
  elif mode == "truncate_files":
65
- self.window.controller.remote_store.openai.batch.handle_truncated_files(store_id, num)
63
+ batch.handle_truncated_files(store_id, num)
66
64
  elif mode == "upload_files":
67
- self.window.controller.remote_store.openai.batch.handle_uploaded_files(num)
65
+ batch.handle_uploaded_files(num)
68
66
  elif mode == "vector_stores":
69
- self.window.controller.remote_store.openai.batch.handle_imported_stores(num)
67
+ batch.handle_imported_stores(num)
70
68
  elif mode == "truncate_vector_stores":
71
- self.window.controller.remote_store.openai.batch.handle_truncated_stores(num)
69
+ batch.handle_truncated_stores(num)
72
70
  elif mode == "refresh_vector_stores":
73
- self.window.controller.remote_store.openai.batch.handle_refreshed_stores(num)
71
+ batch.handle_refreshed_stores(num)
74
72
 
75
73
  @Slot(str, str)
76
74
  def handle_status(self, mode: str, msg: str):
@@ -92,14 +90,6 @@ class Importer(QObject):
92
90
  """
93
91
  self.window.controller.assistant.threads.log(mode + ": " + msg)
94
92
 
95
- def import_assistants(self):
96
- """Import assistants"""
97
- self.worker = ImportWorker()
98
- self.worker.window = self.window
99
- self.worker.mode = "assistants"
100
- self.connect_signals(self.worker)
101
- self.window.threadpool.start(self.worker)
102
-
103
93
  def import_vector_stores(self):
104
94
  """Import vector stores"""
105
95
  self.worker = ImportWorker()
@@ -203,9 +193,7 @@ class ImportWorker(QRunnable):
203
193
  """Importer thread"""
204
194
  try:
205
195
  # import data
206
- if self.mode == "assistants":
207
- self.import_assistants()
208
- elif self.mode == "vector_stores":
196
+ if self.mode == "vector_stores":
209
197
  if self.import_vector_stores():
210
198
  self.import_files()
211
199
  elif self.mode == "truncate_vector_stores":
@@ -225,36 +213,6 @@ class ImportWorker(QRunnable):
225
213
  finally:
226
214
  self.cleanup()
227
215
 
228
- def import_assistants(self, silent: bool = False) -> bool:
229
- """
230
- Import assistants from API
231
-
232
- :param silent: silent mode (no signals)
233
- :return: result
234
- """
235
- try:
236
- # import assistants
237
- self.log("Importing assistants...")
238
- self.window.core.assistants.clear()
239
- items = self.window.core.assistants.get_all()
240
- self.window.core.api.openai.assistants.import_all(items, callback=self.callback)
241
- self.window.core.assistants.items = items
242
- self.window.core.assistants.save()
243
-
244
- # import vector stores
245
- self.import_vector_stores(True)
246
-
247
- # import files
248
- self.import_files(True)
249
-
250
- if not silent:
251
- self.signals.finished.emit("assistants", self.store_id, len(items))
252
- return True
253
- except Exception as e:
254
- self.log("API error: {}".format(e))
255
- self.signals.error.emit("assistants", e)
256
- return False
257
-
258
216
  def import_vector_stores(self, silent: bool = False) -> bool:
259
217
  """
260
218
  Import vector stores from API
@@ -310,7 +268,7 @@ class ImportWorker(QRunnable):
310
268
  for id in stores:
311
269
  store = stores[id]
312
270
  try:
313
- self.window.controller.remote_store.openai.refresh_store(store, update=False)
271
+ self.window.controller.remote_store.refresh_store(store, update=False, provider="openai")
314
272
  num += 1
315
273
  except Exception as e:
316
274
  self.log("Failed to refresh store: {}".format(id))
@@ -0,0 +1,230 @@
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.05 17:00:00 #
10
+ # ================================================== #
11
+
12
+ import os
13
+
14
+ from PySide6.QtCore import QObject, Signal, QRunnable, Slot
15
+
16
+
17
+ class Importer(QObject):
18
+ def __init__(self, window=None):
19
+ """
20
+ Importer core
21
+
22
+ :param window: Window instance
23
+ """
24
+ super(Importer, self).__init__()
25
+ self.window = window
26
+ self.worker = None
27
+
28
+ @Slot(str, object)
29
+ def handle_error(self, mode: str, err: any):
30
+ """
31
+ Handle thread error signal
32
+
33
+ :param mode: mode
34
+ :param err: error message
35
+ """
36
+ batch = self.window.controller.remote_store.batch
37
+ if mode == "assistants":
38
+ self.window.controller.assistant.batch.handle_imported_assistants_failed(err)
39
+
40
+ @Slot(str, str, int)
41
+ def handle_finished(self, mode: str, store_id: str = None, num: int = 0):
42
+ """
43
+ Handle thread finished signal
44
+
45
+ :param mode: mode
46
+ :param store_id: store ID
47
+ :param num: number of affected items
48
+ """
49
+ batch = self.window.controller.remote_store.batch
50
+ if mode == "assistants":
51
+ self.window.controller.assistant.batch.handle_imported_assistants(num)
52
+
53
+ @Slot(str, str)
54
+ def handle_status(self, mode: str, msg: str):
55
+ """
56
+ Handle thread status change signal
57
+
58
+ :param mode: mode
59
+ :param msg: message
60
+ """
61
+ self.window.controller.assistant.batch.handle_status_change(mode, msg)
62
+
63
+ @Slot(str, str)
64
+ def handle_log(self, mode: str, msg: str):
65
+ """
66
+ Handle thread log message signal
67
+
68
+ :param mode: mode
69
+ :param msg: message
70
+ """
71
+ self.window.controller.assistant.threads.log(mode + ": " + msg)
72
+
73
+ def import_assistants(self):
74
+ """Import assistants"""
75
+ self.worker = ImportWorker()
76
+ self.worker.window = self.window
77
+ self.worker.mode = "assistants"
78
+ self.connect_signals(self.worker)
79
+ self.window.threadpool.start(self.worker)
80
+
81
+ def connect_signals(self, worker):
82
+ """
83
+ Connect signals
84
+
85
+ :param worker: worker instance
86
+ """
87
+ worker.signals.finished.connect(self.handle_finished)
88
+ worker.signals.error.connect(self.handle_error)
89
+ worker.signals.status.connect(self.handle_status)
90
+ worker.signals.log.connect(self.handle_log)
91
+
92
+
93
+ class ImportWorkerSignals(QObject):
94
+ """Import worker signals"""
95
+ status = Signal(str, str) # mode, message
96
+ finished = Signal(str, str, int) # mode, store_id, num
97
+ error = Signal(str, object) # mode, error
98
+ log = Signal(str, str) # mode, message
99
+
100
+
101
+ class ImportWorker(QRunnable):
102
+ """Import worker"""
103
+ def __init__(self, *args, **kwargs):
104
+ super().__init__()
105
+ self.signals = ImportWorkerSignals()
106
+ self.window = None
107
+ self.mode = "assistants"
108
+ self.assistant = None
109
+ self.store_id = None
110
+ self.files = []
111
+
112
+ @Slot()
113
+ def run(self):
114
+ """Importer thread"""
115
+ try:
116
+ # import data
117
+ if self.mode == "assistants":
118
+ self.import_assistants()
119
+
120
+ except Exception as e:
121
+ self.signals.error.emit(self.mode, e)
122
+
123
+ finally:
124
+ self.cleanup()
125
+
126
+ def import_assistants(self, silent: bool = False) -> bool:
127
+ """
128
+ Import assistants from API
129
+
130
+ :param silent: silent mode (no signals)
131
+ :return: result
132
+ """
133
+ try:
134
+ # import assistants
135
+ self.log("Importing assistants...")
136
+ self.window.core.assistants.clear()
137
+ items = self.window.core.assistants.get_all()
138
+ self.window.core.api.openai.assistants.import_all(items, callback=self.callback)
139
+ self.window.core.assistants.items = items
140
+ self.window.core.assistants.save()
141
+
142
+ # import vector stores
143
+ self.import_vector_stores(True)
144
+
145
+ # import files
146
+ self.import_files(True)
147
+
148
+ if not silent:
149
+ self.signals.finished.emit("assistants", self.store_id, len(items))
150
+ return True
151
+ except Exception as e:
152
+ self.log("API error: {}".format(e))
153
+ self.signals.error.emit("assistants", e)
154
+ return False
155
+
156
+ def import_vector_stores(self, silent: bool = False) -> bool:
157
+ """
158
+ Import vector stores from API
159
+
160
+ :param silent: silent mode (no signals emit)
161
+ :return: result
162
+ """
163
+ try:
164
+ self.log("Importing vector stores...")
165
+ self.window.core.remote_store.openai.clear()
166
+ items = {}
167
+ self.window.core.api.openai.store.import_stores(items, callback=self.callback)
168
+ self.window.core.remote_store.openai.import_items(items)
169
+ if not silent:
170
+ self.signals.finished.emit("vector_stores", self.store_id, len(items))
171
+ return True
172
+ except Exception as e:
173
+ self.log("API error: {}".format(e))
174
+ self.signals.error.emit("vector_stores", e)
175
+ return False
176
+
177
+ def import_files(self, silent: bool = False) -> bool:
178
+ """
179
+ Import assistant files from API
180
+
181
+ :param silent: silent mode (no signals emit)
182
+ :return: result
183
+ """
184
+ try:
185
+ if self.store_id is None:
186
+ self.log("Importing all files...")
187
+ self.window.core.remote_store.openai.files.truncate_local() # clear local DB (all)
188
+ num = self.window.core.api.openai.store.import_stores_files(self.callback) # import all files
189
+ else:
190
+ self.log("Importing files for store: {}".format(self.store_id))
191
+ self.window.core.remote_store.openai.files.truncate_local(self.store_id) # clear local DB (all)
192
+ items = self.window.core.api.openai.store.import_store_files(
193
+ self.store_id,
194
+ [],
195
+ callback=self.callback,
196
+ ) # import store files
197
+ num = len(items)
198
+ if not silent:
199
+ self.signals.finished.emit("import_files", self.store_id, num)
200
+ return True
201
+ except Exception as e:
202
+ self.log("API error: {}".format(e))
203
+ self.signals.error.emit("import_files", e)
204
+ return False
205
+
206
+ def callback(self, msg: str):
207
+ """
208
+ Log callback
209
+
210
+ :param msg: message
211
+ """
212
+ self.log(msg)
213
+
214
+ def log(self, msg: str):
215
+ """
216
+ Log message
217
+
218
+ :param msg: message
219
+ """
220
+ self.signals.log.emit(self.mode, msg)
221
+
222
+ def cleanup(self):
223
+ """Cleanup resources after worker execution."""
224
+ sig = self.signals
225
+ self.signals = None
226
+ if sig is not None:
227
+ try:
228
+ sig.deleteLater()
229
+ except RuntimeError:
230
+ pass