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,13 +6,13 @@
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.04 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import base64
13
13
  import datetime
14
14
  import os
15
- from typing import Optional, Dict, Any, List
15
+ from typing import Optional, Dict, Any, List, Iterable
16
16
 
17
17
  import requests
18
18
  from PySide6.QtCore import QObject, Signal, QRunnable, Slot
@@ -35,7 +35,7 @@ class Image:
35
35
  sync: bool = True
36
36
  ) -> bool:
37
37
  """
38
- Generate image(s) via xAI REST API /v1/images/generations (OpenAI-compatible).
38
+ Generate image(s) via xAI SDK image API.
39
39
  Model: grok-2-image (or -1212 variants).
40
40
 
41
41
  :param context: BridgeContext with prompt, model, ctx
@@ -60,7 +60,7 @@ class Image:
60
60
  worker = ImageWorker()
61
61
  worker.window = self.window
62
62
  worker.ctx = ctx
63
- worker.model = model.id or "grok-2-image"
63
+ worker.model = (model.id or "grok-2-image")
64
64
  worker.input_prompt = prompt
65
65
  worker.model_prompt = prompt_model
66
66
  worker.system_prompt = self.window.core.prompt.get('img')
@@ -108,8 +108,10 @@ class ImageWorker(QRunnable):
108
108
  self.raw = False
109
109
  self.num = 1
110
110
 
111
- # API
112
- self.api_url = "https://api.x.ai/v1/images/generations" # OpenAI-compatible endpoint
111
+ # SDK image_format:
112
+ # - "base64": returns raw image bytes in SDK response (preferred for local saving)
113
+ # - "url": returns URL on xAI managed storage (fallback: we download)
114
+ self.image_format = "base64"
113
115
 
114
116
  @Slot()
115
117
  def run(self):
@@ -143,48 +145,35 @@ class ImageWorker(QRunnable):
143
145
 
144
146
  self.signals.status.emit(trans('img.status.generating') + f": {self.input_prompt}...")
145
147
 
146
- cfg = self.window.core.config
147
- api_key = cfg.get("api_key_xai") or os.environ.get("XAI_API_KEY") or ""
148
- self.api_url = cfg.get("api_endpoint_xai") + "/images/generations"
149
- if not api_key:
150
- raise RuntimeError("Missing xAI API key. Set `api_key_xai` in config or XAI_API_KEY in env.")
151
-
152
- headers = {
153
- "Authorization": f"Bearer {api_key}",
154
- "Content-Type": "application/json",
155
- }
156
- payload = {
157
- "model": self.model or "grok-2-image",
158
- "prompt": self.input_prompt or "",
159
- "n": max(1, min(int(self.num), 10)),
160
- "response_format": "b64_json", # get base64 so we can save locally
161
- }
162
-
163
- r = requests.post(self.api_url, headers=headers, json=payload, timeout=180)
164
- r.raise_for_status()
165
- data = r.json()
166
-
167
- images = []
168
- for idx, img in enumerate((data.get("data") or [])[: self.num]):
169
- b64 = img.get("b64_json")
170
- if not b64:
171
- # fallback: url download
172
- url = img.get("url")
173
- if url:
174
- try:
175
- rr = requests.get(url, timeout=60)
176
- if rr.status_code == 200:
177
- images.append(rr.content)
178
- except Exception:
179
- pass
180
- continue
181
- try:
182
- images.append(base64.b64decode(b64))
183
- except Exception:
184
- continue
148
+ # use xAI SDK client
149
+ client = self.window.core.api.xai.get_client()
150
+
151
+ # enforce n range [1..10] as per docs
152
+ n = max(1, min(int(self.num or 1), 10))
185
153
 
154
+ images_bytes: List[bytes] = []
155
+ if n == 1:
156
+ # single image
157
+ resp = client.image.sample(
158
+ model=self.model or "grok-2-image",
159
+ prompt=self.input_prompt or "",
160
+ image_format=("base64" if self.image_format == "base64" else "url"),
161
+ )
162
+ images_bytes = self._extract_bytes_from_single(resp)
163
+ else:
164
+ # batch images
165
+ resp_iter = client.image.sample_batch(
166
+ model=self.model or "grok-2-image",
167
+ prompt=self.input_prompt or "",
168
+ n=n,
169
+ image_format=("base64" if self.image_format == "base64" else "url"),
170
+ )
171
+ images_bytes = self._extract_bytes_from_batch(resp_iter)
172
+
173
+ # save images to files
186
174
  paths: List[str] = []
187
- for i, content in enumerate(images):
175
+ for i, content in enumerate(images_bytes):
176
+ # generate filename
188
177
  name = (
189
178
  datetime.date.today().strftime("%Y-%m-%d") + "_" +
190
179
  datetime.datetime.now().strftime("%H-%M-%S") + "-" +
@@ -192,7 +181,7 @@ class ImageWorker(QRunnable):
192
181
  str(i + 1) + ".jpg"
193
182
  )
194
183
  path = os.path.join(self.window.core.config.get_user_dir("img"), name)
195
- self.signals.status.emit(trans('img.status.downloading') + f" ({i + 1} / {self.num}) -> {path}")
184
+ self.signals.status.emit(trans('img.status.downloading') + f" ({i + 1} / {len(images_bytes)}) -> {path}")
196
185
 
197
186
  if self.window.core.image.save_image(path, content):
198
187
  paths.append(path)
@@ -207,6 +196,119 @@ class ImageWorker(QRunnable):
207
196
  finally:
208
197
  self._cleanup()
209
198
 
199
+ # ---------- SDK response helpers ----------
200
+
201
+ def _extract_bytes_from_single(self, resp) -> List[bytes]:
202
+ """
203
+ Normalize single-image SDK response to a list of bytes.
204
+ Accepts:
205
+ - resp.image -> bytes or base64 str (docs say raw bytes)
206
+ - resp.url -> download
207
+ - dict-like/legacy: {'b64_json': ..., 'url': ...}
208
+ """
209
+ out: List[bytes] = []
210
+ try:
211
+ # preferred path: raw bytes when image_format="base64"
212
+ img_bytes = getattr(resp, "image", None)
213
+ if isinstance(img_bytes, (bytes, bytearray)):
214
+ out.append(bytes(img_bytes))
215
+ return out
216
+ if isinstance(img_bytes, str):
217
+ try:
218
+ out.append(base64.b64decode(img_bytes))
219
+ return out
220
+ except Exception:
221
+ pass
222
+
223
+ # url fallback
224
+ url = getattr(resp, "url", None)
225
+ if isinstance(url, str) and url:
226
+ try:
227
+ r = requests.get(url, timeout=60)
228
+ if r.status_code == 200:
229
+ out.append(r.content)
230
+ return out
231
+ except Exception:
232
+ pass
233
+
234
+ # dict-like fallbacks
235
+ if isinstance(resp, dict):
236
+ if "b64_json" in resp and resp["b64_json"]:
237
+ try:
238
+ out.append(base64.b64decode(resp["b64_json"]))
239
+ return out
240
+ except Exception:
241
+ pass
242
+ if "url" in resp and resp["url"]:
243
+ try:
244
+ r = requests.get(resp["url"], timeout=60)
245
+ if r.status_code == 200:
246
+ out.append(r.content)
247
+ return out
248
+ except Exception:
249
+ pass
250
+ except Exception:
251
+ pass
252
+ return out
253
+
254
+ def _extract_bytes_from_batch(self, resp_iter: Iterable) -> List[bytes]:
255
+ """
256
+ Normalize batch SDK response (iterable of images) to a list of bytes.
257
+ Handles item.image (bytes/str), item.url, dict-like or bytes directly.
258
+ """
259
+ out: List[bytes] = []
260
+ if resp_iter is None:
261
+ return out
262
+ try:
263
+ for item in resp_iter:
264
+ # bytes directly
265
+ if isinstance(item, (bytes, bytearray)):
266
+ out.append(bytes(item))
267
+ continue
268
+
269
+ # preferred: raw bytes in item.image
270
+ img_bytes = getattr(item, "image", None)
271
+ if isinstance(img_bytes, (bytes, bytearray)):
272
+ out.append(bytes(img_bytes))
273
+ continue
274
+ if isinstance(img_bytes, str):
275
+ try:
276
+ out.append(base64.b64decode(img_bytes))
277
+ continue
278
+ except Exception:
279
+ pass
280
+
281
+ # url fallback
282
+ url = getattr(item, "url", None)
283
+ if isinstance(url, str) and url:
284
+ try:
285
+ r = requests.get(url, timeout=60)
286
+ if r.status_code == 200:
287
+ out.append(r.content)
288
+ continue
289
+ except Exception:
290
+ pass
291
+
292
+ # dict-like fallbacks
293
+ if isinstance(item, dict):
294
+ if "b64_json" in item and item["b64_json"]:
295
+ try:
296
+ out.append(base64.b64decode(item["b64_json"]))
297
+ continue
298
+ except Exception:
299
+ pass
300
+ if "url" in item and item["url"]:
301
+ try:
302
+ r = requests.get(item["url"], timeout=60)
303
+ if r.status_code == 200:
304
+ out.append(r.content)
305
+ continue
306
+ except Exception:
307
+ pass
308
+ except Exception:
309
+ pass
310
+ return out
311
+
210
312
  def _cleanup(self):
211
313
  """Cleanup signals to avoid multiple calls."""
212
314
  sig = self.signals
@@ -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