pygpt-net 2.6.36__py3-none-any.whl → 2.6.37__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 (61) hide show
  1. pygpt_net/CHANGELOG.txt +5 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/chat/handler/anthropic_stream.py +166 -0
  4. pygpt_net/controller/chat/handler/google_stream.py +181 -0
  5. pygpt_net/controller/chat/handler/langchain_stream.py +24 -0
  6. pygpt_net/controller/chat/handler/llamaindex_stream.py +47 -0
  7. pygpt_net/controller/chat/handler/openai_stream.py +260 -0
  8. pygpt_net/controller/chat/handler/utils.py +210 -0
  9. pygpt_net/controller/chat/handler/worker.py +566 -0
  10. pygpt_net/controller/chat/handler/xai_stream.py +135 -0
  11. pygpt_net/controller/chat/stream.py +1 -1
  12. pygpt_net/controller/ctx/ctx.py +1 -1
  13. pygpt_net/controller/model/editor.py +3 -0
  14. pygpt_net/core/bridge/context.py +35 -35
  15. pygpt_net/core/bridge/worker.py +40 -16
  16. pygpt_net/core/render/web/body.py +29 -34
  17. pygpt_net/data/config/config.json +10 -3
  18. pygpt_net/data/config/models.json +3 -3
  19. pygpt_net/data/config/settings.json +105 -0
  20. pygpt_net/data/css/style.dark.css +2 -3
  21. pygpt_net/data/css/style.light.css +2 -3
  22. pygpt_net/data/locale/locale.de.ini +3 -1
  23. pygpt_net/data/locale/locale.en.ini +19 -1
  24. pygpt_net/data/locale/locale.es.ini +3 -1
  25. pygpt_net/data/locale/locale.fr.ini +3 -1
  26. pygpt_net/data/locale/locale.it.ini +3 -1
  27. pygpt_net/data/locale/locale.pl.ini +4 -2
  28. pygpt_net/data/locale/locale.uk.ini +3 -1
  29. pygpt_net/data/locale/locale.zh.ini +3 -1
  30. pygpt_net/provider/api/__init__.py +5 -3
  31. pygpt_net/provider/api/anthropic/__init__.py +190 -29
  32. pygpt_net/provider/api/anthropic/audio.py +30 -0
  33. pygpt_net/provider/api/anthropic/chat.py +341 -0
  34. pygpt_net/provider/api/anthropic/image.py +25 -0
  35. pygpt_net/provider/api/anthropic/tools.py +266 -0
  36. pygpt_net/provider/api/anthropic/vision.py +142 -0
  37. pygpt_net/provider/api/google/chat.py +2 -2
  38. pygpt_net/provider/api/google/tools.py +58 -48
  39. pygpt_net/provider/api/google/vision.py +7 -1
  40. pygpt_net/provider/api/openai/chat.py +1 -0
  41. pygpt_net/provider/api/openai/vision.py +6 -0
  42. pygpt_net/provider/api/x_ai/__init__.py +247 -0
  43. pygpt_net/provider/api/x_ai/audio.py +32 -0
  44. pygpt_net/provider/api/x_ai/chat.py +968 -0
  45. pygpt_net/provider/api/x_ai/image.py +208 -0
  46. pygpt_net/provider/api/x_ai/remote.py +262 -0
  47. pygpt_net/provider/api/x_ai/tools.py +120 -0
  48. pygpt_net/provider/api/x_ai/vision.py +119 -0
  49. pygpt_net/provider/core/config/patch.py +28 -0
  50. pygpt_net/provider/llms/anthropic.py +4 -2
  51. pygpt_net/ui/base/config_dialog.py +5 -11
  52. pygpt_net/ui/dialog/models.py +2 -4
  53. pygpt_net/ui/dialog/plugins.py +40 -43
  54. pygpt_net/ui/widget/element/labels.py +19 -3
  55. pygpt_net/ui/widget/textarea/web.py +1 -1
  56. {pygpt_net-2.6.36.dist-info → pygpt_net-2.6.37.dist-info}/METADATA +11 -6
  57. {pygpt_net-2.6.36.dist-info → pygpt_net-2.6.37.dist-info}/RECORD +60 -41
  58. pygpt_net/controller/chat/handler/stream_worker.py +0 -1136
  59. {pygpt_net-2.6.36.dist-info → pygpt_net-2.6.37.dist-info}/LICENSE +0 -0
  60. {pygpt_net-2.6.36.dist-info → pygpt_net-2.6.37.dist-info}/WHEEL +0 -0
  61. {pygpt_net-2.6.36.dist-info → pygpt_net-2.6.37.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,119 @@
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.09.05 01:00:00 #
10
+ # ================================================== #
11
+
12
+ import os
13
+ from typing import Optional, Dict, List
14
+
15
+ from pygpt_net.item.attachment import AttachmentItem
16
+ from pygpt_net.item.ctx import CtxItem
17
+
18
+
19
+ class Vision:
20
+ def __init__(self, window=None):
21
+ """
22
+ Vision helpers for xAI (image inputs as data: URIs).
23
+
24
+ :param window: Window instance
25
+ """
26
+ self.window = window
27
+ self.attachments: Dict[str, str] = {}
28
+ self.urls: List[str] = []
29
+ self.input_tokens = 0
30
+ self.allowed_mimes = {"image/jpeg", "image/png"}
31
+
32
+ def build_images_for_chat(self, attachments: Optional[Dict[str, AttachmentItem]]) -> List[str]:
33
+ """
34
+ Build image inputs for xai_sdk.chat.image(...).
35
+ Returns list of image sources (URLs or data: URIs).
36
+
37
+ :param attachments: Attachments dict (id -> AttachmentItem)
38
+ :return: List of image sources
39
+ """
40
+ import base64
41
+
42
+ images: List[str] = []
43
+ self.attachments = {}
44
+ self.urls = []
45
+
46
+ if not attachments:
47
+ return images
48
+
49
+ for id_, att in (attachments or {}).items():
50
+ try:
51
+ if att.path and self.window.core.api.xai.vision.is_image(att.path):
52
+ mime = self.window.core.api.xai.vision.guess_mime(att.path)
53
+ # Accept only JPEG/PNG for SDK too (for consistency)
54
+ #if mime not in self.allowed_mimes:
55
+ # continue
56
+ with open(att.path, "rb") as f:
57
+ b64 = base64.b64encode(f.read()).decode("utf-8")
58
+ images.append(f"data:{mime};base64,{b64}")
59
+ self.attachments[id_] = att.path
60
+ att.consumed = True
61
+ except Exception:
62
+ continue
63
+ return images
64
+
65
+ def is_image(self, path: str) -> bool:
66
+ """
67
+ Return True if path looks like an image file.
68
+
69
+ :param path: File path
70
+ """
71
+ return path.lower().endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif', '.webp'))
72
+
73
+ def guess_mime(self, path: str) -> str:
74
+ """Guess MIME by extension.
75
+
76
+ :param path: File path
77
+ :return: MIME type string
78
+ """
79
+ ext = os.path.splitext(path)[1].lower().lstrip(".")
80
+ if ext in ("jpg", "jpeg"):
81
+ return "image/jpeg"
82
+ if ext == "png":
83
+ return "image/png"
84
+ if ext == "gif":
85
+ return "image/gif"
86
+ if ext == "bmp":
87
+ return "image/bmp"
88
+ if ext == "webp":
89
+ return "image/webp"
90
+ if ext == "tiff":
91
+ return "image/tiff"
92
+ return "image/jpeg"
93
+
94
+ def append_images(self, ctx: CtxItem):
95
+ """
96
+ Append sent images list to context for UI/history.
97
+
98
+ :param ctx: CtxItem
99
+ """
100
+ images = self.get_attachments()
101
+ if len(images) > 0:
102
+ ctx.images = self.window.core.filesystem.make_local_list(list(images.values()))
103
+
104
+ def get_attachments(self) -> Dict[str, str]:
105
+ return self.attachments
106
+
107
+ def get_urls(self) -> List[str]:
108
+ return self.urls
109
+
110
+ def reset_tokens(self):
111
+ self.input_tokens = 0
112
+
113
+ def get_used_tokens(self) -> int:
114
+ return self.input_tokens
115
+
116
+ def reset(self):
117
+ self.attachments = {}
118
+ self.urls = []
119
+ self.input_tokens = 0
@@ -2447,6 +2447,34 @@ class Patch:
2447
2447
  patch_css('web-blocks.css', True)
2448
2448
  updated = True
2449
2449
 
2450
+ # < 2.6.37
2451
+ if old < parse_version("2.6.37"):
2452
+ print("Migrating config from < 2.6.37...")
2453
+
2454
+ # add: label-desc CSS
2455
+ patch_css('style.dark.css', True)
2456
+ patch_css('style.light.css', True)
2457
+
2458
+ # add: Anthropic SDK
2459
+ if "api_native_anthropic" not in data:
2460
+ data["api_native_anthropic"] = True
2461
+ if "remote_tools.anthropic.web_search" not in data:
2462
+ data["remote_tools.anthropic.web_search"] = True
2463
+
2464
+ # add: xAI SDK
2465
+ if "api_native_xai" not in data:
2466
+ data["api_native_xai"] = True
2467
+ if "remote_tools.xai.mode" not in data:
2468
+ data["remote_tools.xai.mode"] = "auto"
2469
+ if "remote_tools.xai.sources.web" not in data:
2470
+ data["remote_tools.xai.sources.web"] = True
2471
+ if "remote_tools.xai.sources.x" not in data:
2472
+ data["remote_tools.xai.sources.x"] = True
2473
+ if "remote_tools.xai.sources.news" not in data:
2474
+ data["remote_tools.xai.sources.news"] = False
2475
+
2476
+ updated = True
2477
+
2450
2478
  # update file
2451
2479
  migrated = False
2452
2480
  if updated:
@@ -15,7 +15,7 @@ from llama_index.core.base.embeddings.base import BaseEmbedding
15
15
  from llama_index.core.llms.llm import BaseLLM as LlamaBaseLLM
16
16
 
17
17
  from pygpt_net.core.types import (
18
- MODE_LLAMA_INDEX,
18
+ MODE_LLAMA_INDEX, MODE_CHAT,
19
19
  )
20
20
  from pygpt_net.provider.llms.base import BaseLLM
21
21
  from pygpt_net.item.model import ModelItem
@@ -93,7 +93,9 @@ class AnthropicLLM(BaseLLM):
93
93
  :param window: window instance
94
94
  :return: list of models
95
95
  """
96
- client = window.core.api.anthropic.get_client()
96
+ model = ModelItem()
97
+ model.provider = "anthropic"
98
+ client = window.core.api.anthropic.get_client(MODE_CHAT, model)
97
99
  models_list = client.models.list()
98
100
  items = []
99
101
  if models_list.data:
@@ -6,14 +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.08.22 10:00:00 #
9
+ # Updated Date: 2025.09.04 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
13
13
  from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QSizePolicy, QWidget, QFrame
14
- from PySide6.QtGui import QFont
15
14
 
16
- from pygpt_net.ui.widget.element.labels import TitleLabel, UrlLabel
15
+ from pygpt_net.ui.widget.element.labels import TitleLabel, UrlLabel, DescLabel, BaseLabel
17
16
  from pygpt_net.ui.widget.option.checkbox import OptionCheckbox
18
17
  from pygpt_net.ui.widget.option.checkbox_list import OptionCheckboxList
19
18
  from pygpt_net.ui.widget.option.combo import OptionCombo
@@ -123,7 +122,7 @@ class BaseConfigDialog:
123
122
  if extra.get('bold'):
124
123
  nodes[label_key] = TitleLabel(txt)
125
124
  else:
126
- nodes[label_key] = QLabel(txt)
125
+ nodes[label_key] = BaseLabel(txt)
127
126
  nodes[label_key].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
128
127
  nodes[label_key].setMinimumWidth(120)
129
128
  nodes[label_key].setWordWrap(True)
@@ -169,7 +168,7 @@ class BaseConfigDialog:
169
168
  if extra.get('bold'):
170
169
  nodes[label_key] = TitleLabel(txt)
171
170
  else:
172
- nodes[label_key] = QLabel(txt)
171
+ nodes[label_key] = BaseLabel(txt)
173
172
  nodes[label_key].setMinimumHeight(30)
174
173
  nodes[label_key].setWordWrap(True)
175
174
 
@@ -234,12 +233,7 @@ class BaseConfigDialog:
234
233
  :param text: text (to translate)
235
234
  :return: QLabel
236
235
  """
237
- desc = trans(text)
238
- label = QLabel(desc)
239
- label.setWordWrap(True)
240
- label.setMaximumHeight(80)
241
- label.setStyleSheet("font-size: 10px;")
242
- return label
236
+ return DescLabel(trans(text))
243
237
 
244
238
  def add_urls(self, urls, align=Qt.AlignLeft) -> QWidget:
245
239
  """
@@ -19,7 +19,7 @@ from PySide6.QtWidgets import QPushButton, QHBoxLayout, QLabel, QVBoxLayout, QSc
19
19
  from pygpt_net.item.model import ModelItem
20
20
  from pygpt_net.ui.widget.dialog.model import ModelDialog
21
21
  from pygpt_net.ui.widget.element.group import CollapsedGroup
22
- from pygpt_net.ui.widget.element.labels import UrlLabel
22
+ from pygpt_net.ui.widget.element.labels import UrlLabel, DescLabel
23
23
  from pygpt_net.ui.widget.lists.model_editor import ModelEditorList
24
24
  from pygpt_net.ui.widget.option.checkbox import OptionCheckbox
25
25
  from pygpt_net.ui.widget.option.checkbox_list import OptionCheckboxList
@@ -303,10 +303,8 @@ class Models:
303
303
  layout.addWidget(widget)
304
304
 
305
305
  if desc:
306
- self.window.ui.nodes[desc_key] = QLabel(desc)
307
- self.window.ui.nodes[desc_key].setWordWrap(True)
306
+ self.window.ui.nodes[desc_key] = DescLabel(desc)
308
307
  self.window.ui.nodes[desc_key].setMaximumHeight(40)
309
- self.window.ui.nodes[desc_key].setStyleSheet("font-size: 10px;")
310
308
  layout.addWidget(self.window.ui.nodes[desc_key])
311
309
 
312
310
  line = self.add_line() # TODO: change name to separator
@@ -6,18 +6,18 @@
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.07.18 18:00:00 #
9
+ # Updated Date: 2025.09.04 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
13
13
  from PySide6.QtGui import QStandardItemModel
14
- from PySide6.QtWidgets import QPushButton, QHBoxLayout, QLabel, QVBoxLayout, QScrollArea, QWidget, QTabWidget, QFrame, \
14
+ from PySide6.QtWidgets import QPushButton, QHBoxLayout, QVBoxLayout, QScrollArea, QWidget, QTabWidget, QFrame, \
15
15
  QSplitter, QSizePolicy
16
16
 
17
17
  from pygpt_net.plugin.base.plugin import BasePlugin
18
18
  from pygpt_net.ui.widget.dialog.settings_plugin import PluginSettingsDialog
19
19
  from pygpt_net.ui.widget.element.group import CollapsedGroup
20
- from pygpt_net.ui.widget.element.labels import UrlLabel, HelpLabel
20
+ from pygpt_net.ui.widget.element.labels import UrlLabel, HelpLabel, DescLabel, BaseLabel
21
21
  from pygpt_net.ui.widget.lists.plugin import PluginList
22
22
  from pygpt_net.ui.widget.option.checkbox import OptionCheckbox
23
23
  from pygpt_net.ui.widget.option.checkbox_list import OptionCheckboxList
@@ -77,14 +77,15 @@ class Plugins:
77
77
  self.window.ui.tabs['plugin.settings.tabs'] = {}
78
78
 
79
79
  sorted_ids = self.window.core.plugins.get_ids(sort=True)
80
+ get_plugin = self.window.core.plugins.get
80
81
 
81
82
  # build plugin settings tabs
82
83
  for id in sorted_ids:
83
84
  content_tabs = {}
84
85
  scroll_tabs = {}
85
86
 
86
- plugin = self.window.core.plugins.get(id)
87
- parent_id = "plugin." + id
87
+ plugin = get_plugin(id)
88
+ parent_id = f"plugin.{id}"
88
89
 
89
90
  # create plugin options entry if not exists
90
91
  if parent_id not in self.window.ui.config:
@@ -137,7 +138,7 @@ class Plugins:
137
138
  # append advanced options at the end
138
139
  if len(advanced_keys) > 0:
139
140
  groups = {}
140
- group_id = 'plugin.settings.advanced' + '.' + id
141
+ group_id = f"plugin.settings.advanced.{id}"
141
142
  for key in widgets:
142
143
  if key not in advanced_keys: # ignore non-advanced options
143
144
  continue
@@ -157,7 +158,7 @@ class Plugins:
157
158
 
158
159
  # add advanced options group to scrolls
159
160
  for tab_id in groups:
160
- full_id = group_id + '.' + tab_id
161
+ full_id = f"{group_id}.{tab_id}"
161
162
  content_tabs[tab_id].addWidget(groups[tab_id])
162
163
  self.window.ui.groups[full_id] = groups[tab_id]
163
164
 
@@ -167,17 +168,16 @@ class Plugins:
167
168
 
168
169
  # set description, translate if localization is enabled
169
170
  name_txt = plugin.name
170
- desc_key = 'plugin.settings.' + id + '.desc'
171
+ desc_key = f"plugin.settings.{id}.desc"
171
172
  desc_txt = plugin.description
172
173
  if plugin.use_locale:
173
- domain = 'plugin.' + plugin.id
174
+ domain = f"plugin.{plugin.id}"
174
175
  name_txt = trans('plugin.name', False, domain)
175
176
  desc_txt = trans('plugin.description', False, domain)
176
177
 
177
- self.window.ui.nodes[desc_key] = QLabel(desc_txt)
178
- self.window.ui.nodes[desc_key].setWordWrap(True)
178
+ self.window.ui.nodes[desc_key] = DescLabel(desc_txt)
179
179
  self.window.ui.nodes[desc_key].setAlignment(Qt.AlignCenter)
180
- self.window.ui.nodes[desc_key].setStyleSheet("font-weight: bold;")
180
+ #self.window.ui.nodes[desc_key].setStyleSheet("font-weight: bold;")
181
181
 
182
182
  line = self.add_line()
183
183
 
@@ -234,8 +234,7 @@ class Plugins:
234
234
 
235
235
  data = {}
236
236
  for plugin_id in sorted_ids:
237
- plugin = self.window.core.plugins.get(plugin_id)
238
- data[plugin_id] = plugin
237
+ data[plugin_id] = get_plugin(plugin_id)
239
238
 
240
239
  # plugins list
241
240
  id = 'plugin.list'
@@ -250,12 +249,13 @@ class Plugins:
250
249
  self.window.ui.nodes[id].setMinimumWidth(self.max_list_width)
251
250
 
252
251
  # splitter
253
- self.window.ui.splitters['dialog.plugins'] = QSplitter(Qt.Horizontal)
254
- self.window.ui.splitters['dialog.plugins'].addWidget(self.window.ui.nodes[id]) # list
255
- self.window.ui.splitters['dialog.plugins'].addWidget(self.window.ui.tabs['plugin.settings']) # tabs
256
- self.window.ui.splitters['dialog.plugins'].setStretchFactor(0, 2)
257
- self.window.ui.splitters['dialog.plugins'].setStretchFactor(1, 5)
258
- self.window.ui.splitters['dialog.plugins'].setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
252
+ splitter = QSplitter(Qt.Horizontal)
253
+ splitter.addWidget(self.window.ui.nodes[id]) # list
254
+ splitter.addWidget(self.window.ui.tabs['plugin.settings']) # tabs
255
+ splitter.setStretchFactor(0, 2)
256
+ splitter.setStretchFactor(1, 5)
257
+ splitter.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
258
+ self.window.ui.splitters['dialog.plugins'] = splitter
259
259
 
260
260
  main_layout = QHBoxLayout()
261
261
  main_layout.addWidget(self.window.ui.splitters['dialog.plugins'])
@@ -279,7 +279,7 @@ class Plugins:
279
279
  self.window.ui.tabs['plugin.settings'].setCurrentIndex(idx)
280
280
  self.window.controller.plugins.set_by_tab(idx)
281
281
  except Exception as e:
282
- print('Failed restore plugin settings tab: {}'.format(idx))
282
+ print(f"Failed restore plugin settings tab: {idx}", e)
283
283
  else:
284
284
  self.window.controller.plugins.set_by_tab(0)
285
285
 
@@ -317,15 +317,16 @@ class Plugins:
317
317
  :return: dict of widgets
318
318
  """
319
319
  id = plugin.id
320
- parent = "plugin." + id # parent id for plugins is in format: plugin.<plugin_id>
320
+ parent = f"plugin.{id}" # parent id for plugins is in format: plugin.<plugin_id>
321
321
  widgets = {}
322
+ base_types = ('text', 'int', 'float')
323
+ number_types = ('int', 'float')
322
324
 
323
325
  for key in options:
324
326
  option = options[key]
325
327
  # create widget by option type
326
- if option['type'] == 'text' or option['type'] == 'int' or option['type'] == 'float':
327
- if 'slider' in option and option['slider'] \
328
- and (option['type'] == 'int' or option['type'] == 'float'):
328
+ if option['type'] in base_types:
329
+ if 'slider' in option and option['slider'] and option['type'] in number_types:
329
330
  widgets[key] = OptionSlider(self.window, parent, key, option) # slider + text input
330
331
  else:
331
332
  if 'secret' in option and option['secret']:
@@ -396,14 +397,14 @@ class Plugins:
396
397
  :param option: option dict
397
398
  :return: QVBoxLayout
398
399
  """
399
- one_column_types = ['textarea', 'dict', 'bool', 'cmd']
400
- no_label_types = ['bool', 'cmd']
401
- no_desc_types = ['cmd']
400
+ one_column_types = ('textarea', 'dict', 'bool', 'cmd')
401
+ no_label_types = ('bool', 'cmd')
402
+ no_desc_types = 'cmd'
402
403
  allow_locale = True
403
404
 
404
405
  key = option['id']
405
- label_key = 'plugin.' + plugin.id + '.' + key + '.label'
406
- desc_key = 'plugin.' + plugin.id + '.' + key + '.desc'
406
+ label_key = f"plugin.{plugin.id}.{key}.label"
407
+ desc_key = f"plugin.{plugin.id}.{key}.desc"
407
408
 
408
409
  # get option label and description
409
410
  txt_title = option['label']
@@ -415,14 +416,14 @@ class Plugins:
415
416
 
416
417
  # translate if localization is enabled
417
418
  if plugin.use_locale and allow_locale:
418
- domain = 'plugin.' + plugin.id
419
- txt_title = trans(key + '.label', False, domain)
420
- txt_desc = trans(key + '.description', False, domain)
421
- txt_tooltip = trans(key + '.tooltip', False, domain)
419
+ domain = f"plugin.{plugin.id}"
420
+ txt_title = trans(f"{key}.label", False, domain)
421
+ txt_desc = trans(f"{key}.description", False, domain)
422
+ # txt_tooltip = trans(f"{key}.tooltip", False, domain)
422
423
 
423
424
  # if empty tooltip then use description
424
- if txt_tooltip == key + '.tooltip':
425
- txt_tooltip = txt_desc
425
+ # if txt_tooltip == f"{key}.tooltip":
426
+ # txt_tooltip = txt_desc
426
427
 
427
428
 
428
429
  """
@@ -431,7 +432,7 @@ class Plugins:
431
432
  """
432
433
 
433
434
  if option['type'] not in no_label_types:
434
- self.window.ui.nodes[label_key] = QLabel(txt_title)
435
+ self.window.ui.nodes[label_key] = BaseLabel(txt_title)
435
436
  self.window.ui.nodes[label_key].setStyleSheet("font-weight: bold;")
436
437
 
437
438
  # 2-columns layout
@@ -447,10 +448,8 @@ class Plugins:
447
448
  cols_widget.setLayout(cols)
448
449
  # cols_widget.setMaximumHeight(90)
449
450
 
450
- self.window.ui.nodes[desc_key] = QLabel(txt_desc)
451
- self.window.ui.nodes[desc_key].setWordWrap(True)
451
+ self.window.ui.nodes[desc_key] = DescLabel(txt_desc)
452
452
  self.window.ui.nodes[desc_key].setMaximumHeight(40)
453
- self.window.ui.nodes[desc_key].setStyleSheet("font-size: 10px;")
454
453
  # self.window.ui.nodes[desc_key].setToolTip(txt_tooltip)
455
454
 
456
455
  layout = QVBoxLayout()
@@ -468,10 +467,8 @@ class Plugins:
468
467
  layout.addWidget(widget)
469
468
 
470
469
  if option['type'] not in no_desc_types:
471
- self.window.ui.nodes[desc_key] = QLabel(txt_desc)
472
- self.window.ui.nodes[desc_key].setWordWrap(True)
470
+ self.window.ui.nodes[desc_key] = DescLabel(txt_desc)
473
471
  self.window.ui.nodes[desc_key].setMaximumHeight(40)
474
- self.window.ui.nodes[desc_key].setStyleSheet("font-size: 10px;")
475
472
  # self.window.ui.nodes[desc_key].setToolTip(txt_tooltip)
476
473
  layout.addWidget(self.window.ui.nodes[desc_key])
477
474
 
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.24 23:00:00 #
9
+ # Updated Date: 2025.09.04 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt, QTimer, QRect, Signal, QUrl, QEvent
@@ -16,20 +16,34 @@ from PySide6.QtWidgets import QLabel, QLineEdit, QToolTip
16
16
  from pygpt_net.utils import trans
17
17
 
18
18
 
19
- class HelpLabel(QLabel):
19
+ class BaseLabel(QLabel):
20
20
  def __init__(self, text, window=None):
21
21
  super().__init__(text, window)
22
22
  self.window = window
23
23
  self.setWordWrap(True)
24
24
  self.setContentsMargins(3, 3, 3, 3)
25
+ self.setTextInteractionFlags(Qt.TextSelectableByMouse)
26
+
27
+ class HelpLabel(BaseLabel):
28
+ def __init__(self, text, window=None):
29
+ super().__init__(text, window)
30
+ self.window = window
25
31
  self.setProperty('class', 'label-help')
26
32
 
33
+ class DescLabel(BaseLabel):
34
+ def __init__(self, text, window=None):
35
+ super().__init__(text, window)
36
+ self.window = window
37
+ self.setMaximumHeight(80)
38
+ self.setProperty('class', 'label-desc')
39
+
27
40
 
28
41
  class TitleLabel(QLabel):
29
42
  def __init__(self, text, window=None):
30
43
  super().__init__(text, window)
31
44
  self.window = window
32
45
  self.setProperty('class', 'label-title')
46
+ self.setTextInteractionFlags(Qt.TextSelectableByMouse)
33
47
 
34
48
 
35
49
  class ChatStatusLabel(QLabel):
@@ -39,6 +53,7 @@ class ChatStatusLabel(QLabel):
39
53
  self.setWordWrap(True)
40
54
  self.setAlignment(Qt.AlignRight)
41
55
  self.setProperty('class', 'label-chat-status')
56
+ self.setTextInteractionFlags(Qt.TextSelectableByMouse)
42
57
 
43
58
 
44
59
  class UrlLabel(QLabel):
@@ -49,6 +64,7 @@ class UrlLabel(QLabel):
49
64
  self.update_url()
50
65
  self.setCursor(QCursor(Qt.PointingHandCursor))
51
66
  self.setToolTip(url)
67
+ self.setTextInteractionFlags(Qt.TextSelectableByMouse)
52
68
  # self.setWordWrap(True)
53
69
 
54
70
  def update_url(self):
@@ -59,7 +75,7 @@ class UrlLabel(QLabel):
59
75
  self.setText(text)
60
76
 
61
77
  def mousePressEvent(self, event):
62
- if self.window:
78
+ if self.window and hasattr(self.window, 'controller'):
63
79
  self.window.controller.dialogs.info.open_url(self.url)
64
80
  else:
65
81
  QDesktopServices.openUrl(QUrl(self.url))
@@ -193,7 +193,7 @@ class ChatWebOutput(QWebEngineView):
193
193
  p = self.page()
194
194
 
195
195
  p.runJavaScript(
196
- f"""clean();"""
196
+ f"""if (typeof window.clean !== 'undefined') clean();"""
197
197
  )
198
198
  try:
199
199
  p.history().clear()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygpt-net
3
- Version: 2.6.36
3
+ Version: 2.6.37
4
4
  Summary: Desktop AI Assistant powered by: OpenAI GPT-5, GPT-4, o1, o3, Gemini, Claude, Grok, DeepSeek, and other models supported by Llama Index, and Ollama. Chatbot, agents, completion, image generation, vision analysis, speech-to-text, plugins, internet access, file handling, command execution and more.
5
5
  License: MIT
6
6
  Keywords: ai,api,api key,app,assistant,bielik,chat,chatbot,chatgpt,claude,dall-e,deepseek,desktop,gemini,gpt,gpt-3.5,gpt-4,gpt-4-vision,gpt-4o,gpt-5,gpt-oss,gpt3.5,gpt4,grok,langchain,llama-index,llama3,mistral,o1,o3,ollama,openai,presets,py-gpt,py_gpt,pygpt,pyside,qt,text completion,tts,ui,vision,whisper
@@ -118,7 +118,7 @@ Description-Content-Type: text/markdown
118
118
 
119
119
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
120
120
 
121
- Release: **2.6.36** | build: **2025-09-04** | Python: **>=3.10, <3.14**
121
+ Release: **2.6.37** | build: **2025-09-05** | Python: **>=3.10, <3.14**
122
122
 
123
123
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
124
124
  >
@@ -520,16 +520,16 @@ Here, you can add or manage API keys for any supported provider.
520
520
 
521
521
  **+ Inline Vision and Image generation**
522
522
 
523
- In **PyGPT**, this mode mirrors `ChatGPT`, allowing you to chat with models like `GPT-5`, `GPT-4`, `o1`, `o3`, `Claude`, `Gemini`, `Grok`, `Perplexity (Sonar)`, `Deepseek`, and more. It works using the OpenAI API `Responses` and `ChatCompletions`, or the `Google GenAI SDK` if the Google native client is enabled. You can choose the API endpoint for `ChatCompletions` in `Config -> Settings -> API Keys`.
523
+ In **PyGPT**, this mode mirrors `ChatGPT`, allowing you to chat with models like `GPT-5`, `GPT-4`, `o1`, `o3`, `Claude`, `Gemini`, `Grok`, `Perplexity (Sonar)`, `Deepseek`, and more. It works with the OpenAI SDK using the `Responses API` and `ChatCompletions API. You can also use SDKs from Google GenAI, Anthropic, or xAI if the native SDK is enabled. You can set the endpoint for `ChatCompletions in Config -> Settings -> API Keys`.
524
524
 
525
- **Tip:** This mode uses the provider SDK directly. If there's no native client built into the app, models like Gemini, Claude, Grok, Sonar, or Llama3 are supported in Chat mode via LlamaIndex or compatible OpenAI API endpoints. The app automatically switches to these endpoints when using non-OpenAI models.
525
+ **Tip:** This mode uses the provider SDK directly. If there's no native client built into the app, models like Sonar, or Llama3 are supported in Chat mode via LlamaIndex or OpenAI-compatible API endpoints. The app automatically switches to these endpoints when using non-OpenAI models. You can enable or disable the use of the native API SDK (per provider) in `Settings -> API Keys`. If the native SDK is disabled, the OpenAI SDK will be used via the compatible ChatCompletions API endpoint.
526
526
 
527
527
  Currently built-in native clients:
528
528
 
529
+ - Anthropic SDK
529
530
  - OpenAI SDK
530
531
  - Google GenAI SDK
531
-
532
- Support for Anthropic and xAI native clients is coming soon.
532
+ - xAI SDK
533
533
 
534
534
  The main part of the interface is a chat window where you see your conversations. Below it is a message box for typing. On the right side, you can set up or change the model and system prompt. You can also save these settings as presets to easily switch between models or tasks.
535
535
 
@@ -3565,6 +3565,11 @@ may consume additional tokens that are not displayed in the main window.
3565
3565
 
3566
3566
  ## Recent changes:
3567
3567
 
3568
+ **2.6.37 (2025-09-05)**
3569
+
3570
+ - Fixed: Function parameters sanitization in the Google Gen AI SDK.
3571
+ - Added: Native support for Anthropic SDK and xAI SDK (with remote tools).
3572
+
3568
3573
  **2.6.36 (2025-09-04)**
3569
3574
 
3570
3575
  - Optimized rendering of large code blocks.