pygpt-net 2.4.53__py3-none-any.whl → 2.4.54__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.
CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 2.4.54 (2025-01-18)
4
+
5
+ - Audio output switched from PyGame to PyAudio. It may be necessary to manually connect Alsa in Snap version with: "sudo snap connect pygpt:alsa".
6
+ - Added audio output volume progress bar.
7
+
3
8
  ## 2.4.53 (2025-01-17)
4
9
 
5
10
  - Fix: issue #89
README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
4
4
 
5
- Release: **2.4.53** | build: **2025.01.17** | Python: **>=3.10, <3.13**
5
+ Release: **2.4.54** | build: **2025.01.18** | Python: **>=3.10, <3.13**
6
6
 
7
7
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
8
8
  >
@@ -121,6 +121,13 @@ sudo snap connect pygpt:audio-record :audio-record
121
121
  sudo snap connect pygpt:alsa
122
122
  ```
123
123
 
124
+ **Using audio output:** to use audio output in Snap version you must connect the audio with:
125
+
126
+ ```commandline
127
+ sudo snap connect pygpt:audio-playback
128
+ sudo snap connect pygpt:alsa
129
+ ```
130
+
124
131
  **Connecting IPython in Docker in Snap version**:
125
132
 
126
133
  To use IPython in the Snap version, you must connect PyGPT to the Docker daemon:
@@ -3953,6 +3960,11 @@ may consume additional tokens that are not displayed in the main window.
3953
3960
 
3954
3961
  ## Recent changes:
3955
3962
 
3963
+ **2.4.54 (2025-01-18)**
3964
+
3965
+ - Audio output switched from PyGame to PyAudio. It may be necessary to manually connect Alsa in Snap version with: "sudo snap connect pygpt:alsa".
3966
+ - Added audio output volume progress bar.
3967
+
3956
3968
  **2.4.53 (2025-01-17)**
3957
3969
 
3958
3970
  - Fix: issue #89
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,8 @@
1
+ 2.4.54 (2025-01-18)
2
+
3
+ - Audio output switched from PyGame to PyAudio. It may be necessary to manually connect Alsa in Snap version with: "sudo snap connect pygpt:alsa".
4
+ - Added audio output volume progress bar.
5
+
1
6
  2.4.53 (2025-01-17)
2
7
 
3
8
  - Fix: issue #89
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.01.17 13:00:00 #
9
+ # Updated Date: 2025.01.18 03: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.4.53"
17
- __build__ = "2025.01.17"
16
+ __version__ = "2.4.54"
17
+ __build__ = "2025.01.18"
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"
@@ -333,10 +333,10 @@ class Capture:
333
333
  :return: True if working
334
334
  """
335
335
  import pyaudio
336
+ p = pyaudio.PyAudio()
336
337
  try:
337
338
  rate = 44100
338
339
  channels = 1
339
- p = pyaudio.PyAudio()
340
340
  stream = p.open(format=pyaudio.paInt16,
341
341
  channels=channels,
342
342
  rate=rate,
@@ -346,4 +346,5 @@ class Capture:
346
346
  p.terminate()
347
347
  return True
348
348
  except Exception as e:
349
+ p.terminate()
349
350
  return False
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.4.53",
4
- "app.version": "2.4.53",
5
- "updated_at": "2025-01-17T00:00:00"
3
+ "version": "2.4.54",
4
+ "app.version": "2.4.54",
5
+ "updated_at": "2025-01-18T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
8
8
  "access.audio.event.speech.disabled": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.4.53",
4
- "app.version": "2.4.53",
5
- "updated_at": "2025-01-17T00:00:00"
3
+ "version": "2.4.54",
4
+ "app.version": "2.4.54",
5
+ "updated_at": "2025-01-18T00:00:00"
6
6
  },
7
7
  "items": {
8
8
  "claude-3-5-sonnet-20240620": {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.4.53",
4
- "app.version": "2.4.53",
5
- "updated_at": "2025-01-17T00:00:00"
3
+ "version": "2.4.54",
4
+ "app.version": "2.4.54",
5
+ "updated_at": "2025-01-18T00:00:00"
6
6
  },
7
7
  "items": {
8
8
  "chat": {
Binary file
@@ -61,6 +61,19 @@ class Simple:
61
61
 
62
62
  :param force: True to force recording
63
63
  """
64
+ # display snap warning if not displayed yet
65
+ if (not self.plugin.window.core.config.get("audio.input.snap", False)
66
+ or not self.plugin.window.core.config.has("audio.input.snap")):
67
+ if self.plugin.window.core.platforms.is_snap():
68
+ self.plugin.window.ui.dialogs.open(
69
+ 'snap_audio_input',
70
+ width=400,
71
+ height=200
72
+ )
73
+ self.plugin.window.core.config.set("audio.input.snap", True)
74
+ self.plugin.window.core.config.save()
75
+ return
76
+
64
77
  # enable continuous mode if notepad tab is active
65
78
  self.plugin.window.core.audio.capture.stop_callback = self.on_stop
66
79
  continuous_enabled = self.plugin.window.core.config.get('audio.input.continuous', False)
@@ -71,8 +84,7 @@ class Simple:
71
84
 
72
85
  try:
73
86
  # stop audio output if playing
74
- if self.plugin.window.controller.audio.is_playing():
75
- self.plugin.window.controller.audio.stop_output()
87
+ self.plugin.window.controller.audio.stop_output()
76
88
 
77
89
  # set audio volume bar
78
90
  self.plugin.window.core.audio.capture.set_bar(
@@ -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: 2024.11.26 19:00:00 #
9
+ # Updated Date: 2025.01.18 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os.path
@@ -16,6 +16,7 @@ import audioop
16
16
 
17
17
  from PySide6.QtCore import Slot, Signal
18
18
 
19
+ from pygpt_net.core.tabs import Tab
19
20
  from pygpt_net.utils import trans
20
21
  from pygpt_net.plugin.base.worker import BaseWorker, BaseSignals
21
22
 
@@ -76,10 +77,12 @@ class Worker(BaseWorker):
76
77
  self.status(trans('audio.speak.wait'))
77
78
 
78
79
  # if multimodal audio, then only return path to audio file and do not transcribe
79
- if self.plugin.window.controller.chat.audio.enabled():
80
- self.signals.on_realtime.emit(self.path)
81
- self.status('')
82
- return
80
+ tab = self.window.controller.ui.tabs.get_current_tab()
81
+ if tab.type == Tab.TAB_CHAT:
82
+ if self.plugin.window.controller.chat.audio.enabled():
83
+ self.signals.on_realtime.emit(self.path)
84
+ self.status('')
85
+ return
83
86
 
84
87
  # transcribe audio
85
88
  transcript = self.plugin.get_provider().transcribe(self.path)
@@ -6,9 +6,11 @@
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 18:00:00 #
9
+ # Updated Date: 2025.01.18 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
+ from typing import Any
13
+
12
14
  from PySide6.QtCore import Slot
13
15
 
14
16
  from pygpt_net.core.types import MODE_AUDIO
@@ -35,6 +37,7 @@ class Plugin(BasePlugin):
35
37
  self.use_locale = True
36
38
  self.output_file = "output.mp3"
37
39
  self.config = Config(self)
40
+ self.worker = None
38
41
 
39
42
  def init_options(self):
40
43
  """Initialize options"""
@@ -171,10 +174,12 @@ class Plugin(BasePlugin):
171
174
  # check for audio read allowed. Prevents reading audio in commands, results, etc.
172
175
  if name == Event.CTX_AFTER:
173
176
  if not ctx.audio_read_allowed():
174
- return # abort if audio read is not allowed (commands, results, etc.)
175
-
177
+ return # abort if audio read is not allowed (commands, results, etc.)
178
+
176
179
  try:
177
180
  if text is not None and len(text) > 0:
181
+ self.stop_audio()
182
+
178
183
  worker = Worker()
179
184
  worker.from_defaults(self)
180
185
  worker.ctx = ctx
@@ -185,13 +190,16 @@ class Plugin(BasePlugin):
185
190
 
186
191
  # signals
187
192
  worker.signals.playback.connect(self.handle_playback)
193
+ worker.signals.error_playback.connect(self.handle_playback_error)
188
194
  worker.signals.stop.connect(self.handle_stop)
195
+ worker.signals.volume_changed.connect(self.handle_volume)
189
196
 
190
197
  worker.run_async()
198
+ self.worker = worker
191
199
 
192
200
  # only for manual reading
193
201
  if name == Event.AUDIO_READ_TEXT:
194
- self.window.controller.audio.on_begin(worker.text)
202
+ self.window.controller.audio.on_begin(self.worker.text)
195
203
 
196
204
  except Exception as e:
197
205
  self.error(e)
@@ -202,8 +210,10 @@ class Plugin(BasePlugin):
202
210
 
203
211
  :param ctx: CtxItem
204
212
  :param event: Event
205
- """
213
+ """
206
214
  try:
215
+ self.stop_audio()
216
+
207
217
  worker = Worker()
208
218
  worker.from_defaults(self)
209
219
  worker.audio_file = event.data["audio_file"]
@@ -211,9 +221,12 @@ class Plugin(BasePlugin):
211
221
 
212
222
  # signals
213
223
  worker.signals.playback.connect(self.handle_playback)
224
+ worker.signals.error_playback.connect(self.handle_playback_error)
214
225
  worker.signals.stop.connect(self.handle_stop)
226
+ worker.signals.volume_changed.connect(self.handle_volume)
215
227
 
216
228
  worker.run_async()
229
+ self.worker = worker
217
230
 
218
231
  except Exception as e:
219
232
  self.error(e)
@@ -250,22 +263,44 @@ class Plugin(BasePlugin):
250
263
 
251
264
  Stop playing the audio
252
265
  """
253
- if self.playback is not None:
254
- self.playback.stop()
255
- self.playback = None
266
+ if self.worker is not None:
267
+ self.worker.stop()
268
+ self.handle_volume(0.0)
256
269
 
257
- @Slot(object, str)
258
- def handle_playback(self, playback, event: str):
270
+ @Slot(object)
271
+ def handle_playback_error(self, err: Any):
272
+ """
273
+ Send error message to logger and alert dialog
274
+
275
+ :param err: error message
276
+ """
277
+ self.error(err)
278
+ if self.window.core.platforms.is_snap():
279
+ self.window.ui.dialogs.open(
280
+ 'snap_audio_output',
281
+ width=400,
282
+ height=200
283
+ )
284
+
285
+ @Slot(str)
286
+ def handle_playback(self, event: str):
259
287
  """
260
288
  Handle thread playback object
261
289
 
262
- :param playback: playback object
263
290
  :param event: event name
264
291
  """
265
- self.playback = playback
266
292
  self.window.controller.audio.on_play(event)
267
293
 
268
294
  @Slot()
269
295
  def handle_stop(self):
270
296
  """Handle thread playback stop"""
271
297
  self.stop_audio()
298
+
299
+ @Slot(float)
300
+ def handle_volume(self, volume: float):
301
+ """
302
+ Handle thread playback volume
303
+
304
+ :param volume: volume level
305
+ """
306
+ self.window.ui.plugin_addon['audio.output.bar'].setLevel(volume)
@@ -6,18 +6,26 @@
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.11.26 19:00:00 #
9
+ # Updated Date: 2025.01.18 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import time
13
+ import pyaudio
14
+ import wave
15
+ import numpy as np
16
+ import io
17
+ from pydub import AudioSegment
13
18
 
14
19
  from PySide6.QtCore import Slot, Signal
20
+
15
21
  from pygpt_net.plugin.base.worker import BaseWorker, BaseSignals
16
22
 
17
23
 
18
24
  class WorkerSignals(BaseSignals):
19
- playback = Signal(object, str)
25
+ volume_changed = Signal(float) # Emits the volume level
26
+ playback = Signal(str)
20
27
  stop = Signal()
28
+ error_playback = Signal(object)
21
29
 
22
30
 
23
31
  class Worker(BaseWorker):
@@ -32,6 +40,9 @@ class Worker(BaseWorker):
32
40
  self.cache_file = None # path to cache file
33
41
  self.mode = "generate" # generate|playback
34
42
  self.audio_file = None
43
+ self.is_stopped = False
44
+ self.p = None
45
+ self.stream = None
35
46
 
36
47
  @Slot()
37
48
  def run(self):
@@ -44,35 +55,118 @@ class Worker(BaseWorker):
44
55
  self.error(e)
45
56
 
46
57
  def generate(self):
47
- """
48
- Generate and play audio file
49
- """
58
+ """Generate and play audio file"""
50
59
  if self.text is None or self.text == "":
51
60
  time.sleep(0.2) # wait
52
61
  return
53
62
  path = self.plugin.get_provider().speech(self.text)
54
63
  if path:
55
- from pygame import mixer
56
- mixer.init()
57
- playback = mixer.Sound(path)
58
- self.stop_playback() # stop previous playback
59
- playback.play()
60
- self.send(playback) # send playback object to main thread to allow force stop
61
-
64
+ self.play_audio(path)
62
65
  # store in cache if enabled
63
66
  if self.cache_file:
64
67
  self.cache_audio_file(path, self.cache_file)
65
68
 
66
69
  def play(self):
70
+ """Play audio file only"""
71
+ if self.audio_file:
72
+ self.play_audio(self.audio_file)
73
+
74
+ def play_audio(self, audio_file: str):
67
75
  """
68
- Play audio file only
76
+ Play audio file using PyAudio
77
+
78
+ :param audio_file: audio file path
69
79
  """
70
- if self.audio_file:
71
- from pygame import mixer
72
- mixer.init()
73
- playback = mixer.Sound(self.audio_file)
74
- playback.play()
75
- self.send(playback) # send playback object to main thread to allow force stop
80
+ try:
81
+ self.signals.playback.emit(self.event)
82
+ audio = AudioSegment.from_file(audio_file)
83
+ audio = audio.set_frame_rate(44100) # resample to 44.1 kHz
84
+ wav_io = io.BytesIO()
85
+ audio.export(wav_io, format='wav')
86
+ wav_io.seek(0)
87
+ wf = wave.open(wav_io, 'rb')
88
+ self.p = pyaudio.PyAudio()
89
+ self.stream = self.p.open(format=self.p.get_format_from_width(wf.getsampwidth()),
90
+ channels=wf.getnchannels(),
91
+ rate=wf.getframerate(),
92
+ output=True)
93
+
94
+ sample_width = wf.getsampwidth()
95
+ format = self.p.get_format_from_width(sample_width)
96
+
97
+ if format == pyaudio.paInt8:
98
+ dtype = np.int8
99
+ max_value = 2 ** 7 - 1 # 127
100
+ offset = 0
101
+ elif format == pyaudio.paInt16:
102
+ dtype = np.int16
103
+ max_value = 2 ** 15 - 1 # 32767
104
+ offset = 0
105
+ elif format == pyaudio.paInt32:
106
+ dtype = np.int32
107
+ max_value = 2 ** 31 - 1 # 2147483647
108
+ offset = 0
109
+ elif format == pyaudio.paUInt8:
110
+ dtype = np.uint8
111
+ max_value = 2 ** 8 - 1 # 255
112
+ offset = 128 # center unsigned data
113
+ else:
114
+ raise ValueError(f"Unsupported format: {format}")
115
+
116
+ chunk_size = 512
117
+ data = wf.readframes(chunk_size)
118
+
119
+ while data != b'' and not self.is_stopped:
120
+ self.stream.write(data)
121
+
122
+ audio_data = np.frombuffer(data, dtype=dtype)
123
+ if len(audio_data) > 0:
124
+ audio_data = audio_data.astype(np.float32)
125
+ if dtype == np.uint8:
126
+ audio_data -= offset
127
+
128
+ # compute RMS
129
+ rms = np.sqrt(np.mean(audio_data ** 2))
130
+
131
+ if rms > 0:
132
+ # RMS to decibels
133
+ db = 20 * np.log10(rms / max_value)
134
+
135
+ # define minimum and maximum dB levels
136
+ min_db = -60 # adjust as needed
137
+ max_db = 0
138
+
139
+ # clamp the db value to the range [min_db, max_db]
140
+ if db < min_db:
141
+ db = min_db
142
+ elif db > max_db:
143
+ db = max_db
144
+
145
+ # map decibel value to volume percentage
146
+ volume_percentage = ((db - min_db) / (max_db - min_db)) * 100
147
+ else:
148
+ volume_percentage = 0
149
+
150
+ # emit volume signal
151
+ self.signals.volume_changed.emit(volume_percentage)
152
+ else:
153
+ # if empty audio_data
154
+ self.signals.volume_changed.emit(0)
155
+
156
+ data = wf.readframes(chunk_size)
157
+
158
+ # close the stream
159
+ if self.stream is not None:
160
+ if self.stream.is_active():
161
+ self.stream.stop_stream()
162
+ self.stream.close()
163
+ if self.p is not None:
164
+ self.p.terminate()
165
+ wf.close()
166
+ self.signals.volume_changed.emit(0)
167
+ except Exception as e:
168
+ self.signals.volume_changed.emit(0)
169
+ self.signals.error_playback.emit(e)
76
170
 
77
171
  def cache_audio_file(self, src: str, dst: str):
78
172
  """
@@ -102,4 +196,4 @@ class Worker(BaseWorker):
102
196
 
103
197
  def stop(self):
104
198
  """Send stop signal to main thread"""
105
- self.signals.stop.emit()
199
+ self.is_stopped = True
pygpt_net/ui/__init__.py CHANGED
@@ -91,7 +91,8 @@ class UI:
91
91
 
92
92
  # FIRST RUN: initial sizes if not set yet
93
93
  if not self.window.core.config.has("layout.splitters") \
94
- or self.window.core.config.get("layout.splitters") == {}:
94
+ or self.window.core.config.get("layout.splitters") == {}\
95
+ or self.window.core.config.get("license.accepted") == False:
95
96
  self.set_initial_size()
96
97
 
97
98
  # menus
@@ -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: 2024.02.26 22:00:00 #
9
+ # Updated Date: 2025.01.18 05:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from pygpt_net.ui.widget.dialog.snap import SnapDialogCamera, SnapDialogAudioInput
12
+ from pygpt_net.ui.widget.dialog.snap import SnapDialogCamera, SnapDialogAudioInput, SnapDialogAudioOutput
13
13
 
14
14
 
15
15
  class Snap:
@@ -25,3 +25,4 @@ class Snap:
25
25
  """Setup snap dialog"""
26
26
  self.window.ui.dialog['snap_camera'] = SnapDialogCamera(self.window)
27
27
  self.window.ui.dialog['snap_audio_input'] = SnapDialogAudioInput(self.window)
28
+ self.window.ui.dialog['snap_audio_output'] = SnapDialogAudioOutput(self.window)
@@ -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: 2024.12.09 03:00:00 #
9
+ # Updated Date: 2025.01.18 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -18,6 +18,7 @@ from pygpt_net.ui.layout.chat.attachments import Attachments
18
18
  from pygpt_net.ui.layout.chat.attachments_uploaded import AttachmentsUploaded
19
19
  from pygpt_net.ui.layout.chat.attachments_ctx import AttachmentsCtx
20
20
  from pygpt_net.ui.layout.status import Status
21
+ from pygpt_net.ui.widget.audio.bar import OutputBar
21
22
  from pygpt_net.ui.widget.audio.input import AudioInput
22
23
  from pygpt_net.ui.widget.audio.input_button import AudioInputButton
23
24
  from pygpt_net.ui.widget.element.labels import HelpLabel
@@ -176,8 +177,10 @@ class Input:
176
177
 
177
178
  :return: QHBoxLayout
178
179
  """
180
+ self.window.ui.plugin_addon['audio.output.bar'] = OutputBar(self.window)
179
181
  layout = QHBoxLayout()
180
182
  layout.addLayout(self.status.setup())
183
+ layout.addWidget(self.window.ui.plugin_addon['audio.output.bar'], alignment=Qt.AlignCenter)
181
184
  layout.addLayout(self.setup_buttons())
182
185
  layout.setContentsMargins(2, 0, 2, 0)
183
186
 
@@ -0,0 +1,103 @@
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.01.18 03:00:00 #
10
+ # ================================================== #
11
+
12
+ from PySide6.QtCore import Qt
13
+ from PySide6.QtGui import QPainter, QPalette
14
+ from PySide6.QtWidgets import QWidget
15
+
16
+ import pygpt_net.icons_rc
17
+
18
+ class InputBar(QWidget):
19
+ def __init__(self, parent=None):
20
+ super().__init__(parent)
21
+ self._level = 0.0 # level from 0.0 to 100.0
22
+ self.setFixedSize(200, 5) # bar size
23
+
24
+ def setLevel(self, level):
25
+ """
26
+ Set volume level
27
+
28
+ :param level: level
29
+ """
30
+ self._level = level
31
+ self.update()
32
+
33
+ def paintEvent(self, event):
34
+ """
35
+ Paint event
36
+
37
+ :param event: event
38
+ """
39
+ palette = self.palette()
40
+ painter = QPainter(self)
41
+ painter.fillRect(self.rect(), Qt.transparent)
42
+ level_width = (self._level / 100.0) * self.width()
43
+ painter.setBrush(palette.color(QPalette.ButtonText))
44
+ painter.setPen(Qt.NoPen)
45
+ painter.drawRect(0, 0, level_width, self.height())
46
+
47
+ """
48
+ # --- bar from center ---
49
+ def paintEvent(self, event):
50
+ painter = QPainter(self)
51
+ painter.fillRect(self.rect(), Qt.transparent)
52
+ level_width = (self._level / 100.0) * self.width()
53
+ half_level_width = level_width / 2
54
+ center_x = self.width() / 2
55
+ rect_x = center_x - half_level_width
56
+ painter.setBrush(Qt.green)
57
+ painter.setPen(Qt.NoPen)
58
+ painter.drawRect(rect_x, 0, level_width, self.height())
59
+ """
60
+
61
+
62
+ class OutputBar(QWidget):
63
+ def __init__(self, parent=None):
64
+ super().__init__(parent)
65
+ self._level = 0.0 # level from 0.0 to 100.0
66
+ self.setFixedSize(200, 5) # bar size
67
+
68
+ def setLevel(self, level):
69
+ """
70
+ Set volume level
71
+
72
+ :param level: level
73
+ """
74
+ self._level = level
75
+ self.update()
76
+
77
+ def paintEvent(self, event):
78
+ """
79
+ Paint event
80
+
81
+ :param event: event
82
+ """
83
+ palette = self.palette()
84
+ painter = QPainter(self)
85
+ painter.fillRect(self.rect(), Qt.transparent)
86
+ level_width = (self._level / 100.0) * self.width()
87
+ painter.setBrush(palette.color(QPalette.ButtonText))
88
+ painter.setPen(Qt.NoPen)
89
+ painter.drawRect(0, 0, level_width, self.height())
90
+
91
+ """
92
+ # --- bar from center ---
93
+ def paintEvent(self, event):
94
+ painter = QPainter(self)
95
+ painter.fillRect(self.rect(), Qt.transparent)
96
+ level_width = (self._level / 100.0) * self.width()
97
+ half_level_width = level_width / 2
98
+ center_x = self.width() / 2
99
+ rect_x = center_x - half_level_width
100
+ painter.setBrush(Qt.green)
101
+ painter.setPen(Qt.NoPen)
102
+ painter.drawRect(rect_x, 0, level_width, self.height())
103
+ """
@@ -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.01.17 02:00:00 #
9
+ # Updated Date: 2025.01.18 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -16,6 +16,8 @@ from PySide6.QtWidgets import QLabel, QHBoxLayout, QWidget, QPushButton, QVBoxLa
16
16
  from pygpt_net.core.events import Event, AppEvent
17
17
  from pygpt_net.ui.widget.option.toggle_label import ToggleLabel
18
18
  from pygpt_net.utils import trans
19
+ from .bar import InputBar
20
+
19
21
  import pygpt_net.icons_rc
20
22
 
21
23
  class VoiceControlButton(QWidget):
@@ -34,7 +36,7 @@ class VoiceControlButton(QWidget):
34
36
  self.btn_toggle.setCursor(Qt.PointingHandCursor)
35
37
  self.btn_toggle.setMinimumWidth(200)
36
38
 
37
- self.bar = LevelBar(self)
39
+ self.bar = InputBar(self)
38
40
  self.bar.setLevel(0)
39
41
 
40
42
  # status
@@ -89,7 +91,7 @@ class AudioInputButton(QWidget):
89
91
  self.btn_toggle.setCursor(Qt.PointingHandCursor)
90
92
  self.btn_toggle.setMinimumWidth(200)
91
93
 
92
- self.bar = LevelBar(self)
94
+ self.bar = InputBar(self)
93
95
  self.bar.setLevel(0)
94
96
 
95
97
  btn_layout = QVBoxLayout()
@@ -133,46 +135,3 @@ class AudioInputButton(QWidget):
133
135
  """Toggle recording"""
134
136
  event = Event(Event.AUDIO_INPUT_RECORD_TOGGLE)
135
137
  self.window.dispatch(event)
136
-
137
-
138
- class LevelBar(QWidget):
139
- def __init__(self, parent=None):
140
- super().__init__(parent)
141
- self._level = 0.0 # level from 0.0 to 100.0
142
- self.setFixedSize(200, 5) # bar size
143
-
144
- def setLevel(self, level):
145
- """
146
- Set volume level
147
-
148
- :param level: level
149
- """
150
- self._level = level
151
- self.update()
152
-
153
- def paintEvent(self, event):
154
- """
155
- Paint event
156
-
157
- :param event: event
158
- """
159
- painter = QPainter(self)
160
- painter.fillRect(self.rect(), Qt.transparent)
161
- level_width = (self._level / 100.0) * self.width()
162
- painter.setBrush(Qt.green)
163
- painter.setPen(Qt.NoPen)
164
- painter.drawRect(0, 0, level_width, self.height())
165
-
166
- """
167
- # --- bar from center ---
168
- def paintEvent(self, event):
169
- painter = QPainter(self)
170
- painter.fillRect(self.rect(), Qt.transparent)
171
- level_width = (self._level / 100.0) * self.width()
172
- half_level_width = level_width / 2
173
- center_x = self.width() / 2
174
- rect_x = center_x - half_level_width
175
- painter.setBrush(Qt.green)
176
- painter.setPen(Qt.NoPen)
177
- painter.drawRect(rect_x, 0, level_width, self.height())
178
- """
@@ -24,7 +24,7 @@ class SnapDialogCamera(QDialog):
24
24
  super(SnapDialogCamera, self).__init__(window)
25
25
  self.window = window
26
26
  self.setParent(window)
27
- self.setWindowTitle("Snap detected")
27
+ self.setWindowTitle("Snap version detected")
28
28
  self.cmd = CmdLabel(self.window, "sudo snap connect pygpt:camera")
29
29
 
30
30
  self.btn = QPushButton("OK")
@@ -34,8 +34,8 @@ class SnapDialogCamera(QDialog):
34
34
  # layout
35
35
  self.layout = QVBoxLayout()
36
36
  self.message = QLabel(
37
- "Camera is not connected! It must be connected in Snap environment.\n"
38
- "Run the following command to enable the camera:")
37
+ "Camera not connected? It must be connected in the Snap environment.\n"
38
+ "Run the following command to enable the camera and restart the application:")
39
39
  self.message.setStyleSheet("margin: 10px 0px 10px 0px;")
40
40
  self.layout.addWidget(self.message)
41
41
  self.layout.addWidget(self.cmd)
@@ -54,7 +54,7 @@ class SnapDialogAudioInput(QDialog):
54
54
  super(SnapDialogAudioInput, self).__init__(window)
55
55
  self.window = window
56
56
  self.setParent(window)
57
- self.setWindowTitle("Snap is detected")
57
+ self.setWindowTitle("Snap version is detected")
58
58
  self.cmd = CmdLabel(self.window, "sudo snap connect pygpt:alsa && sudo snap connect pygpt:audio-record :audio-record")
59
59
 
60
60
  self.btn = QPushButton("OK")
@@ -64,8 +64,39 @@ class SnapDialogAudioInput(QDialog):
64
64
  # layout
65
65
  self.layout = QVBoxLayout()
66
66
  self.message = QLabel(
67
- "Microphone is not connected! It must be connected in Snap environment.\n"
68
- "Run the following command to enable the microphone:")
67
+ "Tip: Microphone must be manually connected in the Snap environment.\n"
68
+ "If it is connected, click on the OK button, and this warning will not be displayed again.\n"
69
+ "If it is NOT connected yet, run the following command and restart the application:")
70
+ self.message.setStyleSheet("margin: 10px 0px 10px 0px;")
71
+ self.layout.addWidget(self.message)
72
+ self.layout.addWidget(self.cmd)
73
+ self.layout.addWidget(self.btn)
74
+ self.layout.addStretch()
75
+ self.setLayout(self.layout)
76
+
77
+
78
+ class SnapDialogAudioOutput(QDialog):
79
+ def __init__(self, window=None):
80
+ """
81
+ Snap dialog for audio output
82
+
83
+ :param window: main window
84
+ """
85
+ super(SnapDialogAudioOutput, self).__init__(window)
86
+ self.window = window
87
+ self.setParent(window)
88
+ self.setWindowTitle("Snap version is detected")
89
+ self.cmd = CmdLabel(self.window, "sudo snap connect pygpt:alsa && sudo snap connect pygpt:audio-playback")
90
+
91
+ self.btn = QPushButton("OK")
92
+ self.btn.clicked.connect(self.accept)
93
+ self.btn.setStyleSheet("margin: 10px 0px 0px 0px;")
94
+
95
+ # layout
96
+ self.layout = QVBoxLayout()
97
+ self.message = QLabel(
98
+ "Audio Device not connected? It must be connected in the Snap environment.\n"
99
+ "Run the following command to enable the audio output and restart the application:")
69
100
  self.message.setStyleSheet("margin: 10px 0px 10px 0px;")
70
101
  self.layout.addWidget(self.message)
71
102
  self.layout.addWidget(self.cmd)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pygpt-net
3
- Version: 2.4.53
3
+ Version: 2.4.54
4
4
  Summary: Desktop AI Assistant powered by models: OpenAI o1, GPT-4o, GPT-4, GPT-4 Vision, GPT-3.5, DALL-E 3, Llama 3, Mistral, Gemini, Claude, Bielik, and other models supported by Langchain, Llama Index, and Ollama. Features include chatbot, text completion, image generation, vision analysis, speech-to-text, internet access, file handling, command execution and more.
5
5
  Home-page: https://pygpt.net
6
6
  License: MIT
@@ -93,7 +93,7 @@ Description-Content-Type: text/markdown
93
93
 
94
94
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
95
95
 
96
- Release: **2.4.53** | build: **2025.01.17** | Python: **>=3.10, <3.13**
96
+ Release: **2.4.54** | build: **2025.01.18** | Python: **>=3.10, <3.13**
97
97
 
98
98
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
99
99
  >
@@ -212,6 +212,13 @@ sudo snap connect pygpt:audio-record :audio-record
212
212
  sudo snap connect pygpt:alsa
213
213
  ```
214
214
 
215
+ **Using audio output:** to use audio output in Snap version you must connect the audio with:
216
+
217
+ ```commandline
218
+ sudo snap connect pygpt:audio-playback
219
+ sudo snap connect pygpt:alsa
220
+ ```
221
+
215
222
  **Connecting IPython in Docker in Snap version**:
216
223
 
217
224
  To use IPython in the Snap version, you must connect PyGPT to the Docker daemon:
@@ -4044,6 +4051,11 @@ may consume additional tokens that are not displayed in the main window.
4044
4051
 
4045
4052
  ## Recent changes:
4046
4053
 
4054
+ **2.4.54 (2025-01-18)**
4055
+
4056
+ - Audio output switched from PyGame to PyAudio. It may be necessary to manually connect Alsa in Snap version with: "sudo snap connect pygpt:alsa".
4057
+ - Added audio output volume progress bar.
4058
+
4047
4059
  **2.4.53 (2025-01-17)**
4048
4060
 
4049
4061
  - Fix: issue #89
@@ -1,9 +1,9 @@
1
- CHANGELOG.md,sha256=KWOYfKpN8niMsPOlBxzzWa6HwCJ2lqi8eCc6DfRVFbU,81496
2
- README.md,sha256=479HJZLOrx3eEV2MPBCzOGTqajkvyk0WtNheinq5Mx8,162317
1
+ CHANGELOG.md,sha256=9kJiaQVA08r9redE_iYTETXGixl9cryuw61GQveLdqQ,81710
2
+ README.md,sha256=4YCoQAqxVyAhDdD9opXn0-M7zqUjDlPHSGr1c0QdbxQ,162714
3
3
  icon.png,sha256=CzcINJaU23a9hNjsDlDNbyuiEvKZ4Wg6DQVYF6SpuRg,13970
4
- pygpt_net/CHANGELOG.txt,sha256=KK8gjgSamq4dp3xuzBtfh-9bpbRNfQIe7eLAe0mOjCo,80001
4
+ pygpt_net/CHANGELOG.txt,sha256=xWt9AnWkqD1C--Zh8yv7kq9_LtMMaFf03IQ5VHD8Kng,80212
5
5
  pygpt_net/LICENSE,sha256=dz9sfFgYahvu2NZbx4C1xCsVn9GVer2wXcMkFRBvqzY,1146
6
- pygpt_net/__init__.py,sha256=ITFDmLKbN0XVHH3QEtEwgnwt_j-yR7LAfObu28q1auk,1307
6
+ pygpt_net/__init__.py,sha256=J52ofbEM6kTMBSYGxEXT_LggHYzeNSGBnF_0DNlsn0k,1307
7
7
  pygpt_net/app.py,sha256=i02M96uLngAs_XZCS1Mi84vb3Okx8ZZewbTdhCqFolM,16029
8
8
  pygpt_net/config.py,sha256=Qc1FOBtTf3O6A6-6KoqUGtoJ0u8hXQeowvCVbZFwtik,16405
9
9
  pygpt_net/container.py,sha256=BemiVZPpPNIzfB-ZvnZeeBPFu-AcX2c30OqYFylEjJc,4023
@@ -125,7 +125,7 @@ pygpt_net/core/attachments/__init__.py,sha256=bUqvfPqlpdXiGf3GvS1kTE45A0Q1Eo3kpU
125
125
  pygpt_net/core/attachments/context.py,sha256=tQM3z_gLI99Ox47XZtVcUnOdfaPYKQwXhm1EJaNikvE,25057
126
126
  pygpt_net/core/attachments/worker.py,sha256=_aUCyi5-Mbz0IGfgY6QKBZ6MFz8aKRDfKasbBVXg7kU,1341
127
127
  pygpt_net/core/audio/__init__.py,sha256=uszH6pqMToDzL0WpPeUvVlyJ8RN4gFmQbsL4GFYMIdc,4521
128
- pygpt_net/core/audio/capture.py,sha256=p4ge43IhQ8Um96QFv0tcd4G3h5Cku0P47Mut09YBFaA,11201
128
+ pygpt_net/core/audio/capture.py,sha256=cR3PsnbxJ8yPE5oirHFAieAIaDGXynUxA4aitocXkgY,11223
129
129
  pygpt_net/core/audio/context.py,sha256=2XpXWhDC09iUvc0FRMq9BF2_rnQ60ZG4Js6LbO5MohY,1115
130
130
  pygpt_net/core/audio/whisper.py,sha256=WZ_fNQ06s1NBxyoYB-lTFqDO6ARcnq9MZFekRaTNxTo,993
131
131
  pygpt_net/core/bridge/__init__.py,sha256=ezS02_2wUrnV6eTF33wfob8rVWM5snfY92-PF_i15uQ,9568
@@ -247,9 +247,9 @@ pygpt_net/css_rc.py,sha256=i13kX7irhbYCWZ5yJbcMmnkFp_UfS4PYnvRFSPF7XXo,11349
247
247
  pygpt_net/data/audio/click_off.mp3,sha256=aNiRDP1pt-Jy7ija4YKCNFBwvGWbzU460F4pZWZDS90,65201
248
248
  pygpt_net/data/audio/click_on.mp3,sha256=qfdsSnthAEHVXzeyN4LlC0OvXuyW8p7stb7VXtlvZ1k,65201
249
249
  pygpt_net/data/audio/ok.mp3,sha256=LTiV32pEBkpUGBkKkcOdOFB7Eyt_QoP2Nv6c5AaXftk,32256
250
- pygpt_net/data/config/config.json,sha256=-1ip6_f_Jkc-mAeGvLuLdHsmlw0yeDEvs84Qo0FEuvk,19805
251
- pygpt_net/data/config/models.json,sha256=c-ko0Ji2-7YGCTZrwsDbZJxPBPHxaoV7O1iRz-EAOzw,61940
252
- pygpt_net/data/config/modes.json,sha256=TLXSysG-1_16pbW6Nup9nAWlNw00lamVuUsukkoet5g,1923
250
+ pygpt_net/data/config/config.json,sha256=BLBW_HrLqQZJ73e0ZuIBs2dZZwyoMM7-kvHlfKUojEU,19805
251
+ pygpt_net/data/config/models.json,sha256=BFVoxYzgJAnhvoPh7BM43eS8HSyUsilD1gJNFuIq5lM,61940
252
+ pygpt_net/data/config/modes.json,sha256=kcA7fM9AeHGKJ3IiCNaNuBV8UQLnIto3eG_2CoBblgU,1923
253
253
  pygpt_net/data/config/presets/agent_openai.json,sha256=vMTR-soRBiEZrpJJHuFLWyx8a3Ez_BqtqjyXgxCAM_Q,733
254
254
  pygpt_net/data/config/presets/agent_openai_assistant.json,sha256=awJw9lNTGpKML6SJUShVn7lv8AXh0oic7wBeyoN7AYs,798
255
255
  pygpt_net/data/config/presets/agent_planner.json,sha256=a6Rv58Bnm2STNWB0Rw_dGhnsz6Lb3J8_GwsUVZaTIXc,742
@@ -1657,7 +1657,7 @@ pygpt_net/data/locale/plugin.voice_control.zh.ini,sha256=SZNVNOtJedH0IaH44YB4ekv
1657
1657
  pygpt_net/data/logo.png,sha256=asjkGb9cP7vjVx9Hdne2bI2GcSlcQ8r_3LBp_znVZj4,19418
1658
1658
  pygpt_net/data/prompts.csv,sha256=dmSg9d3TFPJKvcU7_2hBjA9q1qxjEX-lIPjx1j9rxsM,83051
1659
1659
  pygpt_net/data/win32/README.rtf,sha256=lfvT7Vselk49tRr3hj7Txi6zQP4OTyDU7FmPm8h0qhM,7650
1660
- pygpt_net/data/win32/USER-LICENSE.rtf,sha256=DZXcQq2GSHXjqw2cPzIdatY4Yr-Z6g6iNaDz98i5Fbc,2043
1660
+ pygpt_net/data/win32/USER-LICENSE.rtf,sha256=WSDcVtNF_7aqTx4FGGEMkA4KrFVQ15gjGlRhjynse-c,2043
1661
1661
  pygpt_net/data/win32/banner.bmp,sha256=eWMV8GTSSWXBDn9VfNkMsEVkuVr_Nk1YqQG_50-_Dt8,85894
1662
1662
  pygpt_net/data/win32/banner_welcome.bmp,sha256=jKUlzecxdq8uWUOhBsAer4GGASO3tAStIfntcUGOsIA,461814
1663
1663
  pygpt_net/data/win32/pygpt.aip,sha256=JeMToDAs7EHPlwvZDM66T8Sxy5h_lCC7s-2FihcVtcI,87160
@@ -1701,11 +1701,11 @@ pygpt_net/plugin/agent/__init__.py,sha256=GPbnpS9djcfzuPRxM2mu0hpUPRa51KtUR27Al6
1701
1701
  pygpt_net/plugin/agent/config.py,sha256=V4M0boutzxFVWTQxM8UA7HgKUR4v_Y-5dX_XfeLzzL8,9792
1702
1702
  pygpt_net/plugin/audio_input/__init__.py,sha256=vGwDtsPkwCEwiz2ePnzW48Tuhr0VHEc7kQua11VE7tI,15714
1703
1703
  pygpt_net/plugin/audio_input/config.py,sha256=x57IVxBapJp9rwos327T6U0jTFSPeRJ6BorqfYxJ4u0,9197
1704
- pygpt_net/plugin/audio_input/simple.py,sha256=5RK7R8teQjpLYBZov1cqIevfASBQsBgsMtp_7KXST2E,6219
1705
- pygpt_net/plugin/audio_input/worker.py,sha256=kG7r08ot1h0Jyw_UQ_sFN1rOKOkOC-ByLbCp-oZZBr4,11828
1706
- pygpt_net/plugin/audio_output/__init__.py,sha256=wR_gBQgJ_arxHPSvjtT7naNwkNrCJc6KFR7ZUAqYcvc,7896
1704
+ pygpt_net/plugin/audio_input/simple.py,sha256=z9YCLGca8dV_i6pOBIAWWQRvp3euL_VDbCNbQVdhsiw,6747
1705
+ pygpt_net/plugin/audio_input/worker.py,sha256=O-boo_flpqS72GLHlD4YgLvmRe4asgu1HTY5nBXVGrs,11996
1706
+ pygpt_net/plugin/audio_output/__init__.py,sha256=OnuHRhtzoOkMA5i_TOVqojP_D1j-_pj6-OYYnBoWxfM,8915
1707
1707
  pygpt_net/plugin/audio_output/config.py,sha256=IA2K-9fQMZSwYGyi30Uh5qAlYwuqwaHo3dtDJ13vQdo,1208
1708
- pygpt_net/plugin/audio_output/worker.py,sha256=qi_pI9S2mXJyvN819vjI0oubewk_Fvi2AkdE2OaOVUE,3110
1708
+ pygpt_net/plugin/audio_output/worker.py,sha256=Rm13dQEp3h9zzKQEfUBw1Gz-OxGdKsid3iJqHJBNZFw,6546
1709
1709
  pygpt_net/plugin/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1710
1710
  pygpt_net/plugin/base/config.py,sha256=q5WAcF-h3KZH4bJFYANasM7UmV1v1c43fF1EZ05iF7Y,848
1711
1711
  pygpt_net/plugin/base/plugin.py,sha256=nbBTNh9nuaLcLdWfmcePpPeZeKJElgmOOP5o1uOZJdc,14103
@@ -1995,7 +1995,7 @@ pygpt_net/tools/text_editor/__init__.py,sha256=NXgKl9XkA6ecRSn3q154KLsu4FHCz4XL9
1995
1995
  pygpt_net/tools/text_editor/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1996
1996
  pygpt_net/tools/text_editor/ui/dialogs.py,sha256=lOgogExJgnLzq0KdbcHli-HslEZ2uivCI1dFFzEpsRA,2906
1997
1997
  pygpt_net/tools/text_editor/ui/widgets.py,sha256=K_yxkyMyLfBfXXZ8iOczci10tPlsBJ5JlUDxrzObm0s,2754
1998
- pygpt_net/ui/__init__.py,sha256=Ruykl9whIfrg3E3iAfDaFaZri-vCLUii0lpeFgunWJ4,9152
1998
+ pygpt_net/ui/__init__.py,sha256=RZmxge47FcBXfjpD8W5K-QcwnTirFDBmRxKQHAUafa0,9225
1999
1999
  pygpt_net/ui/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2000
2000
  pygpt_net/ui/base/config_dialog.py,sha256=92CnUmDFv74BQ6mbwhHjn3L_s7IYhJX3sChFqAD_5Vo,9255
2001
2001
  pygpt_net/ui/base/context_menu.py,sha256=Mq2UPWBwc9eaHc9qVOMQEQ7a2343wsrMrMnEzYdvs6Q,3245
@@ -2021,7 +2021,7 @@ pygpt_net/ui/dialog/preset_plugins.py,sha256=ynqc0aWjU7MTL4jxcVKaRH_tC9uzeWJCUzU
2021
2021
  pygpt_net/ui/dialog/profile.py,sha256=Xk9NNQmr1A5pRUxdedu7ePEBq5OYhLT8UInuRWYBktU,4105
2022
2022
  pygpt_net/ui/dialog/rename.py,sha256=Spb7cUVq1ivy3Zg28SBACJ7p_GwJ1gm82Oz9Ld_a_FU,973
2023
2023
  pygpt_net/ui/dialog/settings.py,sha256=XZQiNI-AbrUBT-mMo8wdQGJS9bOunaY6dYKAFdzDiUI,15725
2024
- pygpt_net/ui/dialog/snap.py,sha256=YtTAPbY3URBLEiSP-xVE4gUkbaBUpvyOLzVAisVpy6k,954
2024
+ pygpt_net/ui/dialog/snap.py,sha256=LQlDsF4E-ernI8GFL1T-q9wZnoo57xyYpg5HKmeSbg4,1065
2025
2025
  pygpt_net/ui/dialog/start.py,sha256=wHGKD6d4XGaKzDDvUCZOe_fK1aCaD21Vi_8R2td7-Zk,2459
2026
2026
  pygpt_net/ui/dialog/update.py,sha256=wJDe2D55XqlAd30vHLeWAuGmgaElwhTd82GIxzvqf4w,842
2027
2027
  pygpt_net/ui/dialog/url.py,sha256=SziMn4Wzyld7Os6b4QdaYAxjwcIWfydmorhTSG7WeUo,949
@@ -2034,7 +2034,7 @@ pygpt_net/ui/layout/chat/attachments_ctx.py,sha256=qyidK2bbTufWX-crtEhat2i3-Juqq
2034
2034
  pygpt_net/ui/layout/chat/attachments_uploaded.py,sha256=8xKa9xwU56ESSfBN5ybwT_BUZEwgij-tjHbhhzNuV2U,5462
2035
2035
  pygpt_net/ui/layout/chat/calendar.py,sha256=Vc6ztv4S_gO2ceqO-SekXyqI4V7cbf-JN_sZ5dwbl34,5812
2036
2036
  pygpt_net/ui/layout/chat/explorer.py,sha256=VGWS6JhXSJV2ry1pIY1Ijme6DvYHfsnKwfrieVXHc3U,1280
2037
- pygpt_net/ui/layout/chat/input.py,sha256=BUtfT_77fJW7xk5ciTMMIHFijU1vSmcvW9QC7NEHQtA,10674
2037
+ pygpt_net/ui/layout/chat/input.py,sha256=54Kd3KJNgN41vyydSsRtr9554BS_IDkymPQGiYEkaYY,10907
2038
2038
  pygpt_net/ui/layout/chat/markdown.py,sha256=hjYY8Da1z0IZZD086_csMcDY1wwagpuQTDZ-XfgeNgs,18656
2039
2039
  pygpt_net/ui/layout/chat/output.py,sha256=4a_BCzk2MKt5z-KYKD8iaapiS4Kj1R7Gj-8v-5R4BLs,9887
2040
2040
  pygpt_net/ui/layout/chat/painter.py,sha256=2yGU9GET5PpcGteGyWcHTtodKqAL7rxrqxhQ10vhodM,5471
@@ -2073,8 +2073,9 @@ pygpt_net/ui/widget/__init__.py,sha256=X9-pucLqQF9_ocDV-qNY6EQAJ_4dubGb-7TcWIzCX
2073
2073
  pygpt_net/ui/widget/anims/loader.py,sha256=PzxHraeABUyMIZlg4Rk_tbJnUPmiwxlhdcHaCkURWWw,5989
2074
2074
  pygpt_net/ui/widget/anims/toggles.py,sha256=_L2533IYyDkbnPCok9XBriIaKM5E9sHSznrwVWpKOOs,5755
2075
2075
  pygpt_net/ui/widget/audio/__init__.py,sha256=8HT4tQFqQogEEpGYTv2RplKBthlsFKcl5egnv4lzzEw,488
2076
+ pygpt_net/ui/widget/audio/bar.py,sha256=7i8zxSa1UDjYdXVY84ixJv1FMX-tVCrZSfB6bjxZ9z0,3198
2076
2077
  pygpt_net/ui/widget/audio/input.py,sha256=t9VAhP15HkSOvNV2crI3Kg6AgrQDj-wSQiiYTMlvK60,1721
2077
- pygpt_net/ui/widget/audio/input_button.py,sha256=WrsUzsMdqflTKU7pi5xfIvnKcbmgcjWGGHAprPTzaKA,5712
2078
+ pygpt_net/ui/widget/audio/input_button.py,sha256=xNK3dFN5BC-2RTsBRY7RDKSKqrKMdckei6Oavr0_XWE,4514
2078
2079
  pygpt_net/ui/widget/audio/output.py,sha256=UxkiCnVT9DNFeByDGTFW_CK0LW8xSvhEK1zygtHvx4k,1586
2079
2080
  pygpt_net/ui/widget/calendar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2080
2081
  pygpt_net/ui/widget/calendar/select.py,sha256=vSEaQZmyeDhmyOfptWv6me_6IaYpQb5Jgc_mGDIe_4U,9051
@@ -2101,7 +2102,7 @@ pygpt_net/ui/widget/dialog/profile.py,sha256=vMYf-9c6HKAOqtSuw-HpBEUgmCSxEQuIrGl
2101
2102
  pygpt_net/ui/widget/dialog/rename.py,sha256=HcImKH_gUHcyB1k5llwStmrlXvcSpxgR2V6IuEdMuCU,2191
2102
2103
  pygpt_net/ui/widget/dialog/settings.py,sha256=fKzbme2tdxzTSiQMNnCEgyD3lwCzFjLi85ikWulisGw,1614
2103
2104
  pygpt_net/ui/widget/dialog/settings_plugin.py,sha256=Kf1ZK_RY9CAnfeuzPoQ4wgsFb2yQl7X-VKzsYETA55o,1696
2104
- pygpt_net/ui/widget/dialog/snap.py,sha256=UKiHOywK1tw0HelSgsUaKXMlageDdzOH_TJot3lUVUg,2756
2105
+ pygpt_net/ui/widget/dialog/snap.py,sha256=4JojKDrDECFbNBnsE0505z336xLVskFz9TK3XF02Mtk,4074
2105
2106
  pygpt_net/ui/widget/dialog/update.py,sha256=aAmk-unSaGie3VXMaXmorza-p9Isrm06d0iSYnfcq3U,6900
2106
2107
  pygpt_net/ui/widget/dialog/url.py,sha256=7I17Pp9P2c3G1pODEY5dum_AF0nFnu2BMfbWTgEES-M,8765
2107
2108
  pygpt_net/ui/widget/dialog/workdir.py,sha256=D-C3YIt-wCoI-Eh7z--Z4R6P1UvtpkxeiaVcI-ycFck,1523
@@ -2175,8 +2176,8 @@ pygpt_net/ui/widget/textarea/web.py,sha256=9FoL02QY6mOxtc4t4fe8X7fVDIdPn9Sb_fwsv
2175
2176
  pygpt_net/ui/widget/vision/__init__.py,sha256=8HT4tQFqQogEEpGYTv2RplKBthlsFKcl5egnv4lzzEw,488
2176
2177
  pygpt_net/ui/widget/vision/camera.py,sha256=T8b5cmK6uhf_WSSxzPt_Qod8JgMnst6q8sQqRvgQiSA,2584
2177
2178
  pygpt_net/utils.py,sha256=Gsh_mITVke3bb8o-Ke57l__xA5a9Wv4t7tlsnSQULj8,6655
2178
- pygpt_net-2.4.53.dist-info/LICENSE,sha256=rbPqNB_xxANH8hKayJyIcTwD4bj4Y2G-Mcm85r1OImM,1126
2179
- pygpt_net-2.4.53.dist-info/METADATA,sha256=k2woDF3erJufNmvl5eDloWT5LGOYXi-aOCEQatZzgM0,167196
2180
- pygpt_net-2.4.53.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
2181
- pygpt_net-2.4.53.dist-info/entry_points.txt,sha256=qvpII6UHIt8XfokmQWnCYQrTgty8FeJ9hJvOuUFCN-8,43
2182
- pygpt_net-2.4.53.dist-info/RECORD,,
2179
+ pygpt_net-2.4.54.dist-info/LICENSE,sha256=rbPqNB_xxANH8hKayJyIcTwD4bj4Y2G-Mcm85r1OImM,1126
2180
+ pygpt_net-2.4.54.dist-info/METADATA,sha256=0fJiXhGHCcQEGca03Bc5_3Vdan85g85lZsU99qR-tn4,167593
2181
+ pygpt_net-2.4.54.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
2182
+ pygpt_net-2.4.54.dist-info/entry_points.txt,sha256=qvpII6UHIt8XfokmQWnCYQrTgty8FeJ9hJvOuUFCN-8,43
2183
+ pygpt_net-2.4.54.dist-info/RECORD,,