pygpt-net 2.6.27__py3-none-any.whl → 2.6.29__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 (44) 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/kernel/kernel.py +2 -0
  5. pygpt_net/controller/notepad/notepad.py +10 -1
  6. pygpt_net/controller/theme/common.py +2 -0
  7. pygpt_net/controller/theme/markdown.py +2 -0
  8. pygpt_net/controller/theme/theme.py +3 -0
  9. pygpt_net/controller/ui/tabs.py +5 -0
  10. pygpt_net/core/audio/backend/native.py +1 -3
  11. pygpt_net/core/command/command.py +2 -0
  12. pygpt_net/core/idx/llm.py +21 -3
  13. pygpt_net/core/render/web/helpers.py +13 -3
  14. pygpt_net/core/render/web/renderer.py +3 -3
  15. pygpt_net/data/config/config.json +3 -3
  16. pygpt_net/data/config/models.json +3 -3
  17. pygpt_net/data/config/settings.json +27 -28
  18. pygpt_net/data/css/web-blocks.darkest.css +91 -0
  19. pygpt_net/data/css/web-chatgpt.css +7 -5
  20. pygpt_net/data/css/web-chatgpt.dark.css +5 -2
  21. pygpt_net/data/css/web-chatgpt.darkest.css +91 -0
  22. pygpt_net/data/css/web-chatgpt.light.css +8 -2
  23. pygpt_net/data/css/web-chatgpt_wide.css +7 -4
  24. pygpt_net/data/css/web-chatgpt_wide.dark.css +5 -2
  25. pygpt_net/data/css/web-chatgpt_wide.darkest.css +91 -0
  26. pygpt_net/data/css/web-chatgpt_wide.light.css +9 -6
  27. pygpt_net/data/themes/dark_darkest.css +31 -0
  28. pygpt_net/data/themes/dark_darkest.xml +10 -0
  29. pygpt_net/plugin/tuya/__init__.py +12 -0
  30. pygpt_net/plugin/tuya/config.py +256 -0
  31. pygpt_net/plugin/tuya/plugin.py +117 -0
  32. pygpt_net/plugin/tuya/worker.py +588 -0
  33. pygpt_net/plugin/wikipedia/__init__.py +12 -0
  34. pygpt_net/plugin/wikipedia/config.py +228 -0
  35. pygpt_net/plugin/wikipedia/plugin.py +114 -0
  36. pygpt_net/plugin/wikipedia/worker.py +430 -0
  37. pygpt_net/provider/core/config/patch.py +11 -0
  38. pygpt_net/ui/widget/tabs/output.py +2 -0
  39. pygpt_net/ui/widget/textarea/input.py +10 -7
  40. {pygpt_net-2.6.27.dist-info → pygpt_net-2.6.29.dist-info}/METADATA +50 -6
  41. {pygpt_net-2.6.27.dist-info → pygpt_net-2.6.29.dist-info}/RECORD +44 -31
  42. {pygpt_net-2.6.27.dist-info → pygpt_net-2.6.29.dist-info}/LICENSE +0 -0
  43. {pygpt_net-2.6.27.dist-info → pygpt_net-2.6.29.dist-info}/WHEEL +0 -0
  44. {pygpt_net-2.6.27.dist-info → pygpt_net-2.6.29.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,430 @@
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.27 20:18:26 #
10
+ # ================================================== #
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ from PySide6.QtCore import Slot
17
+ from pygpt_net.plugin.base.worker import BaseWorker, BaseSignals
18
+
19
+ # Lazy import
20
+ try:
21
+ import wikipedia as wiki
22
+ from wikipedia.exceptions import (
23
+ DisambiguationError,
24
+ PageError,
25
+ RedirectError,
26
+ HTTPTimeoutError,
27
+ WikipediaException,
28
+ )
29
+ except Exception: # fallback mapping for type hints
30
+ wiki = None
31
+ DisambiguationError = PageError = RedirectError = HTTPTimeoutError = WikipediaException = Exception # type: ignore
32
+
33
+
34
+ class WorkerSignals(BaseSignals):
35
+ pass
36
+
37
+
38
+ class Worker(BaseWorker):
39
+ """
40
+ Wikipedia plugin worker: language, search, suggest, summary, page, section, random, geosearch, open.
41
+ """
42
+
43
+ def __init__(self, *args, **kwargs):
44
+ super(Worker, self).__init__()
45
+ self.signals = WorkerSignals()
46
+ self.args = args
47
+ self.kwargs = kwargs
48
+ self.plugin = None
49
+ self.cmds = None
50
+ self.ctx = None
51
+ self.msg = None
52
+
53
+ # ---------------------- Core runner ----------------------
54
+
55
+ @Slot()
56
+ def run(self):
57
+ try:
58
+ responses = []
59
+ self._apply_settings() # sync module settings with plugin options
60
+ for item in self.cmds:
61
+ if self.is_stopped():
62
+ break
63
+ try:
64
+ response = None
65
+ if item["cmd"] in self.plugin.allowed_cmds and self.plugin.has_cmd(item["cmd"]):
66
+
67
+ # -------- Language --------
68
+ if item["cmd"] == "wp_set_lang":
69
+ response = self.cmd_wp_set_lang(item)
70
+ elif item["cmd"] == "wp_get_lang":
71
+ response = self.cmd_wp_get_lang(item)
72
+ elif item["cmd"] == "wp_languages":
73
+ response = self.cmd_wp_languages(item)
74
+
75
+ # -------- Search / Suggest --------
76
+ elif item["cmd"] == "wp_search":
77
+ response = self.cmd_wp_search(item)
78
+ elif item["cmd"] == "wp_suggest":
79
+ response = self.cmd_wp_suggest(item)
80
+
81
+ # -------- Read --------
82
+ elif item["cmd"] == "wp_summary":
83
+ response = self.cmd_wp_summary(item)
84
+ elif item["cmd"] == "wp_page":
85
+ response = self.cmd_wp_page(item)
86
+ elif item["cmd"] == "wp_section":
87
+ response = self.cmd_wp_section(item)
88
+
89
+ # -------- Discover --------
90
+ elif item["cmd"] == "wp_random":
91
+ response = self.cmd_wp_random(item)
92
+ elif item["cmd"] == "wp_geosearch":
93
+ response = self.cmd_wp_geosearch(item)
94
+
95
+ # -------- Utilities --------
96
+ elif item["cmd"] == "wp_open":
97
+ response = self.cmd_wp_open(item)
98
+
99
+ if response:
100
+ responses.append(response)
101
+
102
+ except DisambiguationError as e:
103
+ responses.append(self.make_response(item, self._wrap_disambig(e)))
104
+ except (PageError, RedirectError, HTTPTimeoutError, WikipediaException) as e:
105
+ responses.append(self.make_response(item, self.throw_error(e)))
106
+ except Exception as e:
107
+ responses.append(self.make_response(item, self.throw_error(e)))
108
+
109
+ if responses:
110
+ self.reply_more(responses)
111
+ if self.msg is not None:
112
+ self.status(self.msg)
113
+ except Exception as e:
114
+ self.error(e)
115
+ finally:
116
+ self.cleanup()
117
+
118
+ # ---------------------- Helpers ----------------------
119
+
120
+ def _require_lib(self):
121
+ if wiki is None:
122
+ raise RuntimeError("Missing 'wikipedia' package. Install with: pip install wikipedia")
123
+
124
+ def _apply_settings(self):
125
+ self._require_lib()
126
+ # Keep module-level settings in sync with plugin options
127
+ lang = (self.plugin.get_option_value("lang") or "en").strip()
128
+ auto_rate = bool(self.plugin.get_option_value("rate_limit") or True)
129
+ ua = (self.plugin.get_option_value("user_agent") or "").strip()
130
+ try:
131
+ wiki.set_lang(lang)
132
+ except Exception:
133
+ pass
134
+ try:
135
+ wiki.set_rate_limit(auto_rate)
136
+ except Exception:
137
+ pass
138
+ if ua:
139
+ try:
140
+ wiki.set_user_agent(ua)
141
+ except Exception:
142
+ pass
143
+
144
+ def _opt_bool(self, key: str, default: bool) -> bool:
145
+ v = self.plugin.get_option_value(key)
146
+ return bool(default if v is None else v)
147
+
148
+ def _opt_int(self, key: str, default: int) -> int:
149
+ v = self.plugin.get_option_value(key)
150
+ try:
151
+ return int(default if v is None else v)
152
+ except Exception:
153
+ return default
154
+
155
+ def _params_bool(self, params: dict, key: str, fallback_opt: Optional[str], default: bool) -> bool:
156
+ if key in params and params[key] is not None:
157
+ return bool(params[key])
158
+ if fallback_opt:
159
+ return self._opt_bool(fallback_opt, default)
160
+ return bool(default)
161
+
162
+ def _params_int(self, params: dict, key: str, fallback_opt: Optional[str], default: int) -> int:
163
+ if key in params and params[key] is not None:
164
+ try:
165
+ return int(params[key])
166
+ except Exception:
167
+ return default
168
+ if fallback_opt:
169
+ return self._opt_int(fallback_opt, default)
170
+ return int(default)
171
+
172
+ def _clip_text(self, text: str, max_chars: Optional[int]) -> str:
173
+ if text is None:
174
+ return ""
175
+ if not max_chars or max_chars <= 0:
176
+ return text
177
+ if len(text) <= max_chars:
178
+ return text
179
+ return text[: max_chars - 3] + "..."
180
+
181
+ def _clip_list(self, items: List[Any], max_items: Optional[int]) -> List[Any]:
182
+ if not isinstance(items, list):
183
+ return []
184
+ if not max_items or max_items <= 0 or len(items) <= max_items:
185
+ return items
186
+ return items[:max_items]
187
+
188
+ def _wrap_disambig(self, e: DisambiguationError) -> dict:
189
+ # Provide options to let the assistant choose a target
190
+ return {
191
+ "error": "Disambiguation required",
192
+ "type": "DisambiguationError",
193
+ "title": getattr(e, "title", None),
194
+ "options": getattr(e, "options", []),
195
+ }
196
+
197
+ def _auto_suggest_param(self, p: dict) -> bool:
198
+ return self._params_bool(p, "auto_suggest", "auto_suggest", True)
199
+
200
+ def _redirect_param(self, p: dict) -> bool:
201
+ return self._params_bool(p, "redirect", "redirect", True)
202
+
203
+ # Decide whether to return full content (no clipping)
204
+ def _full_content_param(self, p: dict) -> bool:
205
+ if p.get("full") is not None:
206
+ return bool(p["full"])
207
+ if p.get("no_clip") is not None: # alias
208
+ return bool(p["no_clip"])
209
+ return bool(self.plugin.get_option_value("content_full_default") or False)
210
+
211
+ # ---------------------- Commands: Language ----------------------
212
+
213
+ def cmd_wp_set_lang(self, item: dict) -> dict:
214
+ p = item.get("params", {})
215
+ lang = (p.get("lang") or "").strip()
216
+ if not lang:
217
+ return self.make_response(item, "Param 'lang' required")
218
+ self.plugin.set_option_value("lang", lang)
219
+ self._apply_settings()
220
+ return self.make_response(item, {"ok": True, "lang": lang})
221
+
222
+ def cmd_wp_get_lang(self, item: dict) -> dict:
223
+ lang = (self.plugin.get_option_value("lang") or "en").strip()
224
+ return self.make_response(item, {"lang": lang})
225
+
226
+ def cmd_wp_languages(self, item: dict) -> dict:
227
+ self._require_lib()
228
+ p = item.get("params", {})
229
+ short = bool(p.get("short", False))
230
+ langs = wiki.languages() # dict: code -> localized name
231
+ if short:
232
+ data = sorted(list(langs.keys()))
233
+ else:
234
+ data = langs
235
+ max_items = self._opt_int("max_list_items", 50)
236
+ if isinstance(data, list):
237
+ data = self._clip_list(data, max_items)
238
+ elif isinstance(data, dict):
239
+ data = dict(list(data.items())[:max_items])
240
+ return self.make_response(item, {"data": data})
241
+
242
+ # ---------------------- Commands: Search / Suggest ----------------------
243
+
244
+ def cmd_wp_search(self, item: dict) -> dict:
245
+ self._require_lib()
246
+ p = item.get("params", {})
247
+ q = (p.get("q") or p.get("query") or "").strip()
248
+ if not q:
249
+ return self.make_response(item, "Param 'q' required")
250
+ results_limit = self._params_int(p, "results", "results_default", 10)
251
+ suggestion = bool(p.get("suggestion", False))
252
+ if suggestion:
253
+ res, sugg = wiki.search(q, results=results_limit, suggestion=True)
254
+ return self.make_response(item, {"results": res, "suggestion": sugg})
255
+ else:
256
+ res = wiki.search(q, results=results_limit, suggestion=False)
257
+ return self.make_response(item, {"results": res})
258
+
259
+ def cmd_wp_suggest(self, item: dict) -> dict:
260
+ self._require_lib()
261
+ p = item.get("params", {})
262
+ q = (p.get("q") or p.get("query") or "").strip()
263
+ if not q:
264
+ return self.make_response(item, "Param 'q' required")
265
+ sugg = wiki.suggest(q)
266
+ return self.make_response(item, {"suggestion": sugg})
267
+
268
+ # ---------------------- Commands: Read ----------------------
269
+
270
+ def cmd_wp_summary(self, item: dict) -> dict:
271
+ self._require_lib()
272
+ p = item.get("params", {})
273
+ title = (p.get("title") or p.get("q") or p.get("query") or "").strip()
274
+ if not title:
275
+ return self.make_response(item, "Param 'title' (or 'q') required")
276
+ sentences = self._params_int(p, "sentences", "summary_sentences", 3)
277
+ auto_suggest = self._auto_suggest_param(p)
278
+ redirect = self._redirect_param(p)
279
+ summary = wiki.summary(title, sentences=sentences, auto_suggest=auto_suggest, redirect=redirect)
280
+ url = None
281
+ try:
282
+ pg = wiki.page(title, auto_suggest=auto_suggest, redirect=redirect)
283
+ url = getattr(pg, "url", None)
284
+ title = getattr(pg, "title", title)
285
+ except Exception:
286
+ pass
287
+ return self.make_response(item, {"title": title, "summary": summary, "url": url})
288
+
289
+ def cmd_wp_page(self, item: dict) -> dict:
290
+ self._require_lib()
291
+ p = item.get("params", {})
292
+ title = (p.get("title") or "").strip()
293
+ if not title:
294
+ return self.make_response(item, "Param 'title' required")
295
+ auto_suggest = self._auto_suggest_param(p)
296
+ redirect = self._redirect_param(p)
297
+ content_chars = self._params_int(p, "content_chars", "content_max_chars", 5000)
298
+ max_list = self._params_int(p, "max_list_items", "max_list_items", 50)
299
+ full = self._full_content_param(p)
300
+
301
+ include = p.get("include")
302
+ if not include:
303
+ include = ["title", "url", "summary", "content", "sections", "categories", "links", "images", "references"]
304
+
305
+ pg = wiki.page(title, auto_suggest=auto_suggest, redirect=redirect)
306
+ data: Dict[str, Any] = {}
307
+
308
+ if "title" in include:
309
+ data["title"] = getattr(pg, "title", title)
310
+ if "url" in include:
311
+ data["url"] = getattr(pg, "url", None)
312
+ if "summary" in include:
313
+ try:
314
+ data["summary"] = getattr(pg, "summary", None)
315
+ except Exception:
316
+ data["summary"] = None
317
+ if "content" in include:
318
+ try:
319
+ raw = getattr(pg, "content", None)
320
+ data["content_body"] = raw if full else self._clip_text(raw, content_chars)
321
+ except Exception:
322
+ data["content_body"] = None
323
+ if "sections" in include:
324
+ try:
325
+ data["sections"] = self._clip_list(getattr(pg, "sections", []), max_list)
326
+ except Exception:
327
+ data["sections"] = []
328
+ if "categories" in include:
329
+ try:
330
+ data["categories"] = self._clip_list(getattr(pg, "categories", []), max_list)
331
+ except Exception:
332
+ data["categories"] = []
333
+ if "links" in include:
334
+ try:
335
+ data["links"] = self._clip_list(getattr(pg, "links", []), max_list)
336
+ except Exception:
337
+ data["links"] = []
338
+ if "images" in include:
339
+ try:
340
+ data["images"] = self._clip_list(getattr(pg, "images", []), max_list)
341
+ except Exception:
342
+ data["images"] = []
343
+ if "references" in include:
344
+ try:
345
+ data["references"] = self._clip_list(getattr(pg, "references", []), max_list)
346
+ except Exception:
347
+ data["references"] = []
348
+
349
+ if bool(p.get("open", False)):
350
+ try:
351
+ if data.get("url"):
352
+ self.plugin.open_url(data["url"])
353
+ except Exception:
354
+ pass
355
+
356
+ return self.make_response(item, data)
357
+
358
+ def cmd_wp_section(self, item: dict) -> dict:
359
+ self._require_lib()
360
+ p = item.get("params", {})
361
+ title = (p.get("title") or "").strip()
362
+ section = (p.get("section") or "").strip()
363
+ if not title or not section:
364
+ return self.make_response(item, "Params 'title' and 'section' required")
365
+ auto_suggest = self._auto_suggest_param(p)
366
+ redirect = self._redirect_param(p)
367
+ content_chars = self._params_int(p, "content_chars", "content_max_chars", 5000)
368
+ full = self._full_content_param(p)
369
+
370
+ pg = wiki.page(title, auto_suggest=auto_suggest, redirect=redirect)
371
+ txt = pg.section(section)
372
+
373
+ return self.make_response(item, {
374
+ "title": getattr(pg, "title", title),
375
+ "section": section,
376
+ "content_body": (txt or "") if full else self._clip_text(txt or "", content_chars),
377
+ "url": getattr(pg, "url", None),
378
+ })
379
+
380
+ # ---------------------- Commands: Discover ----------------------
381
+
382
+ def cmd_wp_random(self, item: dict) -> dict:
383
+ self._require_lib()
384
+ p = item.get("params", {})
385
+ count = self._params_int(p, "results", "results_default", 1)
386
+ res = wiki.random(pages=count)
387
+ if isinstance(res, str):
388
+ res = [res]
389
+ return self.make_response(item, {"results": res})
390
+
391
+ def cmd_wp_geosearch(self, item: dict) -> dict:
392
+ self._require_lib()
393
+ p = item.get("params", {})
394
+ lat = p.get("lat") or p.get("latitude")
395
+ lon = p.get("lon") or p.get("lng") or p.get("longitude")
396
+ if lat is None or lon is None:
397
+ return self.make_response(item, "Params 'lat' and 'lon' required")
398
+ try:
399
+ lat = float(lat)
400
+ lon = float(lon)
401
+ except Exception:
402
+ return self.make_response(item, "Params 'lat' and 'lon' must be numbers")
403
+ radius = self._params_int(p, "radius", None, 1000)
404
+ results_limit = self._params_int(p, "results", "results_default", 10)
405
+ title = p.get("title")
406
+ res = wiki.geosearch(latitude=lat, longitude=lon, title=title, results=results_limit, radius=radius)
407
+ return self.make_response(item, {"results": res})
408
+
409
+ # ---------------------- Commands: Utilities ----------------------
410
+
411
+ def cmd_wp_open(self, item: dict) -> dict:
412
+ self._require_lib()
413
+ p = item.get("params", {})
414
+ url = p.get("url")
415
+ title = p.get("title")
416
+ auto_suggest = self._auto_suggest_param(p)
417
+ redirect = self._redirect_param(p)
418
+
419
+ if not url and not title:
420
+ return self.make_response(item, "Provide 'url' or 'title'")
421
+ if not url:
422
+ pg = wiki.page(title, auto_suggest=auto_suggest, redirect=redirect)
423
+ url = getattr(pg, "url", None)
424
+ if not url:
425
+ return self.make_response(item, "Unable to resolve URL")
426
+ try:
427
+ self.plugin.open_url(url)
428
+ except Exception:
429
+ pass
430
+ return self.make_response(item, {"ok": True, "url": url})
@@ -2344,6 +2344,17 @@ class Patch:
2344
2344
  data["api_endpoint_open_router"] = "https://openrouter.ai/api/v1"
2345
2345
  updated = True
2346
2346
 
2347
+ # < 2.6.28 -- fix: cmd color
2348
+ if old < parse_version("2.6.28"):
2349
+ print("Migrating config from < 2.6.28...")
2350
+ self.window.core.updater.patch_css('web-chatgpt.css', True)
2351
+ self.window.core.updater.patch_css('web-chatgpt_wide.css', True)
2352
+ self.window.core.updater.patch_css('web-chatgpt.dark.css', True)
2353
+ self.window.core.updater.patch_css('web-chatgpt_wide.dark.css', True)
2354
+ self.window.core.updater.patch_css('web-chatgpt.light.css', True)
2355
+ self.window.core.updater.patch_css('web-chatgpt_wide.light.css', True)
2356
+ updated = True
2357
+
2347
2358
  # update file
2348
2359
  migrated = False
2349
2360
  if updated:
@@ -323,6 +323,8 @@ class AddButton(QPushButton):
323
323
  idx = 0
324
324
  column_idx = self.column.get_idx()
325
325
  self.show_menu(idx, column_idx, event.globalPos())
326
+ event.accept()
327
+ return
326
328
  super(AddButton, self).mousePressEvent(event)
327
329
 
328
330
  def show_menu(self, index: int, column_idx: int, global_pos):
@@ -30,6 +30,7 @@ class ChatInput(QTextEdit):
30
30
  ICON_VOLUME = QIcon(":/icons/volume.svg")
31
31
  ICON_SAVE = QIcon(":/icons/save.svg")
32
32
  ICON_ATTACHMENT = QIcon(":/icons/add.svg")
33
+ #ICON_ATTACHMENT = QIcon(":/icons/attachment.svg")
33
34
  ICON_MIC_ON = QIcon(":/icons/mic.svg")
34
35
  ICON_MIC_OFF = QIcon(":/icons/mic_off.svg")
35
36
 
@@ -46,7 +47,7 @@ class ChatInput(QTextEdit):
46
47
  self.value = self.window.core.config.data['font_size.input']
47
48
  self.max_font_size = 42
48
49
  self.min_font_size = 8
49
- self._text_top_padding = 12
50
+ self._text_top_padding = 10
50
51
  self.textChanged.connect(self.window.controller.ui.update_tokens)
51
52
  self.setProperty('class', 'layout-input')
52
53
 
@@ -66,8 +67,8 @@ class ChatInput(QTextEdit):
66
67
  self._icon_meta = {} # key -> {"icon": QIcon, "alt_icon": Optional[QIcon], "tooltip": str, "alt_tooltip": Optional[str], "active": bool}
67
68
  self._icon_order = [] # rendering order
68
69
 
69
- # Add a "+" button in the top-left corner to add attachments
70
70
  self._init_icon_bar()
71
+ # Add a "+" button in the top-left corner to add attachments
71
72
  self.add_icon(
72
73
  key="attach",
73
74
  icon=self.ICON_ATTACHMENT,
@@ -75,6 +76,7 @@ class ChatInput(QTextEdit):
75
76
  callback=self.action_add_attachment,
76
77
  visible=True,
77
78
  )
79
+ # Add a microphone button (hidden by default; shown when audio input is enabled)
78
80
  self.add_icon(
79
81
  key="mic",
80
82
  icon=self.ICON_MIC_ON,
@@ -94,7 +96,11 @@ class ChatInput(QTextEdit):
94
96
  self._apply_margins()
95
97
 
96
98
  def set_text_top_padding(self, px: int):
97
- """Public helper to adjust top padding at runtime."""
99
+ """
100
+ Public helper to adjust top padding at runtime.
101
+
102
+ :param px: padding in pixels
103
+ """
98
104
  self._text_top_padding = max(0, int(px))
99
105
  self._apply_margins()
100
106
 
@@ -177,9 +183,7 @@ class ChatInput(QTextEdit):
177
183
  menu.deleteLater()
178
184
 
179
185
  def action_from_clipboard(self):
180
- """
181
- Get from clipboard
182
- """
186
+ """Paste from clipboard"""
183
187
  clipboard = QApplication.clipboard()
184
188
  source = clipboard.mimeData()
185
189
  self.handle_clipboard(source)
@@ -324,7 +328,6 @@ class ChatInput(QTextEdit):
324
328
  pass
325
329
  btn.clicked.connect(callback)
326
330
  btn.setHidden(not visible)
327
- self._sync_icon_order(key)
328
331
  self._rebuild_icon_layout()
329
332
  self._update_icon_bar_geometry()
330
333
  self._apply_margins()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygpt-net
3
- Version: 2.6.27
3
+ Version: 2.6.29
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
@@ -117,7 +117,7 @@ Description-Content-Type: text/markdown
117
117
 
118
118
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
119
119
 
120
- Release: **2.6.27** | build: **2025-08-26** | Python: **>=3.10, <3.14**
120
+ Release: **2.6.29** | build: **2025-08-27** | Python: **>=3.10, <3.14**
121
121
 
122
122
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
123
123
  >
@@ -1470,12 +1470,16 @@ The following plugins are currently available, and model can use them instantly:
1470
1470
 
1471
1471
  - `Telegram` - Send messages, photos, and documents; manage chats and contacts.
1472
1472
 
1473
+ - `Tuya (IoT)` - Handle Tuya Smart Home devices via Tuya Cloud API.
1474
+
1473
1475
  - `Vision (inline)` - integrates Vision capabilities with any chat mode, not just Vision mode. When the plugin is enabled, the model temporarily switches to vision in the background when an image attachment or vision capture is provided.
1474
1476
 
1475
1477
  - `Voice Control (inline)` - provides voice control command execution within a conversation.
1476
1478
 
1477
1479
  - `Web Search` - provides the ability to connect to the Web, search web pages for current data, and index external content using LlamaIndex data loaders.
1478
1480
 
1481
+ - `Wikipedia` - Search Wikipedia for information.
1482
+
1479
1483
  - `X/Twitter` - Interact with tweets and users, manage bookmarks and media, perform likes, retweets, and more.
1480
1484
 
1481
1485
 
@@ -1862,6 +1866,20 @@ The plugin enables integration with Telegram for both bots and user accounts thr
1862
1866
 
1863
1867
  Documentation: https://pygpt.readthedocs.io/en/latest/plugins.html#telegram
1864
1868
 
1869
+ ## Tuya (IoT)
1870
+
1871
+ The Tuya plugin integrates with Tuya's Smart Home platform, enabling seamless interactions with your smart devices via the Tuya Cloud API. This plugin provides a user-friendly interface to manage and control devices directly from your assistant.
1872
+
1873
+ * Provide your Tuya Cloud credentials to enable communication.
1874
+ * Access and list all smart devices connected to your Tuya app account.
1875
+ * Retrieve detailed information about each device, including its status and supported functions.
1876
+ * Effortlessly search for devices by their names using cached data for quick access.
1877
+ * Control devices by turning them on or off, toggle states, and set specific device parameters.
1878
+ * Send custom commands to devices for more advanced control.
1879
+ * Read sensor values and normalize them for easy interpretation.
1880
+
1881
+ Documentation: https://pygpt.readthedocs.io/en/latest/plugins.html#tuya-iot
1882
+
1865
1883
  ## Vision (inline)
1866
1884
 
1867
1885
  The plugin integrates vision capabilities across all chat modes, not just Vision mode. Once enabled, it allows the model to seamlessly switch to vision processing in the background whenever an image attachment or vision capture is detected.
@@ -1886,6 +1904,20 @@ Web searches are provided by `Google Custom Search Engine` and `Microsoft Bing`
1886
1904
 
1887
1905
  Documentation: https://pygpt.readthedocs.io/en/latest/plugins.html#web-search
1888
1906
 
1907
+ ## Wikipedia
1908
+
1909
+ The Wikipedia plugin allows for comprehensive interactions with Wikipedia, including language settings, article searching, summaries, and random article discovery. This plugin offers a variety of options to optimize your search experience.
1910
+
1911
+ * Set your preferred language for Wikipedia queries.
1912
+ * Retrieve and check the current language setting.
1913
+ * Explore a list of supported languages.
1914
+ * Search for articles using keywords or get suggestions for queries.
1915
+ * Obtain summaries and detailed page content.
1916
+ * Discover articles by geographic location or randomly.
1917
+ * Open articles directly in your web browser.
1918
+
1919
+ Documentation: https://pygpt.readthedocs.io/en/latest/plugins.html#wikipedia
1920
+
1889
1921
  ## X/Twitter
1890
1922
 
1891
1923
  The X/Twitter plugin integrates with the X platform, allowing for comprehensive interactions such as tweeting, retweeting, liking, media uploads, and more. This plugin requires OAuth2 authentication and offers various configuration options to manage API interactions effectively.
@@ -2331,8 +2363,6 @@ Config -> Settings...
2331
2363
 
2332
2364
  - `Directory for file downloads`: Subdirectory for downloaded files, e.g. in Assistants mode, inside "data". Default: "download"
2333
2365
 
2334
- - `Verbose mode`: Enabled verbose mode when using attachment as additional context.
2335
-
2336
2366
  - `Model for querying index`: Model to use for preparing query and querying the index when the RAG option is selected.
2337
2367
 
2338
2368
  - `Model for attachment content summary`: Model to use when generating a summary for the content of a file when the Summary option is selected.
@@ -2537,8 +2567,6 @@ Enable/disable remote tools, like Web Search or Image generation to use in OpenA
2537
2567
 
2538
2568
  **General**
2539
2569
 
2540
- - `Verbose` - enables verbose mode.
2541
-
2542
2570
  - `Auto retrieve additional context from RAG`: Auto retrieve additional context from RAG at the beginning if the index is provided.
2543
2571
 
2544
2572
  - `Display a tray notification when the goal is achieved.`: If enabled, a notification will be displayed after goal achieved / finished run.
@@ -2621,6 +2649,10 @@ Enable/disable remote tools, like Web Search or Image generation to use in OpenA
2621
2649
 
2622
2650
  - `Log DALL-E usage to console`: Enables logging of DALL-E usage to console.
2623
2651
 
2652
+ - `Log attachments usage to console`: Enables logging of attachments usage to console.
2653
+
2654
+ - `Log Agents usage to console`: Enables logging of Agents usage to console.
2655
+
2624
2656
  - `Log LlamaIndex usage to console`: Enables logging of LlamaIndex usage to console.
2625
2657
 
2626
2658
  - `Log Assistants usage to console`: Enables logging of Assistants API usage to console.
@@ -3543,6 +3575,18 @@ may consume additional tokens that are not displayed in the main window.
3543
3575
 
3544
3576
  ## Recent changes:
3545
3577
 
3578
+ **2.6.29 (2025-08-28)**
3579
+
3580
+ - Verbose options have been moved to the Developer section in settings.
3581
+ - Enhanced logging of embeddings usage.
3582
+ - Fixed styles list.
3583
+
3584
+ **2.6.28 (2025-08-27)**
3585
+
3586
+ - Added new plugins: Tuya (IoT) and Wikipedia.
3587
+ - Improved formatting of JSON command output.
3588
+ - Fixed CSS issues.
3589
+
3546
3590
  **2.6.27 (2025-08-26)**
3547
3591
 
3548
3592
  - Simplified audio input: A microphone icon has been added to the input field.