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,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.
|
|
9
|
+
# Updated Date: 2026.01.06 06:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -33,18 +33,19 @@ class Importer(QObject):
|
|
|
33
33
|
:param mode: mode
|
|
34
34
|
:param err: error message
|
|
35
35
|
"""
|
|
36
|
+
batch = self.window.controller.remote_store.batch
|
|
36
37
|
if mode == "import_files":
|
|
37
|
-
|
|
38
|
+
batch.handle_imported_files_failed(err)
|
|
38
39
|
elif mode == "truncate_files":
|
|
39
|
-
|
|
40
|
+
batch.handle_truncated_files_failed(err)
|
|
40
41
|
elif mode == "upload_files":
|
|
41
|
-
|
|
42
|
+
batch.handle_uploaded_files_failed(err)
|
|
42
43
|
elif mode in "vector_stores":
|
|
43
|
-
|
|
44
|
+
batch.handle_imported_stores_failed(err)
|
|
44
45
|
elif mode in "truncate_vector_stores":
|
|
45
|
-
|
|
46
|
+
batch.handle_truncated_stores_failed(err)
|
|
46
47
|
elif mode in "refresh_vector_stores":
|
|
47
|
-
|
|
48
|
+
batch.handle_refreshed_stores_failed(err)
|
|
48
49
|
|
|
49
50
|
@Slot(str, str, int)
|
|
50
51
|
def handle_finished(self, mode: str, store_id: str = None, num: int = 0):
|
|
@@ -55,18 +56,19 @@ class Importer(QObject):
|
|
|
55
56
|
:param store_id: store ID
|
|
56
57
|
:param num: number of affected items
|
|
57
58
|
"""
|
|
59
|
+
batch = self.window.controller.remote_store.batch
|
|
58
60
|
if mode == "import_files":
|
|
59
|
-
|
|
61
|
+
batch.handle_imported_files(num)
|
|
60
62
|
elif mode == "truncate_files":
|
|
61
|
-
|
|
63
|
+
batch.handle_truncated_files(store_id, num)
|
|
62
64
|
elif mode == "upload_files":
|
|
63
|
-
|
|
65
|
+
batch.handle_uploaded_files(num)
|
|
64
66
|
elif mode == "vector_stores":
|
|
65
|
-
|
|
67
|
+
batch.handle_imported_stores(num)
|
|
66
68
|
elif mode == "truncate_vector_stores":
|
|
67
|
-
|
|
69
|
+
batch.handle_truncated_stores(num)
|
|
68
70
|
elif mode == "refresh_vector_stores":
|
|
69
|
-
|
|
71
|
+
batch.handle_refreshed_stores(num)
|
|
70
72
|
|
|
71
73
|
@Slot(str, str)
|
|
72
74
|
def handle_status(self, mode: str, msg: str):
|
|
@@ -216,20 +218,6 @@ class ImportWorker(QRunnable):
|
|
|
216
218
|
finally:
|
|
217
219
|
self.cleanup()
|
|
218
220
|
|
|
219
|
-
def import_assistants(self, silent: bool = False) -> bool:
|
|
220
|
-
"""
|
|
221
|
-
Import assistants (not used for Google by default; kept for parity)
|
|
222
|
-
|
|
223
|
-
:param silent: silent mode
|
|
224
|
-
"""
|
|
225
|
-
try:
|
|
226
|
-
if not silent:
|
|
227
|
-
self.signals.finished.emit("assistants", self.store_id, 0)
|
|
228
|
-
return True
|
|
229
|
-
except Exception as e:
|
|
230
|
-
self.signals.error.emit("assistants", e)
|
|
231
|
-
return False
|
|
232
|
-
|
|
233
221
|
def import_vector_stores(self, silent: bool = False) -> bool:
|
|
234
222
|
"""
|
|
235
223
|
Import File Search stores
|
|
@@ -282,7 +270,7 @@ class ImportWorker(QRunnable):
|
|
|
282
270
|
for id in stores:
|
|
283
271
|
store = stores[id]
|
|
284
272
|
try:
|
|
285
|
-
self.window.controller.remote_store.
|
|
273
|
+
self.window.controller.remote_store.refresh_store(store, update=False, provider="google")
|
|
286
274
|
num += 1
|
|
287
275
|
except Exception as e:
|
|
288
276
|
self.log("Failed to refresh store: {}".format(id))
|
|
@@ -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:
|
|
9
|
+
# Updated Date: 2026.01.05 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
@@ -16,7 +16,7 @@ from pygpt_net.item.assistant import AssistantItem
|
|
|
16
16
|
from pygpt_net.item.ctx import CtxItem
|
|
17
17
|
|
|
18
18
|
from .worker.assistants import AssistantsWorker, EventHandler
|
|
19
|
-
from .worker.
|
|
19
|
+
from .worker.importer_assistants import Importer
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class Assistants:
|
|
@@ -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:
|
|
9
|
+
# Updated Date: 2026.01.07 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
@@ -102,6 +102,31 @@ class Realtime:
|
|
|
102
102
|
self.handler.update_ctx(context.ctx)
|
|
103
103
|
return True # do not start new session, just send tool results
|
|
104
104
|
|
|
105
|
+
# Resolve last session ID from history only (do not fallback anywhere)
|
|
106
|
+
last_session_id = extract_last_session_id(context.history) if context.history else None
|
|
107
|
+
if is_debug:
|
|
108
|
+
print("[realtime session] Last ID", last_session_id)
|
|
109
|
+
|
|
110
|
+
# Enforce clean state rules before any live updates:
|
|
111
|
+
# - If there is no history at all: always reset live session to ensure a fresh context.
|
|
112
|
+
# - If there is history but it has no resumable session id: close any active session to avoid accidental continuation.
|
|
113
|
+
try:
|
|
114
|
+
history_len = len(context.history) if context.history else 0
|
|
115
|
+
except Exception:
|
|
116
|
+
history_len = 0
|
|
117
|
+
|
|
118
|
+
if history_len == 0:
|
|
119
|
+
if self.handler.is_session_active():
|
|
120
|
+
self.handler.close_session_sync()
|
|
121
|
+
try:
|
|
122
|
+
if context.ctx and isinstance(context.ctx.extra, dict):
|
|
123
|
+
context.ctx.extra.pop("rt_session_id", None)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
last_session_id = None # force new session
|
|
127
|
+
elif not last_session_id and self.handler.is_session_active():
|
|
128
|
+
self.handler.close_session_sync()
|
|
129
|
+
|
|
105
130
|
# update auto-turn in active session
|
|
106
131
|
if (self.handler.is_session_active()
|
|
107
132
|
and (auto_turn != self.prev_auto_turn
|
|
@@ -116,11 +141,6 @@ class Realtime:
|
|
|
116
141
|
self.window.update_status(trans("speech.listening"))
|
|
117
142
|
return True # do not send new request if session is active
|
|
118
143
|
|
|
119
|
-
# Last session ID
|
|
120
|
-
last_session_id = extract_last_session_id(context.history)
|
|
121
|
-
if is_debug:
|
|
122
|
-
print("[realtime session] Last ID", last_session_id)
|
|
123
|
-
|
|
124
144
|
# Voice
|
|
125
145
|
voice = "alloy"
|
|
126
146
|
try:
|
|
@@ -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.
|
|
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: 2026.01.
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
40
|
+
batch.handle_truncated_files_failed(err)
|
|
42
41
|
elif mode == "upload_files":
|
|
43
|
-
|
|
42
|
+
batch.handle_uploaded_files_failed(err)
|
|
44
43
|
elif mode in "vector_stores":
|
|
45
|
-
|
|
44
|
+
batch.handle_imported_stores_failed(err)
|
|
46
45
|
elif mode in "truncate_vector_stores":
|
|
47
|
-
|
|
46
|
+
batch.handle_truncated_stores_failed(err)
|
|
48
47
|
elif mode in "refresh_vector_stores":
|
|
49
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
63
|
+
batch.handle_truncated_files(store_id, num)
|
|
66
64
|
elif mode == "upload_files":
|
|
67
|
-
|
|
65
|
+
batch.handle_uploaded_files(num)
|
|
68
66
|
elif mode == "vector_stores":
|
|
69
|
-
|
|
67
|
+
batch.handle_imported_stores(num)
|
|
70
68
|
elif mode == "truncate_vector_stores":
|
|
71
|
-
|
|
69
|
+
batch.handle_truncated_stores(num)
|
|
72
70
|
elif mode == "refresh_vector_stores":
|
|
73
|
-
|
|
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 == "
|
|
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.
|
|
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
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2026.01.06 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Optional, Dict, Any
|
|
@@ -35,6 +35,8 @@ from .audio import Audio
|
|
|
35
35
|
from .image import Image
|
|
36
36
|
from .remote_tools import Remote
|
|
37
37
|
from .responses import Responses
|
|
38
|
+
from .store import Store
|
|
39
|
+
from .realtime import Realtime
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
class ApiXAI:
|
|
@@ -52,6 +54,8 @@ class ApiXAI:
|
|
|
52
54
|
self.image = Image(window)
|
|
53
55
|
self.remote = Remote(window)
|
|
54
56
|
self.responses = Responses(window)
|
|
57
|
+
self.store = Store(window)
|
|
58
|
+
self.realtime = Realtime(window)
|
|
55
59
|
self.client: Optional[xai_sdk.Client] = None
|
|
56
60
|
self.locked = False
|
|
57
61
|
self.last_client_args: Optional[Dict[str, Any]] = None
|
|
@@ -59,7 +63,8 @@ class ApiXAI:
|
|
|
59
63
|
def get_client(
|
|
60
64
|
self,
|
|
61
65
|
mode: str = MODE_CHAT,
|
|
62
|
-
model: ModelItem = None
|
|
66
|
+
model: ModelItem = None,
|
|
67
|
+
management_api_key = None
|
|
63
68
|
) -> xai_sdk.Client:
|
|
64
69
|
"""
|
|
65
70
|
Get or create xAI client.
|
|
@@ -69,11 +74,9 @@ class ApiXAI:
|
|
|
69
74
|
|
|
70
75
|
:param mode: One of MODE_*
|
|
71
76
|
:param model: ModelItem (optional, not used currently)
|
|
77
|
+
:param management_api_key: Override API key (for management calls)
|
|
72
78
|
:return: xai_sdk.Client
|
|
73
79
|
"""
|
|
74
|
-
if self.client is not None:
|
|
75
|
-
return self.client
|
|
76
|
-
|
|
77
80
|
cfg = self.window.core.config
|
|
78
81
|
api_key = cfg.get("api_key_xai") or os.environ.get("XAI_API_KEY") or ""
|
|
79
82
|
timeout = cfg.get("api_native_xai.timeout") # optional
|
|
@@ -90,7 +93,13 @@ class ApiXAI:
|
|
|
90
93
|
if proxy:
|
|
91
94
|
kwargs["channel_options"] = []
|
|
92
95
|
kwargs["channel_options"].append(("grpc.http_proxy", proxy))
|
|
96
|
+
if management_api_key:
|
|
97
|
+
kwargs["management_api_key"] = management_api_key
|
|
98
|
+
|
|
99
|
+
if self.client is not None and self.last_client_args == kwargs:
|
|
100
|
+
return self.client
|
|
93
101
|
|
|
102
|
+
self.last_client_args = kwargs
|
|
94
103
|
self.client = xai_sdk.Client(**kwargs)
|
|
95
104
|
return self.client
|
|
96
105
|
|
|
@@ -129,8 +138,20 @@ class ApiXAI:
|
|
|
129
138
|
MODE_COMPLETION,
|
|
130
139
|
MODE_CHAT,
|
|
131
140
|
MODE_AUDIO,
|
|
132
|
-
MODE_RESEARCH
|
|
141
|
+
MODE_RESEARCH,
|
|
142
|
+
MODE_AUDIO
|
|
133
143
|
):
|
|
144
|
+
if mode == MODE_AUDIO and stream:
|
|
145
|
+
# Realtime API for audio streaming
|
|
146
|
+
is_realtime = self.realtime.begin(
|
|
147
|
+
context=context,
|
|
148
|
+
model=model,
|
|
149
|
+
extra=extra or {},
|
|
150
|
+
rt_signals=rt_signals
|
|
151
|
+
)
|
|
152
|
+
if is_realtime:
|
|
153
|
+
return True
|
|
154
|
+
|
|
134
155
|
# Audio TTS is not exposed via public SDK; treat MODE_AUDIO as chat input.
|
|
135
156
|
# NOTE: for grok-3 use Chat completions, for > grok-4 use Chat responses
|
|
136
157
|
if use_responses_api:
|