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.
- pygpt_net/CHANGELOG.txt +12 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +5 -1
- pygpt_net/controller/kernel/kernel.py +2 -0
- pygpt_net/controller/notepad/notepad.py +10 -1
- pygpt_net/controller/theme/common.py +2 -0
- pygpt_net/controller/theme/markdown.py +2 -0
- pygpt_net/controller/theme/theme.py +3 -0
- pygpt_net/controller/ui/tabs.py +5 -0
- pygpt_net/core/audio/backend/native.py +1 -3
- pygpt_net/core/command/command.py +2 -0
- pygpt_net/core/idx/llm.py +21 -3
- pygpt_net/core/render/web/helpers.py +13 -3
- pygpt_net/core/render/web/renderer.py +3 -3
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/settings.json +27 -28
- pygpt_net/data/css/web-blocks.darkest.css +91 -0
- pygpt_net/data/css/web-chatgpt.css +7 -5
- pygpt_net/data/css/web-chatgpt.dark.css +5 -2
- pygpt_net/data/css/web-chatgpt.darkest.css +91 -0
- pygpt_net/data/css/web-chatgpt.light.css +8 -2
- pygpt_net/data/css/web-chatgpt_wide.css +7 -4
- pygpt_net/data/css/web-chatgpt_wide.dark.css +5 -2
- pygpt_net/data/css/web-chatgpt_wide.darkest.css +91 -0
- pygpt_net/data/css/web-chatgpt_wide.light.css +9 -6
- pygpt_net/data/themes/dark_darkest.css +31 -0
- pygpt_net/data/themes/dark_darkest.xml +10 -0
- pygpt_net/plugin/tuya/__init__.py +12 -0
- pygpt_net/plugin/tuya/config.py +256 -0
- pygpt_net/plugin/tuya/plugin.py +117 -0
- pygpt_net/plugin/tuya/worker.py +588 -0
- pygpt_net/plugin/wikipedia/__init__.py +12 -0
- pygpt_net/plugin/wikipedia/config.py +228 -0
- pygpt_net/plugin/wikipedia/plugin.py +114 -0
- pygpt_net/plugin/wikipedia/worker.py +430 -0
- pygpt_net/provider/core/config/patch.py +11 -0
- pygpt_net/ui/widget/tabs/output.py +2 -0
- pygpt_net/ui/widget/textarea/input.py +10 -7
- {pygpt_net-2.6.27.dist-info → pygpt_net-2.6.29.dist-info}/METADATA +50 -6
- {pygpt_net-2.6.27.dist-info → pygpt_net-2.6.29.dist-info}/RECORD +44 -31
- {pygpt_net-2.6.27.dist-info → pygpt_net-2.6.29.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.27.dist-info → pygpt_net-2.6.29.dist-info}/WHEEL +0 -0
- {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 =
|
|
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
|
-
"""
|
|
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.
|
|
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
|
[](https://snapcraft.io/pygpt)
|
|
119
119
|
|
|
120
|
-
Release: **2.6.
|
|
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.
|