pygpt-net 2.5.88__py3-none-any.whl → 2.5.90__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 (77) hide show
  1. pygpt_net/CHANGELOG.txt +11 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +9 -1
  4. pygpt_net/controller/access/voice.py +6 -5
  5. pygpt_net/controller/audio/audio.py +3 -0
  6. pygpt_net/controller/chat/output.py +2 -2
  7. pygpt_net/controller/config/placeholder.py +43 -1
  8. pygpt_net/controller/ctx/ctx.py +2 -2
  9. pygpt_net/controller/kernel/kernel.py +2 -3
  10. pygpt_net/controller/layout/layout.py +10 -4
  11. pygpt_net/controller/model/model.py +14 -1
  12. pygpt_net/controller/settings/editor.py +35 -1
  13. pygpt_net/controller/ui/tabs.py +53 -3
  14. pygpt_net/controller/ui/ui.py +2 -0
  15. pygpt_net/core/audio/audio.py +54 -37
  16. pygpt_net/core/audio/backend/__init__.py +0 -0
  17. pygpt_net/core/audio/backend/native.py +604 -0
  18. pygpt_net/core/audio/backend/pyaudio.py +557 -0
  19. pygpt_net/core/audio/backend/pygame.py +503 -0
  20. pygpt_net/core/audio/capture.py +66 -275
  21. pygpt_net/core/audio/output.py +91 -0
  22. pygpt_net/core/ctx/ctx.py +13 -1
  23. pygpt_net/core/debug/debug.py +30 -13
  24. pygpt_net/core/events/base.py +2 -1
  25. pygpt_net/core/notepad/notepad.py +2 -2
  26. pygpt_net/core/render/plain/pid.py +2 -2
  27. pygpt_net/core/render/web/pid.py +2 -2
  28. pygpt_net/core/tabs/tab.py +1 -1
  29. pygpt_net/core/tabs/tabs.py +61 -1
  30. pygpt_net/core/text/utils.py +3 -3
  31. pygpt_net/core/web/helpers.py +4 -2
  32. pygpt_net/data/config/config.json +6 -3
  33. pygpt_net/data/config/models.json +3 -3
  34. pygpt_net/data/config/settings.json +43 -7
  35. pygpt_net/data/css/style.dark.css +3 -0
  36. pygpt_net/data/css/style.light.css +3 -0
  37. pygpt_net/data/css/web-blocks.css +35 -0
  38. pygpt_net/data/css/web-blocks.dark.css +4 -0
  39. pygpt_net/data/css/web-blocks.light.css +5 -0
  40. pygpt_net/data/locale/locale.de.ini +10 -2
  41. pygpt_net/data/locale/locale.en.ini +8 -0
  42. pygpt_net/data/locale/locale.es.ini +10 -2
  43. pygpt_net/data/locale/locale.fr.ini +10 -2
  44. pygpt_net/data/locale/locale.it.ini +10 -2
  45. pygpt_net/data/locale/locale.pl.ini +10 -2
  46. pygpt_net/data/locale/locale.uk.ini +10 -2
  47. pygpt_net/data/locale/locale.zh.ini +9 -1
  48. pygpt_net/item/assistant.py +2 -1
  49. pygpt_net/item/attachment.py +1 -1
  50. pygpt_net/item/calendar_note.py +2 -1
  51. pygpt_net/item/ctx.py +4 -1
  52. pygpt_net/item/index.py +2 -1
  53. pygpt_net/item/mode.py +2 -1
  54. pygpt_net/item/model.py +1 -1
  55. pygpt_net/item/notepad.py +2 -1
  56. pygpt_net/item/preset.py +3 -1
  57. pygpt_net/item/prompt.py +2 -1
  58. pygpt_net/launcher.py +4 -4
  59. pygpt_net/plugin/audio_input/simple.py +5 -7
  60. pygpt_net/plugin/audio_output/plugin.py +16 -6
  61. pygpt_net/plugin/audio_output/worker.py +15 -92
  62. pygpt_net/plugin/base/worker.py +0 -16
  63. pygpt_net/provider/core/config/patch.py +23 -1
  64. pygpt_net/tools/code_interpreter/tool.py +33 -21
  65. pygpt_net/ui/layout/chat/chat.py +14 -0
  66. pygpt_net/ui/layout/chat/input.py +6 -1
  67. pygpt_net/ui/layout/chat/output.py +7 -7
  68. pygpt_net/ui/widget/element/labels.py +63 -4
  69. pygpt_net/ui/widget/lists/attachment.py +19 -19
  70. pygpt_net/ui/widget/lists/attachment_ctx.py +12 -13
  71. pygpt_net/ui/widget/lists/preset.py +14 -11
  72. pygpt_net/ui/widget/textarea/web.py +12 -8
  73. {pygpt_net-2.5.88.dist-info → pygpt_net-2.5.90.dist-info}/METADATA +19 -2
  74. {pygpt_net-2.5.88.dist-info → pygpt_net-2.5.90.dist-info}/RECORD +77 -72
  75. {pygpt_net-2.5.88.dist-info → pygpt_net-2.5.90.dist-info}/LICENSE +0 -0
  76. {pygpt_net-2.5.88.dist-info → pygpt_net-2.5.90.dist-info}/WHEEL +0 -0
  77. {pygpt_net-2.5.88.dist-info → pygpt_net-2.5.90.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,14 @@
1
+ 2.5.90 (2025-08-07)
2
+
3
+ - Fix: Initialize context summary if a conversation starts with a tool call.
4
+ - Fix: Store splitter positions even if the object is deleted from memory.
5
+ - Update: CSS improvements.
6
+
7
+ 2.5.89 (2025-08-07)
8
+
9
+ - Added audio output device selection in Config -> Audio - issue #117
10
+ - Added audio input and output backend selections in Config -> Audio.
11
+
1
12
  2.5.88 (2025-08-06)
2
13
 
3
14
  - Optimized the process of unloading tabs from memory.
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.06 00:00:00 #
9
+ # Updated Date: 2025.08.07 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.5.88"
17
- __build__ = "2025-08-06"
16
+ __version__ = "2.5.90"
17
+ __build__ = "2025-08-07"
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,17 +6,25 @@
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.02 03:00:00 #
9
+ # Updated Date: 2025.08.07 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
13
13
  import builtins
14
14
  import io
15
+ import platform
15
16
 
16
17
  # disable warnings
17
18
  os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
18
19
  os.environ["QT_LOGGING_RULES"] = "qt.multimedia.ffmpeg=false"
19
20
 
21
+ if platform.system() == 'Windows':
22
+ # fix ffmpeg bug: [SWR] Output channel layout "" is invalid or unsupported.
23
+ os.environ['QT_MEDIA_BACKEND'] = 'windows'
24
+
25
+ # enable debug logging
26
+ # os.environ["QT_LOGGING_RULES"] = "*.debug=true"
27
+
20
28
  _original_open = builtins.open
21
29
 
22
30
  def open_wrapper(file, mode='r', *args, **kwargs):
@@ -6,14 +6,14 @@
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.03 14:00:00 #
9
+ # Updated Date: 2025.08.07 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional, List, Dict, Any
13
13
 
14
14
  import os
15
15
 
16
- from PySide6.QtCore import QTimer, Slot
16
+ from PySide6.QtCore import QTimer, Slot, QObject
17
17
  from qasync import QApplication
18
18
 
19
19
  from pygpt_net.item.ctx import CtxItem
@@ -22,7 +22,7 @@ from pygpt_net.core.events import ControlEvent, AppEvent
22
22
  from pygpt_net.utils import trans
23
23
 
24
24
 
25
- class Voice:
25
+ class Voice(QObject):
26
26
 
27
27
  MIN_FRAMES = 25 # minimum frames to start transcription
28
28
  PLAY_DELAY = 500 # ms, delay before playing audio event
@@ -33,6 +33,7 @@ class Voice:
33
33
 
34
34
  :param window: Window instance
35
35
  """
36
+ super().__init__()
36
37
  self.window = window
37
38
  self.is_recording = False
38
39
  self.timer = None
@@ -212,6 +213,7 @@ class Voice:
212
213
 
213
214
  def start_recording(self):
214
215
  """Start recording"""
216
+ self.window.controller.kernel.resume()
215
217
  # display snap warning if not displayed yet
216
218
  if (not self.window.core.config.get("audio.input.snap", False)
217
219
  or not self.window.core.config.has("audio.input.snap")):
@@ -290,8 +292,7 @@ class Voice:
290
292
  return
291
293
 
292
294
  if self.window.core.audio.capture.has_frames():
293
- frames = self.window.core.audio.capture.get_frames()
294
- if len(frames) < self.MIN_FRAMES:
295
+ if not self.window.core.audio.capture.has_min_frames():
295
296
  self.window.update_status(trans("status.audio.too_short"))
296
297
  self.window.dispatch(AppEvent(AppEvent.VOICE_CONTROL_STOPPED)) # app event
297
298
  return
@@ -12,6 +12,8 @@
12
12
  import os
13
13
  from typing import Optional
14
14
 
15
+ from qasync import QApplication
16
+
15
17
  from pygpt_net.core.tabs.tab import Tab
16
18
  from pygpt_net.core.events import Event, BaseEvent
17
19
  from pygpt_net.item.ctx import CtxItem
@@ -351,6 +353,7 @@ class Audio:
351
353
  :param text: text to play
352
354
  """
353
355
  self.window.update_status(trans("status.audio.start"))
356
+ QApplication.processEvents() # process events to update UI
354
357
 
355
358
  def on_play(self, event: str):
356
359
  """
@@ -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.05 21:00:00 #
9
+ # Updated Date: 2025.08.07 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import gc
@@ -226,7 +226,7 @@ class Output:
226
226
  self.window.controller.chat.common.unlock_input() # unlock input
227
227
 
228
228
  # handle ctx name (generate title from summary if not initialized)
229
- if not reply and not internal: # don't call if reply or internal mode
229
+ if not ctx.meta or not ctx.meta.initialized: # don't call if reply or internal mode
230
230
  if self.window.core.config.get('ctx.auto_summary'):
231
231
  self.log("Calling for prepare context name...")
232
232
  self.window.controller.ctx.prepare_name(ctx) # async
@@ -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.07.30 00:00:00 #
9
+ # Updated Date: 2025.08.07 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Dict, Any, List
@@ -136,6 +136,12 @@ class Placeholder:
136
136
  return self.get_voice_control_actions()
137
137
  elif id == "audio_input_devices":
138
138
  return self.get_audio_input_devices()
139
+ elif id == "audio_output_devices":
140
+ return self.get_audio_output_devices()
141
+ elif id == "audio_input_backend":
142
+ return self.get_audio_input_backend()
143
+ elif id == "audio_output_backend":
144
+ return self.get_audio_output_backend()
139
145
  elif id == "audio_tts_whisper_voices":
140
146
  return self.get_audio_tts_whisper_voices()
141
147
  else:
@@ -165,6 +171,42 @@ class Placeholder:
165
171
  data.append({str(device[0]): device[1]})
166
172
  return data
167
173
 
174
+ def get_audio_output_devices(self) -> List[Dict[str, str]]:
175
+ """
176
+ Get audio output devices list
177
+
178
+ :return: placeholders list
179
+ """
180
+ devices = self.window.core.audio.get_output_devices()
181
+ data = []
182
+ for device in devices:
183
+ data.append({str(device[0]): device[1]})
184
+ return data
185
+
186
+ def get_audio_input_backend(self) -> List[Dict[str, str]]:
187
+ """
188
+ Get audio input backends list
189
+
190
+ :return: placeholders list
191
+ """
192
+ items = self.window.core.audio.get_input_backends()
193
+ data = []
194
+ for item in items:
195
+ data.append({str(item[0]): item[1]})
196
+ return data
197
+
198
+ def get_audio_output_backend(self) -> List[Dict[str, str]]:
199
+ """
200
+ Get audio output backends list
201
+
202
+ :return: placeholders list
203
+ """
204
+ items = self.window.core.audio.get_output_backends()
205
+ data = []
206
+ for item in items:
207
+ data.append({str(item[0]): item[1]})
208
+ return data
209
+
168
210
  def get_langchain_providers(self) -> List[Dict[str, str]]:
169
211
  """
170
212
  Get Langchain LLM provider placeholders list
@@ -6,9 +6,9 @@
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.06 01:00:00 #
9
+ # Updated Date: 2025.08.07 03:00:00 #
10
10
  # ================================================== #
11
-
11
+ import gc
12
12
  from typing import Optional, List
13
13
 
14
14
  from PySide6.QtCore import QModelIndex, QTimer
@@ -6,10 +6,9 @@
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.05 00:00:00 #
9
+ # Updated Date: 2025.08.06 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
- import asyncio
13
12
  import threading
14
13
  import time
15
14
  from typing import Any, Dict, Optional, Union, List
@@ -289,7 +288,7 @@ class Kernel(QObject):
289
288
  """Terminate kernel"""
290
289
  self.window.dispatch(KernelEvent(KernelEvent.TERMINATE))
291
290
  self.stop(exit=True)
292
- self.window.ui.hide_loading()
291
+ # self.window.ui.hide_loading()
293
292
  self.window.controller.plugins.destroy()
294
293
 
295
294
  def stop(self, exit: bool = False):
@@ -112,8 +112,11 @@ class Layout:
112
112
  # do not save main splitter state if notepad was not opened yet
113
113
  if splitter == "calendar" and not self.window.controller.notepad.opened_once:
114
114
  continue
115
- if splitter in self.window.ui.splitters:
116
- data[splitter] = self.window.ui.splitters[splitter].sizes()
115
+ try:
116
+ if splitter in self.window.ui.splitters:
117
+ data[splitter] = self.window.ui.splitters[splitter].sizes()
118
+ except Exception as e:
119
+ pass
117
120
  self.window.core.config.set('layout.splitters', data)
118
121
 
119
122
  def splitters_restore(self):
@@ -151,8 +154,11 @@ class Layout:
151
154
 
152
155
  # notepads
153
156
  for id in self.window.ui.notepad:
154
- scroll_id = "notepad." + str(id)
155
- data[scroll_id] = self.window.ui.notepad[id].textarea.verticalScrollBar().value()
157
+ try:
158
+ scroll_id = "notepad." + str(id)
159
+ data[scroll_id] = self.window.ui.notepad[id].textarea.verticalScrollBar().value()
160
+ except Exception as e:
161
+ pass
156
162
  self.window.core.config.set('layout.scroll', data)
157
163
 
158
164
  def scroll_restore(self):
@@ -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.07.12 19:00:00 #
9
+ # Updated Date: 2025.08.07 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional
@@ -54,6 +54,19 @@ class Model:
54
54
  self.window.controller.ui.update()
55
55
  self.window.dispatch(AppEvent(AppEvent.MODEL_SELECTED)) # app event
56
56
 
57
+ # update model in preset
58
+ preset = self.window.core.config.get('preset')
59
+ if preset and preset != "*":
60
+ preset_data = self.window.core.presets.get_by_id(mode, preset)
61
+ if preset_data:
62
+ preset_data.model = model
63
+ self.window.core.presets.save(preset)
64
+
65
+ # update model in current ctx meta
66
+ self.window.core.ctx.model = model
67
+ self.window.core.ctx.last_model = model
68
+ self.window.core.ctx.update_model_in_current(model)
69
+
57
70
  def next(self):
58
71
  """Select next model"""
59
72
  mode = self.window.core.config.get('mode')
@@ -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.07.22 22:00:00 #
9
+ # Updated Date: 2025.08.07 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -67,6 +67,8 @@ class Editor:
67
67
  self.window.ui.add_hook("update.config.render.code_syntax", self.hook_update)
68
68
  self.window.ui.add_hook("update.config.theme.style", self.hook_update)
69
69
  self.window.ui.add_hook("update.config.llama.idx.chat.agent.render.all", self.hook_update)
70
+ self.window.ui.add_hook("update.config.audio.input.backend", self.hook_update)
71
+ self.window.ui.add_hook("update.config.audio.output.backend", self.hook_update)
70
72
  # self.window.ui.add_hook("llama.idx.storage", self.hook_update) # vector store update
71
73
  # self.window.ui.add_hook("update.config.llama.idx.list", self.hook_update)
72
74
 
@@ -335,6 +337,38 @@ class Editor:
335
337
  self.window.core.config.set(key, value)
336
338
  self.window.controller.debug.toggle_menu()
337
339
 
340
+ # audio devices
341
+ elif key == "audio.input.backend":
342
+ self.window.core.config.set(key, value)
343
+ items = self.window.controller.config.placeholder.get_audio_input_devices()
344
+ option = self.options.get("audio.input.device", {})
345
+ default_id, _ = self.window.core.audio.get_default_input_device()
346
+ if default_id is None:
347
+ default_id = 0
348
+ self.window.controller.config.update_list(option, "config", "audio.input.device", items)
349
+ self.window.controller.config.apply_value(
350
+ parent_id='config',
351
+ key='audio.input.device',
352
+ option=option,
353
+ value=str(default_id) # default device,
354
+ )
355
+ self.window.core.config.set("audio.input.device", str(default_id)) # reset to default device
356
+ elif key == "audio.output.backend":
357
+ self.window.core.config.set(key, value)
358
+ items = self.window.controller.config.placeholder.get_audio_output_devices()
359
+ option = self.options.get("audio.output.device", {})
360
+ default_id, _ = self.window.core.audio.get_default_output_device()
361
+ if default_id is None:
362
+ default_id = 0
363
+ self.window.controller.config.update_list(option, "config", "audio.output.device", items)
364
+ self.window.controller.config.apply_value(
365
+ parent_id='config',
366
+ key='audio.output.device',
367
+ option=option,
368
+ value=str(default_id) # default device,
369
+ )
370
+ self.window.core.config.set("audio.output.device", str(default_id)) # reset to default device
371
+
338
372
  def toggle_collapsed(
339
373
  self,
340
374
  id: str,
@@ -6,10 +6,10 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.07.20 23:00:00 #
9
+ # Updated Date: 2025.08.07 18:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from typing import Any, Optional
12
+ from typing import Any, Optional, Tuple
13
13
 
14
14
  from PySide6.QtCore import QTimer
15
15
 
@@ -178,6 +178,7 @@ class Tabs:
178
178
 
179
179
  prev_tab = self.current
180
180
  prev_column = self.column_idx
181
+
181
182
  self.current = idx
182
183
  self.column_idx = column_idx
183
184
  self.window.controller.ui.mode.update()
@@ -287,6 +288,38 @@ class Tabs:
287
288
  """
288
289
  return self.window.core.tabs.get_min_idx_by_type(type, self.column_idx)
289
290
 
291
+ def get_prev_idx_from(self, idx: int) -> Tuple[int, bool]:
292
+ """
293
+ Get previous tab index from given index
294
+
295
+ :param idx: tab index
296
+ :return: tuple of previous index and boolean indicating if it exists
297
+ """
298
+ return self.window.core.tabs.get_prev_idx_from(idx, self.column_idx)
299
+
300
+ def get_next_idx_from(self, idx: int) -> Tuple[int, bool]:
301
+ """
302
+ Get next tab index from given index
303
+
304
+ :param idx: tab index
305
+ :return: tuple of next index and boolean indicating if it exists
306
+ """
307
+ return self.window.core.tabs.get_next_idx_from(idx, self.column_idx)
308
+
309
+ def get_after_close_idx(self, idx: int) -> int:
310
+ """
311
+ Get tab index after closing the given index
312
+
313
+ :param idx: tab index
314
+ :return: previous tab index if exists, otherwise None
315
+ """
316
+ prev_idx, exists = self.get_prev_idx_from(idx)
317
+ if exists:
318
+ return prev_idx
319
+ next_idx, exists = self.get_next_idx_from(idx)
320
+ if exists:
321
+ return next_idx
322
+
290
323
  def on_column_changed(self):
291
324
  """Column changed event"""
292
325
  if self.locked:
@@ -381,7 +414,21 @@ class Tabs:
381
414
  """
382
415
  if self.locked:
383
416
  return
417
+
418
+ previous_current = self.current
419
+ idx_after = None # <--- next tab index after close to switch to
420
+ if previous_current != idx and self.column_idx == column_idx:
421
+ idx_after = previous_current
422
+ if idx_after > idx:
423
+ idx_after -= 1 # if current is after closed tab, idx will be shifted
424
+
425
+ if idx_after is None:
426
+ idx_after = self.get_after_close_idx(idx) # find next tab index after close
427
+
384
428
  self.window.core.tabs.remove_tab_by_idx(idx, column_idx)
429
+ if idx_after is not None:
430
+ self.switch_tab_by_idx(idx_after, column_idx)
431
+
385
432
  self.on_changed()
386
433
  self.update_current()
387
434
  self.debug()
@@ -639,7 +686,10 @@ class Tabs:
639
686
 
640
687
  :param column_idx: column index
641
688
  """
642
- idx = self.get_current_idx(column_idx)
689
+ # append at the end of column
690
+ idx = self.window.core.tabs.get_max_idx_by_column(column_idx)
691
+ if idx == -1:
692
+ idx = 0
643
693
  self.append(
644
694
  type=Tab.TAB_CHAT,
645
695
  tool_id=None,
@@ -42,6 +42,8 @@ class UI:
42
42
  7: {'label': 'label.color.violet', 'color': QColor(238, 130, 238), 'font': QColor(255, 255, 255)},
43
43
  }
44
44
  self.stop_action = None
45
+ self.splitter_output_size_input = None
46
+ self.splitter_output_size_files = None
45
47
 
46
48
  def setup(self):
47
49
  """Setup UI"""
@@ -6,19 +6,17 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.07.14 00:00:00 #
9
+ # Updated Date: 2025.08.07 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
13
13
  from typing import Union, Optional, Tuple, List
14
14
 
15
- from PySide6.QtMultimedia import QMediaDevices
16
- from bs4 import UnicodeDammit
17
-
18
15
  from pygpt_net.provider.audio_input.base import BaseProvider as InputBaseProvider
19
16
  from pygpt_net.provider.audio_output.base import BaseProvider as OutputBaseProvider
20
17
 
21
18
  from .capture import Capture
19
+ from .output import Output
22
20
  from .whisper import Whisper
23
21
 
24
22
 
@@ -31,10 +29,11 @@ class Audio:
31
29
  """
32
30
  self.window = window
33
31
  self.capture = Capture(window)
32
+ self.output = Output(window)
34
33
  self.whisper = Whisper(window)
35
34
  self.providers = {
36
- "input": {},
37
- "output": {},
35
+ "input": {}, # audio transcription providers
36
+ "output": {}, # speech synthesis providers
38
37
  }
39
38
  self.last_error = None
40
39
 
@@ -44,37 +43,55 @@ class Audio:
44
43
 
45
44
  :return devices list: [(id, name)]
46
45
  """
47
- devices = QMediaDevices.audioInputs()
48
- devices_list = []
49
- for index, device in enumerate(devices):
50
- dammit = UnicodeDammit(device.description())
51
- devices_list.append((index, dammit.unicode_markup))
52
- return devices_list
53
-
54
- def is_device_compatible(self, device_index: int) -> bool:
55
- """
56
- Check if device is compatible
57
-
58
- :param device_index: device index
59
- :return: True if compatible
60
- """
61
- import pyaudio
62
- rate = int(self.window.core.config.get('audio.input.rate', 44100))
63
- channels = int(self.window.core.config.get('audio.input.channels', 1))
64
- p = pyaudio.PyAudio()
65
- info = p.get_device_info_by_index(device_index)
66
- try:
67
- p.is_format_supported(
68
- rate=rate,
69
- input_device=info['index'],
70
- input_channels=channels,
71
- input_format=pyaudio.paInt16)
72
- supported = True
73
- except ValueError as e:
74
- self.last_error = str(e)
75
- supported = False
76
- p.terminate()
77
- return supported
46
+ return self.capture.get_input_devices()
47
+
48
+ def get_output_devices(self) -> List[Tuple[int, str]]:
49
+ """
50
+ Get output devices
51
+
52
+ :return devices list: [(id, name)]
53
+ """
54
+ return self.output.get_output_devices()
55
+
56
+ def get_input_backends(self) -> List[Tuple[str, str]]:
57
+ """
58
+ Get input backends
59
+
60
+ :return backends list: [(id, name)]
61
+ """
62
+ choices = []
63
+ choices.append(("native", "Native / QtMultimedia"))
64
+ choices.append(("pyaudio", "PyAudio"))
65
+ choices.append(("pygame", "PyGame"))
66
+ return choices
67
+
68
+ def get_output_backends(self) -> List[Tuple[str, str]]:
69
+ """
70
+ Get output backends
71
+
72
+ :return backends list: [(id, name)]
73
+ """
74
+ choices = []
75
+ choices.append(("native", "Native / QtMultimedia"))
76
+ choices.append(("pyaudio", "PyAudio"))
77
+ # choices.append(("pygame", "PyGame"))
78
+ return choices
79
+
80
+ def get_default_input_device(self) -> Tuple[int, str]:
81
+ """
82
+ Get default input device
83
+
84
+ :return: (id, name)
85
+ """
86
+ return self.capture.get_default_input_device()
87
+
88
+ def get_default_output_device(self) -> Tuple[int, str]:
89
+ """
90
+ Get default output device
91
+
92
+ :return: (id, name)
93
+ """
94
+ return self.output.get_default_output_device()
78
95
 
79
96
  def is_registered(self, id: str, type: str = "output") -> bool:
80
97
  """
File without changes