pygpt-net 2.6.26__py3-none-any.whl → 2.6.28__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 (63) hide show
  1. pygpt_net/CHANGELOG.txt +10 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +5 -1
  4. pygpt_net/controller/access/voice.py +3 -5
  5. pygpt_net/controller/audio/audio.py +9 -6
  6. pygpt_net/controller/audio/ui.py +263 -0
  7. pygpt_net/controller/chat/common.py +17 -1
  8. pygpt_net/controller/kernel/kernel.py +2 -0
  9. pygpt_net/controller/notepad/notepad.py +10 -1
  10. pygpt_net/controller/theme/markdown.py +2 -0
  11. pygpt_net/controller/theme/theme.py +4 -1
  12. pygpt_net/controller/ui/tabs.py +5 -0
  13. pygpt_net/core/audio/backend/native.py +114 -82
  14. pygpt_net/core/audio/backend/pyaudio.py +16 -19
  15. pygpt_net/core/audio/backend/pygame.py +12 -15
  16. pygpt_net/core/audio/capture.py +10 -9
  17. pygpt_net/core/audio/context.py +3 -6
  18. pygpt_net/core/command/command.py +2 -0
  19. pygpt_net/core/render/web/helpers.py +13 -3
  20. pygpt_net/core/render/web/renderer.py +3 -3
  21. pygpt_net/data/config/config.json +7 -5
  22. pygpt_net/data/config/models.json +3 -3
  23. pygpt_net/data/config/settings.json +24 -10
  24. pygpt_net/data/css/web-blocks.darkest.css +91 -0
  25. pygpt_net/data/css/web-chatgpt.css +7 -5
  26. pygpt_net/data/css/web-chatgpt.dark.css +5 -2
  27. pygpt_net/data/css/web-chatgpt.darkest.css +91 -0
  28. pygpt_net/data/css/web-chatgpt.light.css +8 -2
  29. pygpt_net/data/css/web-chatgpt_wide.css +7 -4
  30. pygpt_net/data/css/web-chatgpt_wide.dark.css +5 -2
  31. pygpt_net/data/css/web-chatgpt_wide.darkest.css +91 -0
  32. pygpt_net/data/css/web-chatgpt_wide.light.css +9 -6
  33. pygpt_net/data/locale/locale.de.ini +2 -0
  34. pygpt_net/data/locale/locale.en.ini +2 -0
  35. pygpt_net/data/locale/locale.es.ini +2 -0
  36. pygpt_net/data/locale/locale.fr.ini +2 -0
  37. pygpt_net/data/locale/locale.it.ini +2 -0
  38. pygpt_net/data/locale/locale.pl.ini +3 -1
  39. pygpt_net/data/locale/locale.uk.ini +2 -0
  40. pygpt_net/data/locale/locale.zh.ini +2 -0
  41. pygpt_net/data/themes/dark_darkest.css +31 -0
  42. pygpt_net/data/themes/dark_darkest.xml +10 -0
  43. pygpt_net/plugin/audio_input/simple.py +5 -10
  44. pygpt_net/plugin/audio_output/plugin.py +4 -17
  45. pygpt_net/plugin/tuya/__init__.py +12 -0
  46. pygpt_net/plugin/tuya/config.py +256 -0
  47. pygpt_net/plugin/tuya/plugin.py +117 -0
  48. pygpt_net/plugin/tuya/worker.py +588 -0
  49. pygpt_net/plugin/wikipedia/__init__.py +12 -0
  50. pygpt_net/plugin/wikipedia/config.py +228 -0
  51. pygpt_net/plugin/wikipedia/plugin.py +114 -0
  52. pygpt_net/plugin/wikipedia/worker.py +430 -0
  53. pygpt_net/provider/core/config/patch.py +11 -0
  54. pygpt_net/ui/layout/chat/input.py +5 -2
  55. pygpt_net/ui/main.py +1 -2
  56. pygpt_net/ui/widget/audio/bar.py +5 -1
  57. pygpt_net/ui/widget/tabs/output.py +2 -0
  58. pygpt_net/ui/widget/textarea/input.py +483 -55
  59. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.28.dist-info}/METADATA +78 -35
  60. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.28.dist-info}/RECORD +63 -49
  61. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.28.dist-info}/LICENSE +0 -0
  62. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.28.dist-info}/WHEEL +0 -0
  63. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.28.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:
@@ -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.08.27 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from functools import partial
@@ -149,6 +149,9 @@ class Input:
149
149
  self.window.ui.plugin_addon['audio.input'] = AudioInput(self.window)
150
150
  self.window.ui.plugin_addon['audio.input.btn'] = AudioInputButton(self.window)
151
151
 
152
+ self.window.ui.plugin_addon['audio.input'].setVisible(False)
153
+ self.window.ui.plugin_addon['audio.input.btn'].setVisible(False)
154
+
152
155
  grid = QGridLayout()
153
156
 
154
157
  center_layout = QHBoxLayout()
@@ -204,7 +207,7 @@ class Input:
204
207
 
205
208
  nodes['input.stop_btn'] = QPushButton(trans("input.btn.stop"))
206
209
  nodes['input.stop_btn'].setVisible(False)
207
- nodes['input.stop_btn'].clicked.connect(controller.kernel.stop)
210
+ nodes['input.stop_btn'].clicked.connect(controller.chat.common.handle_stop)
208
211
 
209
212
  nodes['input.update_btn'] = QPushButton(trans("input.btn.update"))
210
213
  nodes['input.update_btn'].setVisible(False)
pygpt_net/ui/main.py CHANGED
@@ -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.08.27 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -254,7 +254,6 @@ class MainWindow(QMainWindow, QtStyleTools):
254
254
  """
255
255
  message = message if isinstance(message, str) else str(message)
256
256
  self.dispatch(KernelEvent(KernelEvent.STATUS, {"status": message}))
257
- del message # free memory
258
257
 
259
258
  @Slot(str)
260
259
  def update_state(self, state: str):
@@ -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.08.27 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -25,6 +25,8 @@ class InputBar(QWidget):
25
25
 
26
26
  :param level: level
27
27
  """
28
+ if self._level == level:
29
+ return
28
30
  self._level = level
29
31
  self.update()
30
32
 
@@ -69,6 +71,8 @@ class OutputBar(QWidget):
69
71
 
70
72
  :param level: level
71
73
  """
74
+ if self._level == level:
75
+ return
72
76
  self._level = level
73
77
  self.update()
74
78
 
@@ -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):