pygpt-net 2.7.7__py3-none-any.whl → 2.7.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygpt_net/CHANGELOG.txt +7 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +5 -1
- pygpt_net/controller/assistant/batch.py +2 -2
- pygpt_net/controller/assistant/files.py +7 -6
- pygpt_net/controller/assistant/threads.py +0 -0
- pygpt_net/controller/chat/command.py +0 -0
- pygpt_net/controller/dialogs/confirm.py +35 -58
- pygpt_net/controller/lang/mapping.py +9 -9
- pygpt_net/controller/remote_store/{google/batch.py → batch.py} +209 -252
- pygpt_net/controller/remote_store/remote_store.py +982 -13
- pygpt_net/core/command/command.py +0 -0
- pygpt_net/core/db/viewer.py +1 -1
- pygpt_net/core/realtime/worker.py +3 -1
- pygpt_net/{controller/remote_store/google → core/remote_store/anthropic}/__init__.py +0 -1
- pygpt_net/core/remote_store/anthropic/files.py +211 -0
- pygpt_net/core/remote_store/anthropic/store.py +208 -0
- pygpt_net/core/remote_store/openai/store.py +5 -4
- pygpt_net/core/remote_store/remote_store.py +5 -1
- pygpt_net/{controller/remote_store/openai → core/remote_store/xai}/__init__.py +0 -1
- pygpt_net/core/remote_store/xai/files.py +225 -0
- pygpt_net/core/remote_store/xai/store.py +219 -0
- pygpt_net/data/config/config.json +9 -6
- pygpt_net/data/config/models.json +5 -4
- pygpt_net/data/config/settings.json +54 -1
- pygpt_net/data/icons/folder_eye.svg +1 -0
- pygpt_net/data/icons/folder_eye_filled.svg +1 -0
- pygpt_net/data/icons/folder_open.svg +1 -0
- pygpt_net/data/icons/folder_open_filled.svg +1 -0
- pygpt_net/data/locale/locale.de.ini +4 -3
- pygpt_net/data/locale/locale.en.ini +14 -4
- pygpt_net/data/locale/locale.es.ini +4 -3
- pygpt_net/data/locale/locale.fr.ini +4 -3
- pygpt_net/data/locale/locale.it.ini +4 -3
- pygpt_net/data/locale/locale.pl.ini +5 -4
- pygpt_net/data/locale/locale.uk.ini +4 -3
- pygpt_net/data/locale/locale.zh.ini +4 -3
- pygpt_net/icons.qrc +4 -0
- pygpt_net/icons_rc.py +282 -138
- pygpt_net/provider/api/anthropic/__init__.py +2 -0
- pygpt_net/provider/api/anthropic/chat.py +84 -1
- pygpt_net/provider/api/anthropic/store.py +307 -0
- pygpt_net/provider/api/anthropic/stream.py +75 -0
- pygpt_net/provider/api/anthropic/worker/__init__.py +0 -0
- pygpt_net/provider/api/anthropic/worker/importer.py +278 -0
- pygpt_net/provider/api/google/chat.py +59 -2
- pygpt_net/provider/api/google/store.py +124 -3
- pygpt_net/provider/api/google/stream.py +91 -24
- pygpt_net/provider/api/google/worker/importer.py +16 -28
- pygpt_net/provider/api/openai/assistants.py +2 -2
- pygpt_net/provider/api/openai/store.py +4 -1
- pygpt_net/provider/api/openai/worker/importer.py +19 -61
- pygpt_net/provider/api/openai/worker/importer_assistants.py +230 -0
- pygpt_net/provider/api/x_ai/__init__.py +30 -6
- pygpt_net/provider/api/x_ai/audio.py +43 -11
- pygpt_net/provider/api/x_ai/chat.py +92 -4
- pygpt_net/provider/api/x_ai/realtime/__init__.py +12 -0
- pygpt_net/provider/api/x_ai/realtime/client.py +1825 -0
- pygpt_net/provider/api/x_ai/realtime/realtime.py +198 -0
- pygpt_net/provider/api/x_ai/remote_tools.py +19 -1
- pygpt_net/provider/api/x_ai/store.py +610 -0
- pygpt_net/provider/api/x_ai/stream.py +30 -9
- pygpt_net/provider/api/x_ai/worker/importer.py +308 -0
- pygpt_net/provider/audio_input/xai_grok_voice.py +390 -0
- pygpt_net/provider/audio_output/xai_tts.py +325 -0
- pygpt_net/provider/core/config/patch.py +18 -3
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
- pygpt_net/provider/core/model/patch.py +13 -0
- pygpt_net/tools/image_viewer/tool.py +334 -34
- pygpt_net/tools/image_viewer/ui/dialogs.py +317 -21
- pygpt_net/ui/dialog/assistant.py +1 -1
- pygpt_net/ui/dialog/plugins.py +13 -5
- pygpt_net/ui/dialog/remote_store.py +552 -0
- pygpt_net/ui/dialogs.py +3 -5
- pygpt_net/ui/layout/ctx/ctx_list.py +58 -7
- pygpt_net/ui/menu/tools.py +6 -13
- pygpt_net/ui/widget/dialog/{remote_store_google.py → remote_store.py} +10 -10
- pygpt_net/ui/widget/element/button.py +4 -4
- pygpt_net/ui/widget/image/display.py +2 -2
- pygpt_net/ui/widget/lists/context.py +2 -2
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/METADATA +9 -2
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/RECORD +82 -70
- pygpt_net/controller/remote_store/google/store.py +0 -615
- pygpt_net/controller/remote_store/openai/batch.py +0 -524
- pygpt_net/controller/remote_store/openai/store.py +0 -699
- pygpt_net/ui/dialog/remote_store_google.py +0 -539
- pygpt_net/ui/dialog/remote_store_openai.py +0 -539
- pygpt_net/ui/widget/dialog/remote_store_openai.py +0 -56
- pygpt_net/ui/widget/lists/remote_store_google.py +0 -248
- pygpt_net/ui/widget/lists/remote_store_openai.py +0 -317
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.8.dist-info}/entry_points.txt +0 -0
|
@@ -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,23 @@ 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:
|
|
145
|
+
raise NotImplementedError("Not available. xAI realtime audio streaming coming soon!")
|
|
146
|
+
|
|
147
|
+
if mode == MODE_AUDIO and stream:
|
|
148
|
+
# Realtime API for audio streaming
|
|
149
|
+
is_realtime = self.realtime.begin(
|
|
150
|
+
context=context,
|
|
151
|
+
model=model,
|
|
152
|
+
extra=extra or {},
|
|
153
|
+
rt_signals=rt_signals
|
|
154
|
+
)
|
|
155
|
+
if is_realtime:
|
|
156
|
+
return True
|
|
157
|
+
|
|
134
158
|
# Audio TTS is not exposed via public SDK; treat MODE_AUDIO as chat input.
|
|
135
159
|
# NOTE: for grok-3 use Chat completions, for > grok-4 use Chat responses
|
|
136
160
|
if use_responses_api:
|
|
@@ -6,27 +6,59 @@
|
|
|
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.06 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
import base64
|
|
13
|
+
from typing import Optional, Union, List, Dict, Any
|
|
14
|
+
|
|
15
|
+
from pygpt_net.core.bridge.context import MultimodalContext
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class Audio:
|
|
16
19
|
def __init__(self, window=None):
|
|
17
20
|
"""
|
|
18
|
-
Audio
|
|
19
|
-
|
|
20
|
-
Note: As of now, the public xAI Python SDK does not expose TTS/STT or realtime audio APIs.
|
|
21
|
-
This class exists to keep provider surface compatible.
|
|
21
|
+
Audio input wrapper
|
|
22
22
|
|
|
23
23
|
:param window: Window instance
|
|
24
24
|
"""
|
|
25
25
|
self.window = window
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
def build_content(
|
|
28
|
+
self,
|
|
29
|
+
content: Optional[Union[str, list]] = None,
|
|
30
|
+
multimodal_ctx: Optional[MultimodalContext] = None,
|
|
31
|
+
) -> List[Dict[str, Any]]:
|
|
32
|
+
"""
|
|
33
|
+
Build audio content from multimodal context
|
|
34
|
+
|
|
35
|
+
:param content: previous content or input prompt
|
|
36
|
+
:param multimodal_ctx: multimodal context
|
|
37
|
+
:return: List of contents
|
|
38
|
+
"""
|
|
39
|
+
if not isinstance(content, list):
|
|
40
|
+
if content:
|
|
41
|
+
content = [
|
|
42
|
+
{
|
|
43
|
+
"type": "text",
|
|
44
|
+
"text": str(content),
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
else:
|
|
48
|
+
content = [] # if empty input return empty list
|
|
49
|
+
|
|
50
|
+
# abort if no audio input provided
|
|
51
|
+
if not multimodal_ctx.is_audio_input:
|
|
52
|
+
return content
|
|
30
53
|
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
encoded = base64.b64encode(multimodal_ctx.audio_data).decode('utf-8')
|
|
55
|
+
audio_format = multimodal_ctx.audio_format # wav by default
|
|
56
|
+
audio_data = {
|
|
57
|
+
"type": "input_audio",
|
|
58
|
+
"input_audio": {
|
|
59
|
+
"data": encoded,
|
|
60
|
+
"format": audio_format,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
content.append(audio_data)
|
|
64
|
+
return content
|
|
@@ -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 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -34,7 +34,6 @@ class Chat:
|
|
|
34
34
|
"""
|
|
35
35
|
self.window = window
|
|
36
36
|
self.input_tokens = 0
|
|
37
|
-
# Image constraints (can be overridden by config keys below)
|
|
38
37
|
self.allowed_mimes = {"image/jpeg", "image/png"}
|
|
39
38
|
self.default_image_max_bytes = 10 * 1024 * 1024 # 10 MiB default
|
|
40
39
|
|
|
@@ -319,6 +318,12 @@ class Chat:
|
|
|
319
318
|
except Exception:
|
|
320
319
|
pass
|
|
321
320
|
|
|
321
|
+
try:
|
|
322
|
+
# Attempt to auto-download file parts or references (file id)
|
|
323
|
+
self._maybe_download_files_from_response(response, ctx)
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
|
|
322
327
|
# Usage
|
|
323
328
|
try:
|
|
324
329
|
if isinstance(response, dict) and response.get("usage"):
|
|
@@ -1089,7 +1094,7 @@ class Chat:
|
|
|
1089
1094
|
|
|
1090
1095
|
def _collect_images_from_message_parts(self, parts: List[dict], ctx: CtxItem):
|
|
1091
1096
|
"""
|
|
1092
|
-
Inspect assistant message parts for image_url outputs and
|
|
1097
|
+
Inspect assistant message parts for image_url outputs and URLs.
|
|
1093
1098
|
For http(s) URLs -> add to ctx.urls; for data URLs -> save to file and add to ctx.images.
|
|
1094
1099
|
"""
|
|
1095
1100
|
try:
|
|
@@ -1098,6 +1103,25 @@ class Chat:
|
|
|
1098
1103
|
for p in parts:
|
|
1099
1104
|
if not isinstance(p, dict):
|
|
1100
1105
|
continue
|
|
1106
|
+
if p.get("type") == "file":
|
|
1107
|
+
file_id = p.get("id") or p.get("file_id")
|
|
1108
|
+
if isinstance(file_id, str):
|
|
1109
|
+
try:
|
|
1110
|
+
save = self.window.core.api.xai.store.download_to_dir(file_id)
|
|
1111
|
+
if save:
|
|
1112
|
+
if not isinstance(ctx.files, list):
|
|
1113
|
+
ctx.files = []
|
|
1114
|
+
if save not in ctx.files:
|
|
1115
|
+
ctx.files.append(save)
|
|
1116
|
+
ext = os.path.splitext(save)[1].lower().lstrip(".")
|
|
1117
|
+
if ext in ["png", "jpg", "jpeg", "gif", "bmp", "tiff", "webp"]:
|
|
1118
|
+
if not isinstance(ctx.images, list):
|
|
1119
|
+
ctx.images = []
|
|
1120
|
+
if save not in ctx.images:
|
|
1121
|
+
ctx.images.append(save)
|
|
1122
|
+
except Exception:
|
|
1123
|
+
pass
|
|
1124
|
+
continue
|
|
1101
1125
|
if p.get("type") != "image_url":
|
|
1102
1126
|
continue
|
|
1103
1127
|
img = p.get("image_url") or {}
|
|
@@ -1135,4 +1159,68 @@ class Chat:
|
|
|
1135
1159
|
"""
|
|
1136
1160
|
Return the locally estimated input tokens count.
|
|
1137
1161
|
"""
|
|
1138
|
-
return self.input_tokens
|
|
1162
|
+
return self.input_tokens
|
|
1163
|
+
|
|
1164
|
+
def _maybe_download_files_from_response(self, response, ctx: CtxItem) -> None:
|
|
1165
|
+
"""
|
|
1166
|
+
Attempt to download any files referenced by id in response payloads (dict/SDK/proto).
|
|
1167
|
+
"""
|
|
1168
|
+
def _walk(o, acc: set):
|
|
1169
|
+
if o is None:
|
|
1170
|
+
return
|
|
1171
|
+
if isinstance(o, dict):
|
|
1172
|
+
fid = o.get("file_id") or o.get("id") if o.get("type") == "file" else None
|
|
1173
|
+
if isinstance(fid, str) and fid.startswith("file-"):
|
|
1174
|
+
acc.add(fid)
|
|
1175
|
+
for v in o.values():
|
|
1176
|
+
_walk(v, acc)
|
|
1177
|
+
elif isinstance(o, (list, tuple)):
|
|
1178
|
+
for it in o:
|
|
1179
|
+
_walk(it, acc)
|
|
1180
|
+
|
|
1181
|
+
ids = set()
|
|
1182
|
+
try:
|
|
1183
|
+
if isinstance(response, dict):
|
|
1184
|
+
_walk(response, ids)
|
|
1185
|
+
else:
|
|
1186
|
+
msg = getattr(response, "message", None) or getattr(response, "output_message", None)
|
|
1187
|
+
if msg:
|
|
1188
|
+
_walk(getattr(msg, "content", None), ids)
|
|
1189
|
+
proto = getattr(response, "proto", None)
|
|
1190
|
+
if proto:
|
|
1191
|
+
ch = getattr(proto, "choices", None) or []
|
|
1192
|
+
if ch:
|
|
1193
|
+
m = getattr(ch[0], "message", None)
|
|
1194
|
+
if m:
|
|
1195
|
+
_walk(getattr(m, "content", None), ids)
|
|
1196
|
+
except Exception:
|
|
1197
|
+
pass
|
|
1198
|
+
|
|
1199
|
+
if not ids:
|
|
1200
|
+
return
|
|
1201
|
+
saved = []
|
|
1202
|
+
for fid in ids:
|
|
1203
|
+
try:
|
|
1204
|
+
p = self.window.core.api.xai.store.download_to_dir(fid)
|
|
1205
|
+
if p:
|
|
1206
|
+
saved.append(p)
|
|
1207
|
+
except Exception:
|
|
1208
|
+
continue
|
|
1209
|
+
if saved:
|
|
1210
|
+
saved = self.window.core.filesystem.make_local_list(saved)
|
|
1211
|
+
if not isinstance(ctx.files, list):
|
|
1212
|
+
ctx.files = []
|
|
1213
|
+
for p in saved:
|
|
1214
|
+
if p not in ctx.files:
|
|
1215
|
+
ctx.files.append(p)
|
|
1216
|
+
imgs = []
|
|
1217
|
+
for p in saved:
|
|
1218
|
+
ext = os.path.splitext(p)[1].lower().lstrip(".")
|
|
1219
|
+
if ext in ["png", "jpg", "jpeg", "gif", "bmp", "tiff", "webp"]:
|
|
1220
|
+
imgs.append(p)
|
|
1221
|
+
if imgs:
|
|
1222
|
+
if not isinstance(ctx.images, list):
|
|
1223
|
+
ctx.images = []
|
|
1224
|
+
for p in imgs:
|
|
1225
|
+
if p not in ctx.images:
|
|
1226
|
+
ctx.images.append(p)
|
|
@@ -0,0 +1,12 @@
|
|
|
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: 2025.08.31 23:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
from .realtime import Realtime
|