pygpt-net 2.6.26__py3-none-any.whl → 2.6.27__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 (34) hide show
  1. pygpt_net/CHANGELOG.txt +4 -0
  2. pygpt_net/__init__.py +1 -1
  3. pygpt_net/controller/access/voice.py +3 -5
  4. pygpt_net/controller/audio/audio.py +9 -6
  5. pygpt_net/controller/audio/ui.py +263 -0
  6. pygpt_net/controller/chat/common.py +17 -1
  7. pygpt_net/controller/theme/theme.py +4 -1
  8. pygpt_net/core/audio/backend/native.py +113 -79
  9. pygpt_net/core/audio/backend/pyaudio.py +16 -19
  10. pygpt_net/core/audio/backend/pygame.py +12 -15
  11. pygpt_net/core/audio/capture.py +10 -9
  12. pygpt_net/core/audio/context.py +3 -6
  13. pygpt_net/data/config/config.json +6 -4
  14. pygpt_net/data/config/models.json +2 -2
  15. pygpt_net/data/config/settings.json +24 -10
  16. pygpt_net/data/locale/locale.de.ini +2 -0
  17. pygpt_net/data/locale/locale.en.ini +2 -0
  18. pygpt_net/data/locale/locale.es.ini +2 -0
  19. pygpt_net/data/locale/locale.fr.ini +2 -0
  20. pygpt_net/data/locale/locale.it.ini +2 -0
  21. pygpt_net/data/locale/locale.pl.ini +3 -1
  22. pygpt_net/data/locale/locale.uk.ini +2 -0
  23. pygpt_net/data/locale/locale.zh.ini +2 -0
  24. pygpt_net/plugin/audio_input/simple.py +5 -10
  25. pygpt_net/plugin/audio_output/plugin.py +4 -17
  26. pygpt_net/ui/layout/chat/input.py +5 -2
  27. pygpt_net/ui/main.py +1 -2
  28. pygpt_net/ui/widget/audio/bar.py +5 -1
  29. pygpt_net/ui/widget/textarea/input.py +475 -50
  30. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.27.dist-info}/METADATA +40 -35
  31. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.27.dist-info}/RECORD +34 -33
  32. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.27.dist-info}/LICENSE +0 -0
  33. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.27.dist-info}/WHEEL +0 -0
  34. {pygpt_net-2.6.26.dist-info → pygpt_net-2.6.27.dist-info}/entry_points.txt +0 -0
@@ -6,14 +6,19 @@
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.07 22:00:00 #
9
+ # Updated Date: 2025.08.27 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
+ from typing import List, Tuple
13
+
14
+ from bs4 import UnicodeDammit
12
15
  import os
13
16
  import time
14
- from typing import List, Tuple
17
+ import numpy as np
18
+ import wave
15
19
 
16
- from PySide6.QtCore import QTimer, QObject
20
+ from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput, QMediaDevices, QAudioFormat, QAudioSource
21
+ from PySide6.QtCore import QTimer, QObject, QUrl
17
22
  from pydub import AudioSegment
18
23
 
19
24
 
@@ -40,7 +45,6 @@ class NativeBackend(QObject):
40
45
  self.start_time = 0
41
46
  self.devices = []
42
47
  self.selected_device = None
43
- self.bar = None
44
48
  self.loop = False
45
49
  self.stop_callback = None
46
50
  self.player = None
@@ -49,14 +53,24 @@ class NativeBackend(QObject):
49
53
  self.audio_output = None
50
54
  self.envelope = []
51
55
  self.chunk_ms = 10
56
+ self.mode = "input" # input|control
52
57
 
53
58
  # Get configuration values (use defaults if unavailable)
54
59
  if self.window is not None and hasattr(self.window, "core"):
55
60
  self.channels = int(self.window.core.config.get('audio.input.channels', 1))
56
61
  self.rate = int(self.window.core.config.get('audio.input.rate', 44100))
62
+ self.latency_ms = int(self.window.core.config.get('audio.input.latency_ms', 30))
57
63
  else:
58
64
  self.channels = 1
59
65
  self.rate = 44100
66
+ self.latency_ms = 30
67
+
68
+ # internal recording state flag to suppress volume updates after stop
69
+ # This prevents late/queued readyRead events from updating the UI when recording is no longer active.
70
+ self._is_recording = False
71
+
72
+ self._dtype = None
73
+ self._norm = None
60
74
 
61
75
  def init(self):
62
76
  """
@@ -66,6 +80,14 @@ class NativeBackend(QObject):
66
80
  self.check_audio_devices()
67
81
  self.initialized = True
68
82
 
83
+ def set_mode(self, mode: str):
84
+ """
85
+ Set input mode (input|control)
86
+
87
+ :param mode: mode name
88
+ """
89
+ self.mode = mode
90
+
69
91
  def set_repeat_callback(self, callback):
70
92
  """
71
93
  Set callback to be called on loop recording
@@ -93,14 +115,6 @@ class NativeBackend(QObject):
93
115
  """
94
116
  self.path = path
95
117
 
96
- def set_bar(self, bar):
97
- """
98
- Set audio level bar
99
-
100
- :param bar: audio level bar
101
- """
102
- self.bar = bar
103
-
104
118
  def start(self):
105
119
  """
106
120
  Start audio input recording
@@ -108,6 +122,7 @@ class NativeBackend(QObject):
108
122
  :return: True if started
109
123
  """
110
124
  self.init()
125
+
111
126
  # Clear previous frames
112
127
  self.frames = []
113
128
 
@@ -127,6 +142,11 @@ class NativeBackend(QObject):
127
142
  # Set up audio input and start recording
128
143
  self.setup_audio_input()
129
144
  self.start_time = time.time()
145
+
146
+ # mark recording as active only after setup succeeded
147
+ # This ensures process_audio_input() will start updating the UI.
148
+ if self.audio_source is not None and self.audio_io_device is not None:
149
+ self._is_recording = True
130
150
  return True
131
151
 
132
152
  def stop(self) -> bool:
@@ -136,9 +156,20 @@ class NativeBackend(QObject):
136
156
  :return: True if stopped
137
157
  """
138
158
  result = False
159
+
160
+ # immediately mark that we are no longer recording
161
+ # This blocks any late UI updates coming from queued signals.
162
+ self._is_recording = False
163
+
139
164
  if self.audio_source is not None:
140
165
  # Disconnect the readyRead signal
141
- self.audio_io_device.readyRead.disconnect(self.process_audio_input)
166
+ try:
167
+ if self.audio_io_device is not None:
168
+ self.audio_io_device.readyRead.disconnect(self.process_audio_input)
169
+ except (TypeError, RuntimeError):
170
+ # ignore if already disconnected or device gone ---
171
+ pass
172
+
142
173
  self.audio_source.stop()
143
174
  self.audio_source = None
144
175
  self.audio_io_device = None
@@ -149,6 +180,10 @@ class NativeBackend(QObject):
149
180
  result = True
150
181
  else:
151
182
  print("No audio data recorded")
183
+
184
+ # reset input volume on stop to visually indicate end of recording ---
185
+ self.reset_audio_level()
186
+
152
187
  return result
153
188
 
154
189
  def has_source(self) -> bool:
@@ -187,8 +222,7 @@ class NativeBackend(QObject):
187
222
 
188
223
  def reset_audio_level(self):
189
224
  """Reset the audio level bar"""
190
- if self.bar is not None:
191
- self.bar.setLevel(0)
225
+ self.window.controller.audio.ui.on_input_volume_change(0, self.mode)
192
226
 
193
227
  def check_audio_input(self) -> bool:
194
228
  """
@@ -197,24 +231,27 @@ class NativeBackend(QObject):
197
231
  :return: True if working
198
232
  """
199
233
  self.init()
200
- from PySide6.QtMultimedia import QMediaDevices, QAudioFormat, QAudioSource
201
-
202
234
  devices = QMediaDevices.audioInputs()
203
235
  if not devices:
204
236
  print("No audio input devices found.")
205
237
  return False
206
238
 
207
239
  device = self.selected_device if self.selected_device else devices[0]
208
- audio_format = QAudioFormat()
209
- audio_format.setSampleRate(self.rate)
210
- audio_format.setChannelCount(self.channels)
211
- audio_format.setSampleFormat(QAudioFormat.SampleFormat.Int16)
212
-
213
- if not device.isFormatSupported(audio_format):
214
- print("Requested format not supported, using nearest format.")
215
- audio_format = device.preferredFormat()
240
+
241
+ audio_format = device.preferredFormat()
242
+ desired = QAudioFormat()
243
+ desired.setSampleRate(self.rate)
244
+ desired.setChannelCount(self.channels)
245
+ desired.setSampleFormat(QAudioFormat.SampleFormat.Int16)
246
+ if device.isFormatSupported(desired):
247
+ audio_format = desired
248
+
216
249
  try:
217
250
  audio_source = QAudioSource(device, audio_format)
251
+ bs = int(audio_format.sampleRate() * audio_format.channelCount() * audio_format.bytesPerSample() * (float(self.latency_ms) / 1000.0))
252
+ if bs < 4096:
253
+ bs = 4096
254
+ audio_source.setBufferSize(bs)
218
255
  io_device = audio_source.start()
219
256
  if io_device is None:
220
257
  print("Unable to access audio input device")
@@ -227,7 +264,6 @@ class NativeBackend(QObject):
227
264
 
228
265
  def check_audio_devices(self):
229
266
  """Check audio input devices"""
230
- from PySide6.QtMultimedia import QMediaDevices
231
267
  self.devices = QMediaDevices.audioInputs()
232
268
  if not self.devices:
233
269
  # no devices found
@@ -274,39 +310,30 @@ class NativeBackend(QObject):
274
310
  print("No audio input device selected")
275
311
  return
276
312
 
277
- from PySide6.QtMultimedia import QAudioFormat, QAudioSource
278
-
279
- # Define audio format
280
- audio_format = QAudioFormat()
281
- audio_format.setSampleRate(int(self.window.core.config.get('audio.input.rate', 44100)))
282
- audio_format.setChannelCount(int(self.window.core.config.get('audio.input.channels', 1)))
283
- audio_format.setSampleFormat(QAudioFormat.SampleFormat.Int16)
284
-
285
- # Select default audio input device
286
313
  audio_input_device = self.selected_device
287
314
 
288
- # Check if the format is supported
289
- if not audio_input_device.isFormatSupported(audio_format):
290
- print("Requested format not supported, using nearest format.")
291
- audio_format = audio_input_device.preferredFormat()
292
- if audio_format.channelCount() > 2:
293
- audio_format.setChannelCount(2)
294
- if audio_format.sampleRate() > 44100:
295
- audio_format.setSampleRate(44100)
296
- if audio_format.bytesPerSample() > 2:
297
- audio_format.setSampleFormat(QAudioFormat.SampleFormat.Int16)
298
-
299
- # Store the actual format being used
315
+ audio_format = audio_input_device.preferredFormat()
316
+ desired = QAudioFormat()
317
+ desired.setSampleRate(int(self.window.core.config.get('audio.input.rate', 44100)))
318
+ desired.setChannelCount(int(self.window.core.config.get('audio.input.channels', 1)))
319
+ desired.setSampleFormat(QAudioFormat.SampleFormat.Int16)
320
+ if audio_input_device.isFormatSupported(desired):
321
+ audio_format = desired
322
+
300
323
  self.actual_audio_format = audio_format
324
+ self._dtype = self.get_dtype_from_sample_format(self.actual_audio_format.sampleFormat())
325
+ self._norm = self.get_normalization_factor(self.actual_audio_format.sampleFormat())
301
326
 
302
- # Create QAudioSource object with the device and format
303
327
  try:
304
328
  self.audio_source = QAudioSource(audio_input_device, audio_format)
329
+ bs = int(audio_format.sampleRate() * audio_format.channelCount() * audio_format.bytesPerSample() * (float(self.latency_ms) / 1000.0))
330
+ if bs < 4096:
331
+ bs = 4096
332
+ self.audio_source.setBufferSize(bs)
305
333
  except Exception as e:
306
334
  self.disconnected = True
307
335
  print(f"Failed to create audio source: {e}")
308
336
 
309
- # Start audio input and obtain the QIODevice
310
337
  try:
311
338
  self.audio_io_device = self.audio_source.start()
312
339
  if self.audio_io_device is None:
@@ -318,13 +345,13 @@ class NativeBackend(QObject):
318
345
  self.audio_io_device = None
319
346
  return
320
347
 
321
- # Connect the readyRead signal to process incoming data
322
348
  self.audio_io_device.readyRead.connect(self.process_audio_input)
323
349
 
324
350
  def process_audio_input(self):
325
351
  """Process incoming audio data"""
326
- import numpy as np
327
- from PySide6.QtMultimedia import QAudioFormat
352
+ # guard against late calls after stop or missing device ---
353
+ if not self._is_recording or self.audio_io_device is None:
354
+ return
328
355
 
329
356
  # add seconds to stop timer
330
357
  data = self.audio_io_device.readAll()
@@ -339,8 +366,8 @@ class NativeBackend(QObject):
339
366
 
340
367
  # Determine the correct dtype and normalization factor
341
368
  sample_format = self.actual_audio_format.sampleFormat()
342
- dtype = self.get_dtype_from_sample_format(sample_format)
343
- normalization_factor = self.get_normalization_factor(sample_format)
369
+ dtype = self._dtype if self._dtype is not None else self.get_dtype_from_sample_format(sample_format)
370
+ normalization_factor = self._norm if self._norm is not None else self.get_normalization_factor(sample_format)
344
371
 
345
372
  # Convert bytes to NumPy array of the appropriate type
346
373
  samples = np.frombuffer(data_bytes, dtype=dtype)
@@ -383,8 +410,10 @@ class NativeBackend(QObject):
383
410
 
384
411
  :param level: audio level
385
412
  """
386
- if self.bar is not None:
387
- self.bar.setLevel(level)
413
+ # do not update UI if recording already stopped
414
+ if not self._is_recording:
415
+ return
416
+ self.window.controller.audio.ui.on_input_volume_change(int(level), self.mode)
388
417
 
389
418
  def save_audio_file(self, filename: str):
390
419
  """
@@ -392,20 +421,40 @@ class NativeBackend(QObject):
392
421
 
393
422
  :param filename: output file name
394
423
  """
395
- import wave
396
424
  # Define the parameters for the WAV file
397
425
  channels = self.actual_audio_format.channelCount()
398
- sample_size = self.actual_audio_format.bytesPerSample()
399
426
  frame_rate = self.actual_audio_format.sampleRate()
427
+ sample_format = self.actual_audio_format.sampleFormat()
428
+
429
+ raw = b''.join(self.frames)
430
+
431
+ if sample_format == QAudioFormat.SampleFormat.Int16:
432
+ out_bytes = raw
433
+ sample_size = 2
434
+ elif sample_format == QAudioFormat.SampleFormat.UInt8:
435
+ arr = np.frombuffer(raw, dtype=np.uint8).astype(np.int16)
436
+ arr = (arr - 128) << 8
437
+ out_bytes = arr.tobytes()
438
+ sample_size = 2
439
+ elif sample_format == QAudioFormat.SampleFormat.Int32:
440
+ arr = np.frombuffer(raw, dtype=np.int32)
441
+ arr = (arr >> 16).astype(np.int16)
442
+ out_bytes = arr.tobytes()
443
+ sample_size = 2
444
+ elif sample_format == QAudioFormat.SampleFormat.Float:
445
+ arr = np.frombuffer(raw, dtype=np.float32)
446
+ arr = np.clip(arr, -1.0, 1.0)
447
+ arr = (arr * 32767.0).astype(np.int16)
448
+ out_bytes = arr.tobytes()
449
+ sample_size = 2
450
+ else:
451
+ raise ValueError("Unsupported sample format")
400
452
 
401
- # Open the WAV file
402
453
  wf = wave.open(filename, 'wb')
403
454
  wf.setnchannels(channels)
404
455
  wf.setsampwidth(sample_size)
405
456
  wf.setframerate(frame_rate)
406
-
407
- # Write frames to the file
408
- wf.writeframes(b''.join(self.frames))
457
+ wf.writeframes(out_bytes)
409
458
  wf.close()
410
459
 
411
460
  def get_dtype_from_sample_format(self, sample_format):
@@ -414,9 +463,6 @@ class NativeBackend(QObject):
414
463
 
415
464
  :param sample_format: QAudioFormat.SampleFormat
416
465
  """
417
- import numpy as np
418
- from PySide6.QtMultimedia import QAudioFormat
419
-
420
466
  if sample_format == QAudioFormat.SampleFormat.UInt8:
421
467
  return np.uint8
422
468
  elif sample_format == QAudioFormat.SampleFormat.Int16:
@@ -434,8 +480,6 @@ class NativeBackend(QObject):
434
480
 
435
481
  :param sample_format: QAudioFormat.SampleFormat
436
482
  """
437
- from PySide6.QtMultimedia import QAudioFormat
438
-
439
483
  if sample_format == QAudioFormat.SampleFormat.UInt8:
440
484
  return 255.0
441
485
  elif sample_format == QAudioFormat.SampleFormat.Int16:
@@ -463,8 +507,6 @@ class NativeBackend(QObject):
463
507
  :param signals: Signals to emit on playback
464
508
  :return: True if started
465
509
  """
466
- from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput, QMediaDevices
467
- from PySide6.QtCore import QUrl, QTimer
468
510
  if signals is not None:
469
511
  signals.playback.emit(event_name)
470
512
 
@@ -558,8 +600,7 @@ class NativeBackend(QObject):
558
600
  """
559
601
  if self.player is not None:
560
602
  self.player.stop()
561
- if self.playback_timer is not None:
562
- self.playback_timer.stop()
603
+ self.stop_timers()
563
604
  return False
564
605
 
565
606
  def calculate_envelope(
@@ -573,7 +614,6 @@ class NativeBackend(QObject):
573
614
  :param audio_file: Path to the audio file
574
615
  :param chunk_ms: Size of each chunk in milliseconds
575
616
  """
576
- import numpy as np
577
617
  audio = AudioSegment.from_file(audio_file)
578
618
  max_amplitude = 32767
579
619
  envelope = []
@@ -611,8 +651,6 @@ class NativeBackend(QObject):
611
651
 
612
652
  :return devices list: [(id, name)]
613
653
  """
614
- from bs4 import UnicodeDammit
615
- from PySide6.QtMultimedia import QMediaDevices
616
654
  devices = QMediaDevices.audioInputs()
617
655
  devices_list = []
618
656
  for index, device in enumerate(devices):
@@ -626,8 +664,6 @@ class NativeBackend(QObject):
626
664
 
627
665
  :return devices list: [(id, name)]
628
666
  """
629
- from bs4 import UnicodeDammit
630
- from PySide6.QtMultimedia import QMediaDevices
631
667
  devices = QMediaDevices.audioOutputs()
632
668
  devices_list = []
633
669
  for index, device in enumerate(devices):
@@ -641,7 +677,6 @@ class NativeBackend(QObject):
641
677
 
642
678
  :return: (device_id, error_message) tuple
643
679
  """
644
- from PySide6.QtMultimedia import QMediaDevices
645
680
  default_device = QMediaDevices.defaultAudioInput()
646
681
  devices = QMediaDevices.audioInputs()
647
682
  try:
@@ -656,11 +691,10 @@ class NativeBackend(QObject):
656
691
 
657
692
  :return: (device_id, None) if successful, (None, error_message) if failed
658
693
  """
659
- from PySide6.QtMultimedia import QMediaDevices
660
694
  default_device = QMediaDevices.defaultAudioOutput()
661
695
  devices = QMediaDevices.audioOutputs()
662
696
  try:
663
697
  index = devices.index(default_device)
664
698
  except ValueError:
665
699
  index = None
666
- return index, None
700
+ return index, 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.07 03:00:00 #
9
+ # Updated Date: 2025.08.27 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import time
@@ -27,7 +27,6 @@ class PyaudioBackend:
27
27
  self.window = window
28
28
  self.path = None
29
29
  self.frames = []
30
- self.bar = None
31
30
  self.loop = False
32
31
  self.stop_callback = None
33
32
  self.start_time = 0
@@ -36,6 +35,7 @@ class PyaudioBackend:
36
35
  self.pyaudio_instance_output = None
37
36
  self.stream = None
38
37
  self.stream_output = None
38
+ self.mode = "input" # input|control
39
39
 
40
40
  # Get configuration values (use defaults if unavailable)
41
41
  if self.window is not None and hasattr(self.window, "core"):
@@ -60,6 +60,14 @@ class PyaudioBackend:
60
60
  self.check_audio_devices()
61
61
  self.initialized = True
62
62
 
63
+ def set_mode(self, mode: str):
64
+ """
65
+ Set input mode (input|control)
66
+
67
+ :param mode: mode name
68
+ """
69
+ self.mode = mode
70
+
63
71
  def set_repeat_callback(self, callback):
64
72
  """
65
73
  Set callback to be called on loop recording.
@@ -87,14 +95,6 @@ class PyaudioBackend:
87
95
  """
88
96
  self.path = path
89
97
 
90
- def set_bar(self, bar):
91
- """
92
- Set audio level bar.
93
-
94
- :param bar: audio level bar widget to update with audio levels
95
- """
96
- self.bar = bar
97
-
98
98
  def start(self):
99
99
  """
100
100
  Start audio input recording using PyAudio.
@@ -169,8 +169,7 @@ class PyaudioBackend:
169
169
  """
170
170
  Reset the audio level bar.
171
171
  """
172
- if self.bar is not None:
173
- self.bar.setLevel(0)
172
+ self.window.controller.audio.ui.on_input_volume_change(0, self.mode)
174
173
 
175
174
  def check_audio_input(self) -> bool:
176
175
  """
@@ -284,11 +283,10 @@ class PyaudioBackend:
284
283
  level_percent = int(level * 100)
285
284
 
286
285
  # Update the audio level bar if available.
287
- if self.bar is not None:
288
- try:
289
- QTimer.singleShot(0, lambda: self.bar.setLevel(level_percent))
290
- except Exception:
291
- pass
286
+ try:
287
+ QTimer.singleShot(0, lambda: self.window.controller.audio.ui.on_input_volume_change(level_percent, self.mode))
288
+ except Exception:
289
+ pass
292
290
 
293
291
  # Handle loop recording if enabled.
294
292
  if self.loop and self.stop_callback is not None:
@@ -306,8 +304,7 @@ class PyaudioBackend:
306
304
  Update the audio level bar.
307
305
  :param level: audio level (0-100).
308
306
  """
309
- if self.bar is not None:
310
- self.bar.setLevel(level)
307
+ self.window.controller.audio.ui.on_input_volume_change(level, self.mode)
311
308
 
312
309
  def save_audio_file(self, filename: str):
313
310
  """
@@ -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.07 03:00:00 #
9
+ # Updated Date: 2025.08.27 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import time
@@ -26,7 +26,6 @@ class PygameBackend:
26
26
  self.window = window
27
27
  self.path = None
28
28
  self.frames = [] # List to store captured audio chunks (bytes)
29
- self.bar = None # Optional audio level bar widget
30
29
  self.loop = False
31
30
  self.stop_callback = None
32
31
  self.start_time = 0
@@ -54,6 +53,7 @@ class PygameBackend:
54
53
  self.devices = []
55
54
  self.selected_device = None
56
55
  self.initialized = False
56
+ self.mode = "input" # input|control
57
57
 
58
58
  def init(self):
59
59
  """
@@ -75,6 +75,14 @@ class PygameBackend:
75
75
  self.check_audio_devices()
76
76
  self.initialized = True
77
77
 
78
+ def set_mode(self, mode: str):
79
+ """
80
+ Set input mode (input|control)
81
+
82
+ :param mode: mode name
83
+ """
84
+ self.mode = mode
85
+
78
86
  def set_repeat_callback(self, callback):
79
87
  """
80
88
  Set a callback to be called on loop recording.
@@ -102,15 +110,6 @@ class PygameBackend:
102
110
  """
103
111
  self.path = path
104
112
 
105
- def set_bar(self, bar):
106
- """
107
- Set an audio level bar widget.
108
- If not available, this will be silently ignored.
109
-
110
- :param bar: A widget that has a setLevel method to update the audio level.
111
- """
112
- self.bar = bar
113
-
114
113
  def start(self):
115
114
  """
116
115
  Start audio recording using pygame’s SDL2 audio capture.
@@ -187,8 +186,7 @@ class PygameBackend:
187
186
  """
188
187
  Reset the audio level bar (if available).
189
188
  """
190
- if self.bar is not None:
191
- self.bar.setLevel(0)
189
+ self.window.controller.audio.ui.on_input_volume_change(0, self.mode)
192
190
 
193
191
  def check_audio_input(self) -> bool:
194
192
  """
@@ -312,8 +310,7 @@ class PygameBackend:
312
310
  level = min(max(rms, 0.0), 1.0)
313
311
  level_percent = int(level * 100)
314
312
 
315
- if self.bar is not None:
316
- QTimer.singleShot(0, lambda: self.bar.setLevel(level_percent))
313
+ QTimer.singleShot(0, lambda: self.window.controller.audio.ui.on_input_volume_change(level_percent, self.mode))
317
314
 
318
315
  # Handle loop recording if enabled.
319
316
  if self.loop and self.stop_callback is not None:
@@ -6,8 +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.07 03:00:00 #
9
+ # Updated Date: 2025.08.27 07:00:00 #
10
10
  # ================================================== #
11
+
11
12
  from typing import List, Tuple
12
13
 
13
14
  from .backend.native import NativeBackend
@@ -57,6 +58,14 @@ class Capture:
57
58
  """
58
59
  self.get_backend().set_repeat_callback(callback)
59
60
 
61
+ def set_mode(self, mode: str):
62
+ """
63
+ Set input mode (input|control)
64
+
65
+ :param mode: mode name
66
+ """
67
+ self.get_backend().set_mode(mode)
68
+
60
69
  def set_loop(self, loop: bool):
61
70
  """
62
71
  Set loop recording
@@ -73,14 +82,6 @@ class Capture:
73
82
  """
74
83
  self.get_backend().set_path(path)
75
84
 
76
- def set_bar(self, bar):
77
- """
78
- Set audio level bar
79
-
80
- :param bar: audio level bar
81
- """
82
- self.get_backend().set_bar(bar)
83
-
84
85
  def start(self):
85
86
  """
86
87
  Start audio input recording
@@ -6,12 +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: 2024.12.14 08:00:00 #
9
+ # Updated Date: 2025.08.27 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from typing import Dict, Any
13
-
14
- from pygpt_net.item.ctx import CtxItem
12
+ from typing import Dict
15
13
 
16
14
 
17
15
  class AudioContext:
@@ -35,5 +33,4 @@ class AudioContext:
35
33
  "prev_id": self.prev_id
36
34
  }
37
35
  # sort by keys
38
- data = dict(sorted(data.items(), key=lambda item: item[0]))
39
- return data
36
+ return dict(sorted(data.items(), key=lambda item: item[0]))
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.26",
4
- "app.version": "2.6.26",
3
+ "version": "2.6.27",
4
+ "app.version": "2.6.27",
5
5
  "updated_at": "2025-08-26T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
@@ -175,7 +175,9 @@
175
175
  "vision": "",
176
176
  "agent": "",
177
177
  "agent_llama": "",
178
- "expert": ""
178
+ "agent_openai": "",
179
+ "expert": "",
180
+ "computer": ""
179
181
  },
180
182
  "debug": false,
181
183
  "debug.render": false,
@@ -345,7 +347,7 @@
345
347
  "plugins": {},
346
348
  "plugins_enabled": {
347
349
  "agent": false,
348
- "audio_input": false,
350
+ "audio_input": true,
349
351
  "audio_output": false,
350
352
  "cmd_api": false,
351
353
  "cmd_code_interpreter": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.26",
4
- "app.version": "2.6.26",
3
+ "version": "2.6.27",
4
+ "app.version": "2.6.27",
5
5
  "updated_at": "2025-08-26T23:07:35"
6
6
  },
7
7
  "items": {