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.
Files changed (98) hide show
  1. pygpt_net/CHANGELOG.txt +12 -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/dialogs/confirm.py +35 -58
  9. pygpt_net/controller/lang/mapping.py +9 -9
  10. pygpt_net/controller/realtime/realtime.py +13 -1
  11. pygpt_net/controller/remote_store/{google/batch.py → batch.py} +209 -252
  12. pygpt_net/controller/remote_store/remote_store.py +982 -13
  13. pygpt_net/core/command/command.py +0 -0
  14. pygpt_net/core/db/viewer.py +1 -1
  15. pygpt_net/core/realtime/worker.py +3 -1
  16. pygpt_net/{controller/remote_store/google → core/remote_store/anthropic}/__init__.py +0 -1
  17. pygpt_net/core/remote_store/anthropic/files.py +211 -0
  18. pygpt_net/core/remote_store/anthropic/store.py +208 -0
  19. pygpt_net/core/remote_store/openai/store.py +5 -4
  20. pygpt_net/core/remote_store/remote_store.py +5 -1
  21. pygpt_net/{controller/remote_store/openai → core/remote_store/xai}/__init__.py +0 -1
  22. pygpt_net/core/remote_store/xai/files.py +225 -0
  23. pygpt_net/core/remote_store/xai/store.py +219 -0
  24. pygpt_net/data/config/config.json +10 -6
  25. pygpt_net/data/config/models.json +38 -22
  26. pygpt_net/data/config/settings.json +54 -1
  27. pygpt_net/data/icons/folder_eye.svg +1 -0
  28. pygpt_net/data/icons/folder_eye_filled.svg +1 -0
  29. pygpt_net/data/icons/folder_open.svg +1 -0
  30. pygpt_net/data/icons/folder_open_filled.svg +1 -0
  31. pygpt_net/data/locale/locale.de.ini +4 -3
  32. pygpt_net/data/locale/locale.en.ini +14 -4
  33. pygpt_net/data/locale/locale.es.ini +4 -3
  34. pygpt_net/data/locale/locale.fr.ini +4 -3
  35. pygpt_net/data/locale/locale.it.ini +4 -3
  36. pygpt_net/data/locale/locale.pl.ini +5 -4
  37. pygpt_net/data/locale/locale.uk.ini +4 -3
  38. pygpt_net/data/locale/locale.zh.ini +4 -3
  39. pygpt_net/icons.qrc +4 -0
  40. pygpt_net/icons_rc.py +282 -138
  41. pygpt_net/provider/api/anthropic/__init__.py +2 -0
  42. pygpt_net/provider/api/anthropic/chat.py +84 -1
  43. pygpt_net/provider/api/anthropic/store.py +307 -0
  44. pygpt_net/provider/api/anthropic/stream.py +75 -0
  45. pygpt_net/provider/api/anthropic/worker/__init__.py +0 -0
  46. pygpt_net/provider/api/anthropic/worker/importer.py +278 -0
  47. pygpt_net/provider/api/google/chat.py +59 -2
  48. pygpt_net/provider/api/google/realtime/client.py +70 -24
  49. pygpt_net/provider/api/google/realtime/realtime.py +48 -12
  50. pygpt_net/provider/api/google/store.py +124 -3
  51. pygpt_net/provider/api/google/stream.py +91 -24
  52. pygpt_net/provider/api/google/worker/importer.py +16 -28
  53. pygpt_net/provider/api/openai/assistants.py +2 -2
  54. pygpt_net/provider/api/openai/realtime/realtime.py +26 -6
  55. pygpt_net/provider/api/openai/store.py +4 -1
  56. pygpt_net/provider/api/openai/worker/importer.py +19 -61
  57. pygpt_net/provider/api/openai/worker/importer_assistants.py +230 -0
  58. pygpt_net/provider/api/x_ai/__init__.py +27 -6
  59. pygpt_net/provider/api/x_ai/audio.py +43 -11
  60. pygpt_net/provider/api/x_ai/chat.py +92 -4
  61. pygpt_net/provider/api/x_ai/realtime/__init__.py +12 -0
  62. pygpt_net/provider/api/x_ai/realtime/client.py +1864 -0
  63. pygpt_net/provider/api/x_ai/realtime/realtime.py +213 -0
  64. pygpt_net/provider/api/x_ai/remote_tools.py +102 -1
  65. pygpt_net/provider/api/x_ai/store.py +610 -0
  66. pygpt_net/provider/api/x_ai/stream.py +30 -9
  67. pygpt_net/provider/api/x_ai/tools.py +51 -0
  68. pygpt_net/provider/api/x_ai/worker/importer.py +308 -0
  69. pygpt_net/provider/audio_input/xai_grok_voice.py +390 -0
  70. pygpt_net/provider/audio_output/xai_tts.py +325 -0
  71. pygpt_net/provider/core/config/patch.py +29 -3
  72. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
  73. pygpt_net/provider/core/model/patch.py +49 -1
  74. pygpt_net/tools/image_viewer/tool.py +334 -34
  75. pygpt_net/tools/image_viewer/ui/dialogs.py +317 -21
  76. pygpt_net/ui/dialog/assistant.py +1 -1
  77. pygpt_net/ui/dialog/plugins.py +13 -5
  78. pygpt_net/ui/dialog/remote_store.py +552 -0
  79. pygpt_net/ui/dialogs.py +3 -5
  80. pygpt_net/ui/layout/ctx/ctx_list.py +58 -7
  81. pygpt_net/ui/menu/tools.py +6 -13
  82. pygpt_net/ui/widget/dialog/{remote_store_google.py → remote_store.py} +10 -10
  83. pygpt_net/ui/widget/element/button.py +4 -4
  84. pygpt_net/ui/widget/image/display.py +2 -2
  85. pygpt_net/ui/widget/lists/context.py +2 -2
  86. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/METADATA +14 -2
  87. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/RECORD +87 -75
  88. pygpt_net/controller/remote_store/google/store.py +0 -615
  89. pygpt_net/controller/remote_store/openai/batch.py +0 -524
  90. pygpt_net/controller/remote_store/openai/store.py +0 -699
  91. pygpt_net/ui/dialog/remote_store_google.py +0 -539
  92. pygpt_net/ui/dialog/remote_store_openai.py +0 -539
  93. pygpt_net/ui/widget/dialog/remote_store_openai.py +0 -56
  94. pygpt_net/ui/widget/lists/remote_store_google.py +0 -248
  95. pygpt_net/ui/widget/lists/remote_store_openai.py +0 -317
  96. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/LICENSE +0 -0
  97. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/WHEEL +0 -0
  98. {pygpt_net-2.7.7.dist-info → pygpt_net-2.7.9.dist-info}/entry_points.txt +0 -0
@@ -26,6 +26,7 @@ class Tools:
26
26
 
27
27
  - prepare(): legacy OpenAI-compatible dicts (kept for compatibility if needed).
28
28
  - prepare_sdk_tools(): xAI SDK client-side tool descriptors for Chat Responses.
29
+ - prepare_realtime(): Realtime/WebSocket-compatible function tools.
29
30
 
30
31
  :param window: Window instance
31
32
  """
@@ -168,4 +169,54 @@ class Tools:
168
169
  ))
169
170
  except Exception:
170
171
  continue
172
+ return tools
173
+
174
+ def prepare_realtime(self, functions: list) -> List[dict]:
175
+ """
176
+ Prepare function tools for Realtime/WebSocket sessions.
177
+
178
+ The returned structure matches the Realtime "tools" schema:
179
+ [
180
+ {
181
+ "type": "function",
182
+ "function": {
183
+ "name": "...",
184
+ "description": "...",
185
+ "parameters": { ... JSON Schema ... }
186
+ }
187
+ }
188
+ ]
189
+
190
+ :param functions: List of functions with keys: name (str), desc (str), params (JSON Schema str)
191
+ :return: List of function tool descriptors
192
+ """
193
+ if not functions or not isinstance(functions, list):
194
+ return []
195
+
196
+ tools: List[dict] = []
197
+ for fn in functions:
198
+ name = str(fn.get("name") or "").strip()
199
+ if not name:
200
+ continue
201
+ desc = fn.get("desc") or ""
202
+ params: Optional[dict] = {}
203
+ if fn.get("params"):
204
+ try:
205
+ params = json.loads(fn["params"])
206
+ except Exception:
207
+ params = {}
208
+ params = self._sanitize_schema(params or {})
209
+ if not params.get("type"):
210
+ params["type"] = "object"
211
+ else:
212
+ params = {"type": "object"}
213
+
214
+ tools.append({
215
+ "type": "function",
216
+ "function": {
217
+ "name": name,
218
+ "description": desc,
219
+ "parameters": params,
220
+ }
221
+ })
171
222
  return tools
@@ -0,0 +1,308 @@
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.06 06: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 (xAI Collections)
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
+ batch = self.window.controller.remote_store.batch
31
+ if mode == "import_files":
32
+ batch.handle_imported_files_failed(err)
33
+ elif mode == "truncate_files":
34
+ batch.handle_truncated_files_failed(err)
35
+ elif mode == "upload_files":
36
+ batch.handle_uploaded_files_failed(err)
37
+ elif mode in "vector_stores":
38
+ batch.handle_imported_stores_failed(err)
39
+ elif mode in "truncate_vector_stores":
40
+ batch.handle_truncated_stores_failed(err)
41
+ elif mode in "refresh_vector_stores":
42
+ batch.handle_refreshed_stores_failed(err)
43
+
44
+ @Slot(str, str, int)
45
+ def handle_finished(self, mode: str, store_id: str = None, num: int = 0):
46
+ batch = self.window.controller.remote_store.batch
47
+ if mode == "import_files":
48
+ batch.handle_imported_files(num)
49
+ elif mode == "truncate_files":
50
+ batch.handle_truncated_files(store_id, num)
51
+ elif mode == "upload_files":
52
+ batch.handle_uploaded_files(num)
53
+ elif mode == "vector_stores":
54
+ batch.handle_imported_stores(num)
55
+ elif mode == "truncate_vector_stores":
56
+ batch.handle_truncated_stores(num)
57
+ elif mode == "refresh_vector_stores":
58
+ batch.handle_refreshed_stores(num)
59
+
60
+ @Slot(str, str)
61
+ def handle_status(self, mode: str, msg: str):
62
+ self.window.controller.assistant.batch.handle_status_change(mode, msg)
63
+
64
+ @Slot(str, str)
65
+ def handle_log(self, mode: str, msg: str):
66
+ self.window.controller.assistant.threads.log(mode + ": " + msg)
67
+
68
+ # ---------- Vector stores (Collections) ----------
69
+
70
+ def import_vector_stores(self):
71
+ """Import collections"""
72
+ self.worker = ImportWorker()
73
+ self.worker.window = self.window
74
+ self.worker.mode = "vector_stores"
75
+ self.connect_signals(self.worker)
76
+ self.window.threadpool.start(self.worker)
77
+
78
+ def truncate_vector_stores(self):
79
+ """Delete collections"""
80
+ self.worker = ImportWorker()
81
+ self.worker.window = self.window
82
+ self.worker.mode = "truncate_vector_stores"
83
+ self.connect_signals(self.worker)
84
+ self.window.threadpool.start(self.worker)
85
+
86
+ def refresh_vector_stores(self):
87
+ """Refresh collections"""
88
+ self.worker = ImportWorker()
89
+ self.worker.window = self.window
90
+ self.worker.mode = "refresh_vector_stores"
91
+ self.connect_signals(self.worker)
92
+ self.window.threadpool.start(self.worker)
93
+
94
+ # ---------- Files (documents) ----------
95
+
96
+ def truncate_files(self, store_id: str = None):
97
+ """Remove documents from one/all collections"""
98
+ self.worker = ImportWorker()
99
+ self.worker.window = self.window
100
+ self.worker.mode = "truncate_files"
101
+ self.worker.store_id = store_id
102
+ self.connect_signals(self.worker)
103
+ self.window.threadpool.start(self.worker)
104
+
105
+ def upload_files(self, store_id: str, files: list = None):
106
+ """Upload files to a collection"""
107
+ self.worker = ImportWorker()
108
+ self.worker.window = self.window
109
+ self.worker.mode = "upload_files"
110
+ self.worker.store_id = store_id
111
+ self.worker.files = files or []
112
+ self.connect_signals(self.worker)
113
+ self.window.threadpool.start(self.worker)
114
+
115
+ def import_files(self, store_id: str = None):
116
+ """Import documents from one/all collections"""
117
+ self.worker = ImportWorker()
118
+ self.worker.window = self.window
119
+ self.worker.mode = "import_files"
120
+ self.worker.store_id = store_id
121
+ self.connect_signals(self.worker)
122
+ self.window.threadpool.start(self.worker)
123
+
124
+ def connect_signals(self, worker):
125
+ worker.signals.finished.connect(self.handle_finished)
126
+ worker.signals.error.connect(self.handle_error)
127
+ worker.signals.status.connect(self.handle_status)
128
+ worker.signals.log.connect(self.handle_log)
129
+
130
+
131
+ class ImportWorkerSignals(QObject):
132
+ status = Signal(str, str) # mode, message
133
+ finished = Signal(str, str, int) # mode, store_id, num
134
+ error = Signal(str, object) # mode, error
135
+ log = Signal(str, str) # mode, message
136
+
137
+
138
+ class ImportWorker(QRunnable):
139
+ """Import worker (xAI Collections)"""
140
+ def __init__(self, *args, **kwargs):
141
+ super().__init__()
142
+ self.signals = ImportWorkerSignals()
143
+ self.window = None
144
+ self.mode = "vector_stores"
145
+ self.store_id = None
146
+ self.files = []
147
+
148
+ @Slot()
149
+ def run(self):
150
+ try:
151
+ if self.mode == "vector_stores":
152
+ if self.import_vector_stores():
153
+ self.import_files()
154
+ elif self.mode == "truncate_vector_stores":
155
+ self.truncate_vector_stores()
156
+ elif self.mode == "refresh_vector_stores":
157
+ self.refresh_vector_stores()
158
+ elif self.mode == "truncate_files":
159
+ self.truncate_files()
160
+ elif self.mode == "import_files":
161
+ self.import_files()
162
+ elif self.mode == "upload_files":
163
+ self.upload_files()
164
+ except Exception as e:
165
+ self.signals.error.emit(self.mode, e)
166
+ finally:
167
+ self.cleanup()
168
+
169
+ # ---------- Collections ----------
170
+
171
+ def import_vector_stores(self, silent: bool = False) -> bool:
172
+ try:
173
+ self.log("Importing collections...")
174
+ self.window.core.remote_store.xai.clear()
175
+ items = {}
176
+ self.window.core.api.xai.store.import_collections_collections(items, callback=self.callback)
177
+ self.window.core.remote_store.xai.import_items(items)
178
+ if not silent:
179
+ self.signals.finished.emit("vector_stores", self.store_id, len(items))
180
+ return True
181
+ except Exception as e:
182
+ self.log("API error: {}".format(e))
183
+ self.signals.error.emit("vector_stores", e)
184
+ return False
185
+
186
+ def truncate_vector_stores(self, silent: bool = False) -> bool:
187
+ try:
188
+ self.log("Truncating collections...")
189
+ num = self.window.core.api.xai.store.remove_all_collections_collections(callback=self.callback)
190
+ self.window.core.remote_store.xai.items = {}
191
+ self.window.core.remote_store.xai.save()
192
+ if not silent:
193
+ self.signals.finished.emit("truncate_vector_stores", self.store_id, num)
194
+ return True
195
+ except Exception as e:
196
+ self.log("API error: {}".format(e))
197
+ self.signals.error.emit("truncate_vector_stores", e)
198
+ return False
199
+
200
+ def refresh_vector_stores(self, silent: bool = False) -> bool:
201
+ try:
202
+ self.log("Refreshing collections...")
203
+ num = 0
204
+ stores = self.window.core.remote_store.xai.items
205
+ for id in list(stores.keys()):
206
+ store = stores[id]
207
+ try:
208
+ self.window.controller.remote_store.refresh_store(store, update=False, provider="xai")
209
+ num += 1
210
+ except Exception as e:
211
+ self.log("Failed to refresh collection: {}".format(id))
212
+ self.window.core.debug.log(e)
213
+ if not silent:
214
+ self.signals.finished.emit("refresh_vector_stores", self.store_id, num)
215
+ return True
216
+ except Exception as e:
217
+ self.log("API error: {}".format(e))
218
+ self.signals.error.emit("refresh_vector_stores", e)
219
+ return False
220
+
221
+ # ---------- Documents ----------
222
+
223
+ def truncate_files(self, silent: bool = False) -> bool:
224
+ try:
225
+ if self.store_id is None:
226
+ self.log("Truncating all collection documents...")
227
+ self.window.core.remote_store.xai.files.truncate() # clear all local + detach from all collections
228
+ num = self.window.core.api.xai.store.remove_files(callback=self.callback) # delete remote files
229
+ else:
230
+ self.log("Truncating documents for collection: {}".format(self.store_id))
231
+ self.window.core.remote_store.xai.files.truncate(self.store_id) # clear local + detach from this collection
232
+ num = self.window.core.api.xai.store.remove_from_collection_collections(
233
+ self.store_id,
234
+ callback=self.callback,
235
+ )
236
+ if not silent:
237
+ self.signals.finished.emit("truncate_files", self.store_id, num)
238
+ return True
239
+ except Exception as e:
240
+ self.log("API error: {}".format(e))
241
+ self.signals.error.emit("truncate_files", e)
242
+ return False
243
+
244
+ def upload_files(self, silent: bool = False) -> bool:
245
+ num = 0
246
+ try:
247
+ self.log("Uploading files to collection...")
248
+ for path in self.files:
249
+ try:
250
+ doc = self.window.core.api.xai.store.upload_to_collection_collections(self.store_id, path)
251
+ if doc is not None:
252
+ self.window.core.remote_store.xai.files.insert(self.store_id, doc.file_metadata)
253
+ num += 1
254
+ msg = "Uploaded file: {}/{}".format(num, len(self.files))
255
+ self.signals.status.emit("upload_files", msg)
256
+ self.log(msg)
257
+ else:
258
+ self.signals.status.emit("upload_files", "Failed to upload: {}".format(os.path.basename(path)))
259
+ except Exception as e:
260
+ self.window.core.debug.log(e)
261
+ self.signals.status.emit("upload_files", "Failed to upload: {}".format(os.path.basename(path)))
262
+ if not silent:
263
+ self.signals.finished.emit("upload_files", self.store_id, num)
264
+ return True
265
+ except Exception as e:
266
+ self.log("API error: {}".format(e))
267
+ self.signals.error.emit("upload_files", e)
268
+ return False
269
+
270
+ def import_files(self, silent: bool = False) -> bool:
271
+ try:
272
+ if self.store_id is None:
273
+ self.log("Importing all collection documents...")
274
+ self.window.core.remote_store.xai.files.truncate_local() # clear local DB (all)
275
+ num = self.window.core.api.xai.store.import_collections_files_collections(callback=self.callback)
276
+ else:
277
+ self.log("Importing documents for collection: {}".format(self.store_id))
278
+ self.window.core.remote_store.xai.files.truncate_local(self.store_id) # clear local DB (store)
279
+ items = self.window.core.api.xai.store.import_collection_files_collections(
280
+ self.store_id,
281
+ [],
282
+ callback=self.callback,
283
+ )
284
+ num = len(items)
285
+ if not silent:
286
+ self.signals.finished.emit("import_files", self.store_id, num)
287
+ return True
288
+ except Exception as e:
289
+ self.log("API error: {}".format(e))
290
+ self.signals.error.emit("import_files", e)
291
+ return False
292
+
293
+ # ---------- Utils ----------
294
+
295
+ def callback(self, msg: str):
296
+ self.log(msg)
297
+
298
+ def log(self, msg: str):
299
+ self.signals.log.emit(self.mode, msg)
300
+
301
+ def cleanup(self):
302
+ sig = self.signals
303
+ self.signals = None
304
+ if sig is not None:
305
+ try:
306
+ sig.deleteLater()
307
+ except RuntimeError:
308
+ pass