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
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,13 @@
1
+ 2.6.28 (2025-08-27)
2
+
3
+ - Added new plugins: Tuya (IoT) and Wikipedia.
4
+ - Improved formatting of JSON command output.
5
+ - Fixed CSS issues.
6
+
7
+ 2.6.27 (2025-08-26)
8
+
9
+ - Simplified audio input: A microphone icon has been added to the input field.
10
+
1
11
  2.6.26 (2025-08-26)
2
12
 
3
13
  - Added new provider: OpenRouter (#133).
pygpt_net/__init__.py CHANGED
@@ -6,15 +6,15 @@
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.26 00:00:00 #
9
+ # Updated Date: 2025.08.27 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.6.26"
17
- __build__ = "2025-08-26"
16
+ __version__ = "2.6.28"
17
+ __build__ = "2025-08-27"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
pygpt_net/app.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.26 23:00:00 #
9
+ # Updated Date: 2025.08.27 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -89,6 +89,8 @@ from pygpt_net.plugin.slack import Plugin as SlackPlugin
89
89
  from pygpt_net.plugin.github import Plugin as GithubPlugin
90
90
  from pygpt_net.plugin.bitbucket import Plugin as BitbucketPlugin
91
91
  from pygpt_net.plugin.server import Plugin as ServerPlugin
92
+ from pygpt_net.plugin.tuya import Plugin as TuyaPlugin
93
+ from pygpt_net.plugin.wikipedia import Plugin as WikipediaPlugin
92
94
 
93
95
  # agents (Llama-index)
94
96
  # from pygpt_net.provider.agents.llama_index.legacy.openai import OpenAIAgent
@@ -410,6 +412,8 @@ def run(**kwargs):
410
412
  launcher.add_plugin(GithubPlugin())
411
413
  launcher.add_plugin(BitbucketPlugin())
412
414
  launcher.add_plugin(ServerPlugin())
415
+ launcher.add_plugin(TuyaPlugin())
416
+ launcher.add_plugin(WikipediaPlugin())
413
417
 
414
418
  # register custom plugins
415
419
  plugins = kwargs.get('plugins', None)
@@ -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 typing import Optional, List, Dict, Any
@@ -233,10 +233,8 @@ class Voice(QObject):
233
233
  # stop audio output if playing
234
234
  self.window.controller.audio.stop_output()
235
235
 
236
- # set audio volume bar
237
- self.window.core.audio.capture.set_bar(
238
- self.window.ui.nodes['voice.control.btn'].bar
239
- )
236
+ # set audio input mode
237
+ self.window.core.audio.capture.set_mode("control")
240
238
 
241
239
  # start timeout timer to prevent infinite recording
242
240
  timeout = int(self.window.core.config.get('audio.input.timeout', 120) or 0) # get timeout
@@ -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
@@ -19,6 +19,8 @@ from pygpt_net.core.events import Event, BaseEvent
19
19
  from pygpt_net.item.ctx import CtxItem
20
20
  from pygpt_net.utils import trans
21
21
 
22
+ from .ui import UI
23
+
22
24
 
23
25
  class Audio:
24
26
  def __init__(self, window=None):
@@ -28,6 +30,7 @@ class Audio:
28
30
  :param window: Window instance
29
31
  """
30
32
  self.window = window
33
+ self.ui = UI(window)
31
34
  self.input_allowed_tabs = [
32
35
  Tab.TAB_NOTEPAD,
33
36
  Tab.TAB_CHAT,
@@ -410,18 +413,18 @@ class Audio:
410
413
  # show/hide extra options
411
414
  tab = self.window.controller.ui.tabs.get_current_tab()
412
415
  if tab.type == Tab.TAB_NOTEPAD:
413
- self.window.ui.plugin_addon['audio.input.btn'].notepad_footer.setVisible(True)
416
+ self.window.controller.audio.ui.on_input_continuous_enable("input")
414
417
  else:
415
- self.window.ui.plugin_addon['audio.input.btn'].notepad_footer.setVisible(False)
418
+ self.window.controller.audio.ui.on_input_continuous_disable("input")
416
419
  if is_advanced:
417
- self.window.ui.plugin_addon['audio.input.btn'].setVisible(False)
420
+ self.window.controller.audio.ui.on_input_disable("input")
418
421
  self.window.ui.plugin_addon['audio.input'].setVisible(True)
419
422
  else:
420
- self.window.ui.plugin_addon['audio.input.btn'].setVisible(True) # simple recording
423
+ self.window.controller.audio.ui.on_input_enable("input")
421
424
  self.window.ui.plugin_addon['audio.input'].setVisible(False) # advanced recording
422
425
  self.toggle_input_icon(True)
423
426
  else:
424
- self.window.ui.plugin_addon['audio.input.btn'].setVisible(False) # simple recording
427
+ self.window.controller.audio.ui.on_input_disable("input") # simple recording
425
428
  self.window.ui.plugin_addon['audio.input'].setVisible(False) # advanced recording
426
429
  self.toggle_input_icon(False)
427
430
 
@@ -0,0 +1,263 @@
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 07:00:00 #
10
+ # ================================================== #
11
+
12
+ from pygpt_net.utils import trans
13
+
14
+
15
+ class UI:
16
+ def __init__(self, window=None):
17
+ """
18
+ Audio/voice UI controller
19
+
20
+ :param window: Window instance
21
+ """
22
+ self.window = window
23
+ self.recording = False
24
+
25
+ self._input_bar = None
26
+ self._input_btn = None
27
+ self._input_control_bar = None
28
+ self._input_control_btn = None
29
+ self._output_bar = None
30
+ self._output_btn = None
31
+
32
+ def get_input_bar(self):
33
+ """
34
+ Get input bar widget
35
+
36
+ :return: input bar widget
37
+ """
38
+ if self._input_bar is not None:
39
+ return self._input_bar
40
+ if "audio.input.btn" not in self.window.ui.plugin_addon:
41
+ return None
42
+ self._input_bar = self.window.ui.plugin_addon['audio.input.btn'].bar
43
+ return self._input_bar
44
+
45
+ def get_input_control_bar(self):
46
+ """
47
+ Get input control bar widget
48
+
49
+ :return: input control bar widget
50
+ """
51
+ if self._input_control_bar is not None:
52
+ return self._input_control_bar
53
+ if 'voice.control.btn' not in self.window.ui.nodes:
54
+ return None
55
+ self._input_control_bar = self.window.ui.nodes['voice.control.btn'].bar
56
+ return self._input_control_bar
57
+
58
+ def get_output_bar(self):
59
+ """
60
+ Get output bar widget
61
+
62
+ :return: output bar widget
63
+ """
64
+ if self._output_bar is not None:
65
+ return self._output_bar
66
+ if "audio.output.bar" not in self.window.ui.plugin_addon:
67
+ return None
68
+ self._output_bar = self.window.ui.plugin_addon['audio.output.bar']
69
+ return self._output_bar
70
+
71
+ def get_input_btn(self):
72
+ """
73
+ Get input bar widget
74
+
75
+ :return: input btn widget
76
+ """
77
+ if self._input_btn is not None:
78
+ return self._input_btn
79
+ if "audio.input.btn" not in self.window.ui.plugin_addon:
80
+ return None
81
+ self._input_btn = self.window.ui.plugin_addon['audio.input.btn']
82
+ return self._input_btn
83
+
84
+ def get_input_control_btn(self):
85
+ """
86
+ Get input control btn widget
87
+
88
+ :return: input control btn widget
89
+ """
90
+ if self._input_control_btn is not None:
91
+ return self._input_control_btn
92
+ if 'voice.control.btn' not in self.window.ui.nodes:
93
+ return None
94
+ self._input_control_btn = self.window.ui.nodes['voice.control.btn']
95
+ return self._input_control_btn
96
+
97
+ def get_output_btn(self):
98
+ """
99
+ Get output btn widget
100
+
101
+ :return: output btn widget
102
+ """
103
+ if self._output_btn is not None:
104
+ return self._output_btn
105
+ if "audio.output" not in self.window.ui.plugin_addon:
106
+ return None
107
+ self._output_btn = self.window.ui.plugin_addon['audio.output']
108
+ return self._output_btn
109
+
110
+ # --- Input events ---
111
+
112
+ def on_input_volume_change(self, value: int, mode: str = 'input'):
113
+ """
114
+ Input volume slider change event
115
+
116
+ :param value: slider value
117
+ :param mode: 'input' or 'control' mode
118
+ """
119
+ bar = self.get_output_bar()
120
+ if bar:
121
+ bar.setLevel(value)
122
+
123
+ def on_input_device_change(self, index: int):
124
+ """
125
+ Input device combo change event
126
+
127
+ :param index: combo index
128
+ """
129
+ pass
130
+
131
+ def on_input_enable(self, mode: str = 'input'):
132
+ """
133
+ Input enable checkbox change event
134
+
135
+ :param mode: 'input' or 'control' mode
136
+ """
137
+ self.window.ui.nodes['input'].set_icon_visible("mic", True)
138
+ if mode == "input":
139
+ return
140
+ btn = self.get_input_btn() if mode == 'input' else self.get_input_control_btn()
141
+ if btn:
142
+ btn.setVisible(True)
143
+
144
+ def on_input_disable(self, mode: str = 'input'):
145
+ """
146
+ Input disabled button click event
147
+
148
+ :param mode: 'input' or 'control' mode
149
+ """
150
+ self.window.ui.nodes['input'].set_icon_visible("mic", False)
151
+ if mode == "input":
152
+ return
153
+ btn = self.get_input_btn() if mode == 'input' else self.get_input_control_btn()
154
+ if btn:
155
+ btn.setVisible(False)
156
+
157
+ def on_input_continuous_enable(self, checked: bool, mode: str = 'input'):
158
+ """
159
+ Input enable checkbox change event
160
+
161
+ :param checked: checkbox state
162
+ :param mode: 'input' or 'control' mode
163
+ """
164
+ btn = self.get_input_btn() if mode == 'input' else self.get_input_control_btn()
165
+ if btn:
166
+ btn.notepad_footer.setVisible(True)
167
+
168
+ def on_input_continuous_disable(self, mode: str = 'input'):
169
+ """
170
+ Input disabled button click event
171
+
172
+ :param mode: 'input' or 'control' mode
173
+ """
174
+ btn = self.get_input_btn() if mode == 'input' else self.get_input_control_btn()
175
+ if btn:
176
+ btn.notepad_footer.setVisible(False)
177
+
178
+ def on_input_begin(self, mode: str = 'input'):
179
+ """
180
+ Input begin button click event
181
+
182
+ :param mode: 'input' or 'control' mode
183
+ """
184
+ self.recording = True
185
+ self.window.ui.nodes['input'].set_icon_state("mic", True)
186
+ if mode == "input":
187
+ self.window.controller.chat.common.lock_input()
188
+ return
189
+ btn = self.get_input_btn() if mode == 'input' else self.get_input_control_btn()
190
+ btn.btn_toggle.setText(trans('audio.speak.btn.stop'))
191
+ btn.btn_toggle.setToolTip(trans('audio.speak.btn.stop.tooltip'))
192
+
193
+ def on_input_end(self, mode: str = 'input'):
194
+ """
195
+ Input end button click event
196
+
197
+ :param mode: 'input' or 'control' mode
198
+ """
199
+ self.recording = False
200
+ self.window.ui.nodes['input'].set_icon_state("mic", False)
201
+ if mode == "input":
202
+ self.window.controller.chat.common.unlock_input()
203
+ return
204
+ btn = self.get_input_btn() if mode == 'input' else self.get_input_control_btn()
205
+ btn.btn_toggle.setText(trans('audio.speak.btn'))
206
+ btn.btn_toggle.setToolTip(trans('audio.speak.btn.tooltip'))
207
+
208
+ def on_input_cancel(self):
209
+ """
210
+ Input cancel button click event
211
+ """
212
+ pass
213
+
214
+ # --- Output events ---
215
+
216
+ def on_output_volume_change(self, value: int):
217
+ """
218
+ Output volume slider change event
219
+ :param value: slider value
220
+ """
221
+ if self.recording:
222
+ return
223
+ bar = self.get_output_bar()
224
+ if bar:
225
+ bar.setLevel(value)
226
+
227
+ def on_output_device_change(self, index: int):
228
+ """
229
+ Output device combo change event
230
+
231
+ :param index: combo index
232
+ """
233
+ pass
234
+
235
+ def on_output_enable(self):
236
+ """
237
+ Output enable checkbox change event
238
+ """
239
+ pass
240
+
241
+ def on_output_disable(self):
242
+ """
243
+ Output disabled button click event
244
+ """
245
+ pass
246
+
247
+ def on_output_begin(self):
248
+ """
249
+ Output begin button click event
250
+ """
251
+ self.window.controller.audio.start_speaking()
252
+
253
+ def on_output_end(self):
254
+ """
255
+ Output end button click event
256
+ """
257
+ pass
258
+
259
+ def on_output_cancel(self):
260
+ """
261
+ Output cancel button click event
262
+ """
263
+ pass
@@ -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.23 15:00:00 #
9
+ # Updated Date: 2025.08.27 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -250,6 +250,22 @@ class Common:
250
250
  unlock = False
251
251
  return unlock
252
252
 
253
+ def handle_stop(self):
254
+ """Handle stop"""
255
+ # stop voice recording if active
256
+ if self.window.controller.access.voice.is_recording:
257
+ self.window.controller.access.voice.stop_recording(timeout=True)
258
+
259
+ if self.window.core.plugins.get("audio_input").handler_simple.is_recording:
260
+ self.window.core.plugins.get("audio_input").handler_simple.stop_recording(timeout=False)
261
+ return
262
+
263
+ # stop audio output if playing
264
+ self.window.controller.audio.stop_output()
265
+
266
+ # stop generating if active
267
+ self.window.controller.kernel.stop()
268
+
253
269
  def auto_unlock(self, ctx: CtxItem) -> bool:
254
270
  """
255
271
  Auto unlock input after end
@@ -13,6 +13,7 @@ import threading
13
13
  from typing import Any, Dict, Optional, Union, List
14
14
 
15
15
  from PySide6.QtCore import QObject, Slot
16
+ from PySide6.QtWidgets import QApplication
16
17
 
17
18
  from pygpt_net.core.types import (
18
19
  MODE_AGENT,
@@ -337,6 +338,7 @@ class Kernel:
337
338
  """
338
339
  self.status = status
339
340
  self.window.ui.status(status)
341
+ QApplication.processEvents() # process events to update UI
340
342
 
341
343
  def resume(self):
342
344
  """
@@ -350,4 +350,13 @@ class Notepad:
350
350
  if not widget.opened:
351
351
  widget.opened = True
352
352
  if idx not in self.opened_idx:
353
- self.opened_idx.add(idx)
353
+ self.opened_idx.add(idx)
354
+
355
+ def focus_opened(self, tab = None):
356
+ """Focus opened notepad"""
357
+ if tab is None:
358
+ tab = self.window.controller.ui.tabs.get_current_tab()
359
+ if tab is not None and tab.type == Tab.TAB_NOTEPAD:
360
+ widget = self.window.ui.notepad.get(tab.data_id)
361
+ if widget is not None:
362
+ QTimer.singleShot(100, widget.textarea.setFocus)
@@ -101,6 +101,8 @@ class Markdown:
101
101
  color = '.light'
102
102
  else:
103
103
  color = '.dark'
104
+ if base_name == 'web' and theme.endswith('darkest'):
105
+ color = '.darkest'
104
106
 
105
107
  # load CSS, app + user
106
108
  file_base = name + suffix + '.css'
@@ -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.25 20:00:00 #
9
+ # Updated Date: 2025.08.27 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -50,6 +50,9 @@ class Theme:
50
50
 
51
51
  :param name: theme name
52
52
  """
53
+ current = self.window.core.config.get('theme')
54
+ if name == current:
55
+ return
53
56
  self.window.update_status(trans("status.reloading"))
54
57
  QApplication.processEvents()
55
58
  self.toggle(name, force=True)
@@ -170,6 +170,7 @@ class Tabs:
170
170
  w = self.window
171
171
  core = w.core
172
172
  tabs_core = core.tabs
173
+ appended = self.appended
173
174
 
174
175
  tab = tabs_core.get_tab_by_index(idx, column_idx)
175
176
  if tab is None:
@@ -185,6 +186,8 @@ class Tabs:
185
186
  if meta is not None:
186
187
  w.controller.ctx.load(meta.id)
187
188
  self.create_new_on_tab = True
189
+ else:
190
+ self.current = idx
188
191
 
189
192
  prev_tab = self.current
190
193
  prev_column = self.column_idx
@@ -197,6 +200,8 @@ class Tabs:
197
200
  if tab.type == Tab.TAB_NOTEPAD:
198
201
  w.controller.notepad.opened_once = True
199
202
  w.controller.notepad.on_open(idx, column_idx)
203
+ if appended:
204
+ w.controller.notepad.focus_opened(tab)
200
205
  elif tab.type == Tab.TAB_CHAT:
201
206
  meta_id = tab.data_id
202
207
  if meta_id is None: