pygpt-net 2.4.49__py3-none-any.whl → 2.4.50__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 +5 -0
- README.md +7 -1
- pygpt_net/CHANGELOG.txt +5 -0
- pygpt_net/__init__.py +2 -2
- pygpt_net/controller/access/voice.py +19 -36
- pygpt_net/core/audio/__init__.py +10 -17
- pygpt_net/core/audio/capture.py +331 -0
- pygpt_net/data/config/config.json +2 -2
- pygpt_net/data/config/models.json +2 -2
- pygpt_net/data/config/modes.json +2 -2
- pygpt_net/plugin/audio_input/simple.py +22 -55
- pygpt_net/ui/widget/audio/input_button.py +61 -9
- pygpt_net/ui/widget/dialog/snap.py +2 -2
- pygpt_net/ui/widget/dialog/update.py +3 -2
- {pygpt_net-2.4.49.dist-info → pygpt_net-2.4.50.dist-info}/METADATA +8 -2
- {pygpt_net-2.4.49.dist-info → pygpt_net-2.4.50.dist-info}/RECORD +19 -18
- {pygpt_net-2.4.49.dist-info → pygpt_net-2.4.50.dist-info}/LICENSE +0 -0
- {pygpt_net-2.4.49.dist-info → pygpt_net-2.4.50.dist-info}/WHEEL +0 -0
- {pygpt_net-2.4.49.dist-info → pygpt_net-2.4.50.dist-info}/entry_points.txt +0 -0
CHANGELOG.md
CHANGED
README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://snapcraft.io/pygpt)
|
4
4
|
|
5
|
-
Release: **2.4.
|
5
|
+
Release: **2.4.50** | build: **2025.01.16** | Python: **>=3.10, <3.13**
|
6
6
|
|
7
7
|
> Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
|
8
8
|
>
|
@@ -118,6 +118,7 @@ sudo snap connect pygpt:camera
|
|
118
118
|
|
119
119
|
```commandline
|
120
120
|
sudo snap connect pygpt:audio-record :audio-record
|
121
|
+
sudo snap connect pygpt:alsa
|
121
122
|
```
|
122
123
|
|
123
124
|
**Connecting IPython in Docker in Snap version**:
|
@@ -3952,6 +3953,11 @@ may consume additional tokens that are not displayed in the main window.
|
|
3952
3953
|
|
3953
3954
|
## Recent changes:
|
3954
3955
|
|
3956
|
+
**2.4.50 (2025-01-16)**
|
3957
|
+
|
3958
|
+
- Refactored audio input core.
|
3959
|
+
- Added audio input volume progress bar.
|
3960
|
+
|
3955
3961
|
**2.4.49 (2025-01-16)**
|
3956
3962
|
|
3957
3963
|
- Fix: stream render in Assistants mode.
|
pygpt_net/CHANGELOG.txt
CHANGED
pygpt_net/__init__.py
CHANGED
@@ -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.01.16
|
9
|
+
# Updated Date: 2025.01.16 17: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.
|
16
|
+
__version__ = "2.4.50"
|
17
17
|
__build__ = "2025.01.16"
|
18
18
|
__maintainer__ = "Marcin Szczygliński"
|
19
19
|
__github__ = "https://github.com/szczyglis-dev/py-gpt"
|
@@ -6,13 +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:
|
9
|
+
# Updated Date: 2025.01.16 17:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
from typing import Optional, List, Dict, Any
|
13
13
|
|
14
|
-
import pyaudio
|
15
|
-
import wave
|
16
14
|
import os
|
17
15
|
|
18
16
|
from PySide6.QtCore import QTimer, Slot
|
@@ -38,9 +36,6 @@ class Voice:
|
|
38
36
|
"""
|
39
37
|
self.window = window
|
40
38
|
self.is_recording = False
|
41
|
-
self.frames = []
|
42
|
-
self.p = None
|
43
|
-
self.stream = None
|
44
39
|
self.timer = None
|
45
40
|
self.input_file = "voice_control.wav"
|
46
41
|
self.thread_started = False
|
@@ -218,15 +213,6 @@ class Voice:
|
|
218
213
|
|
219
214
|
def start_recording(self):
|
220
215
|
"""Start recording"""
|
221
|
-
self.frames = [] # clear audio frames
|
222
|
-
|
223
|
-
def callback(in_data, frame_count, time_info, status):
|
224
|
-
self.frames.append(in_data)
|
225
|
-
if self.is_recording:
|
226
|
-
return (in_data, pyaudio.paContinue)
|
227
|
-
else:
|
228
|
-
return (in_data, pyaudio.paComplete)
|
229
|
-
|
230
216
|
try:
|
231
217
|
self.is_recording = True
|
232
218
|
self.switch_btn_stop()
|
@@ -235,23 +221,26 @@ class Voice:
|
|
235
221
|
if self.window.controller.audio.is_playing():
|
236
222
|
self.window.controller.audio.stop_output()
|
237
223
|
|
224
|
+
# set audio volume bar
|
225
|
+
self.window.core.audio.capture.set_bar(
|
226
|
+
self.window.ui.nodes['voice.control.btn'].bar
|
227
|
+
)
|
228
|
+
|
238
229
|
# start timeout timer to prevent infinite recording
|
239
230
|
if self.timer is None:
|
240
231
|
self.timer = QTimer()
|
241
232
|
self.timer.timeout.connect(self.stop_timeout)
|
242
233
|
self.timer.start(self.TIMEOUT_SECONDS * 1000)
|
243
234
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
frames_per_buffer=1024,
|
250
|
-
stream_callback=callback)
|
235
|
+
if not self.window.core.audio.capture.check_audio_input():
|
236
|
+
raise Exception("Audio input not working.")
|
237
|
+
# IMPORTANT!!!!
|
238
|
+
# Stop here if audio input not working!
|
239
|
+
# This prevents the app from freezing when audio input is not working!
|
251
240
|
|
241
|
+
self.window.core.audio.capture.start() # start recording if audio is OK
|
252
242
|
self.window.update_status(trans('audio.speak.now'))
|
253
243
|
self.window.dispatch(AppEvent(AppEvent.VOICE_CONTROL_STARTED)) # app event
|
254
|
-
self.stream.start_stream()
|
255
244
|
except Exception as e:
|
256
245
|
self.is_recording = False
|
257
246
|
self.window.core.debug.log(e)
|
@@ -270,35 +259,29 @@ class Voice:
|
|
270
259
|
|
271
260
|
:param timeout: True if stopped due to timeout
|
272
261
|
"""
|
262
|
+
self.window.core.audio.capture.reset_audio_level()
|
273
263
|
self.is_recording = False
|
274
264
|
if self.timer:
|
275
265
|
self.timer.stop()
|
276
266
|
self.timer = None
|
277
267
|
self.switch_btn_start() # switch button to start
|
278
268
|
path = os.path.join(self.window.core.config.path, self.input_file)
|
269
|
+
self.window.core.audio.capture.set_path(path)
|
279
270
|
|
280
|
-
if self.
|
281
|
-
self.
|
282
|
-
self.stream.close()
|
283
|
-
self.p.terminate()
|
284
|
-
|
271
|
+
if self.window.core.audio.capture.has_source():
|
272
|
+
self.window.core.audio.capture.stop() # stop recording
|
285
273
|
# abort if timeout
|
286
274
|
if timeout:
|
287
275
|
self.window.dispatch(AppEvent(AppEvent.VOICE_CONTROL_STOPPED)) # app event
|
288
276
|
self.window.update_status("Aborted.".format(self.TIMEOUT_SECONDS))
|
289
277
|
return
|
290
278
|
|
291
|
-
if self.
|
292
|
-
|
279
|
+
if self.window.core.audio.capture.has_frames():
|
280
|
+
frames = self.window.core.audio.capture.get_frames()
|
281
|
+
if len(frames) < self.MIN_FRAMES:
|
293
282
|
self.window.update_status(trans("status.audio.too_short"))
|
294
283
|
self.window.dispatch(AppEvent(AppEvent.VOICE_CONTROL_STOPPED)) # app event
|
295
284
|
return
|
296
|
-
wf = wave.open(path, 'wb')
|
297
|
-
wf.setnchannels(1)
|
298
|
-
wf.setsampwidth(self.p.get_sample_size(pyaudio.paInt16))
|
299
|
-
wf.setframerate(44100)
|
300
|
-
wf.writeframes(b''.join(self.frames))
|
301
|
-
wf.close()
|
302
285
|
self.window.dispatch(AppEvent(AppEvent.VOICE_CONTROL_SENT)) # app event
|
303
286
|
self.handle_thread(True) # handle transcription in simple mode
|
304
287
|
else:
|
pygpt_net/core/audio/__init__.py
CHANGED
@@ -6,17 +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:
|
9
|
+
# Updated Date: 2025.01.16 17: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
|
15
16
|
from bs4 import UnicodeDammit
|
16
17
|
|
17
18
|
from pygpt_net.provider.audio_input.base import BaseProvider as InputBaseProvider
|
18
19
|
from pygpt_net.provider.audio_output.base import BaseProvider as OutputBaseProvider
|
19
20
|
|
21
|
+
from .capture import Capture
|
20
22
|
from .whisper import Whisper
|
21
23
|
|
22
24
|
|
@@ -28,6 +30,7 @@ class Audio:
|
|
28
30
|
:param window: Window instance
|
29
31
|
"""
|
30
32
|
self.window = window
|
33
|
+
self.capture = Capture(window)
|
31
34
|
self.whisper = Whisper(window)
|
32
35
|
self.providers = {
|
33
36
|
"input": {},
|
@@ -41,21 +44,12 @@ class Audio:
|
|
41
44
|
|
42
45
|
:return devices list: [(id, name)]
|
43
46
|
"""
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
info = p.get_device_info_by_index(i)
|
51
|
-
if info["maxInputChannels"] > 0:
|
52
|
-
dammit = UnicodeDammit(info["name"])
|
53
|
-
devices.append((i, dammit.unicode_markup))
|
54
|
-
# print(f"Device ID {i}: {info['name']}")
|
55
|
-
p.terminate()
|
56
|
-
except Exception as e:
|
57
|
-
print(f"Audio input devices receive error: {e}")
|
58
|
-
return devices
|
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
|
59
53
|
|
60
54
|
def is_device_compatible(self, device_index: int) -> bool:
|
61
55
|
"""
|
@@ -69,7 +63,6 @@ class Audio:
|
|
69
63
|
channels = int(self.window.core.config.get('audio.input.channels', 1))
|
70
64
|
p = pyaudio.PyAudio()
|
71
65
|
info = p.get_device_info_by_index(device_index)
|
72
|
-
supported = False
|
73
66
|
try:
|
74
67
|
p.is_format_supported(
|
75
68
|
rate=rate,
|
@@ -0,0 +1,331 @@
|
|
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.16 17:00:00 #
|
10
|
+
# ================================================== #
|
11
|
+
|
12
|
+
import numpy as np
|
13
|
+
import wave
|
14
|
+
|
15
|
+
from PySide6.QtMultimedia import QAudioFormat, QMediaDevices, QAudioSource
|
16
|
+
|
17
|
+
class Capture:
|
18
|
+
def __init__(self, window=None):
|
19
|
+
"""
|
20
|
+
Audio input capture core
|
21
|
+
|
22
|
+
:param window: Window instance
|
23
|
+
"""
|
24
|
+
self.window = window
|
25
|
+
self.audio_source = None
|
26
|
+
self.audio_io_device = None
|
27
|
+
self.frames = []
|
28
|
+
self.actual_audio_format = None
|
29
|
+
self.path = None
|
30
|
+
self.disconnected = False
|
31
|
+
self.devices = []
|
32
|
+
self.selected_device = None
|
33
|
+
self.bar = None
|
34
|
+
self.check_audio_devices()
|
35
|
+
|
36
|
+
def set_path(self, path: str):
|
37
|
+
"""
|
38
|
+
Set audio input file path
|
39
|
+
|
40
|
+
:param path: file path
|
41
|
+
"""
|
42
|
+
self.path = path
|
43
|
+
|
44
|
+
def set_bar(self, bar):
|
45
|
+
"""
|
46
|
+
Set audio level bar
|
47
|
+
|
48
|
+
:param bar: audio level bar
|
49
|
+
"""
|
50
|
+
self.bar = bar
|
51
|
+
|
52
|
+
def check_audio_devices(self):
|
53
|
+
"""Check audio input devices"""
|
54
|
+
self.devices = QMediaDevices.audioInputs()
|
55
|
+
if not self.devices:
|
56
|
+
# no devices found
|
57
|
+
self.selected_device = None
|
58
|
+
print("No audio input devices found.")
|
59
|
+
else:
|
60
|
+
# set the first device as default
|
61
|
+
self.selected_device = self.devices[0]
|
62
|
+
|
63
|
+
def device_changed(self, index: int):
|
64
|
+
"""
|
65
|
+
Change audio input device
|
66
|
+
|
67
|
+
:param index: device index
|
68
|
+
"""
|
69
|
+
if 0 <= index < len(self.devices):
|
70
|
+
self.selected_device = self.devices[index]
|
71
|
+
else:
|
72
|
+
self.selected_device = None
|
73
|
+
|
74
|
+
def prepare_device(self):
|
75
|
+
"""Set the current audio input device"""
|
76
|
+
device_id = int(self.window.core.config.get('audio.input.device', 0))
|
77
|
+
self.device_changed(device_id)
|
78
|
+
|
79
|
+
def start(self):
|
80
|
+
"""
|
81
|
+
Start audio input recording
|
82
|
+
|
83
|
+
:return: True if started
|
84
|
+
"""
|
85
|
+
# Clear previous frames
|
86
|
+
self.frames = []
|
87
|
+
|
88
|
+
# Prepare selected device
|
89
|
+
self.prepare_device()
|
90
|
+
if not self.selected_device:
|
91
|
+
print("No audio input device selected")
|
92
|
+
return
|
93
|
+
if self.disconnected:
|
94
|
+
print("Audio source disconnected, please connect the audio source")
|
95
|
+
return False
|
96
|
+
|
97
|
+
# Prevent multiple recordings
|
98
|
+
if self.audio_source is not None:
|
99
|
+
return False
|
100
|
+
|
101
|
+
# Set up audio input and start recording
|
102
|
+
self.setup_audio_input()
|
103
|
+
return True
|
104
|
+
|
105
|
+
def stop(self):
|
106
|
+
"""
|
107
|
+
Stop audio input recording
|
108
|
+
|
109
|
+
:return: True if stopped
|
110
|
+
"""
|
111
|
+
result = False
|
112
|
+
if self.audio_source is not None:
|
113
|
+
# Disconnect the readyRead signal
|
114
|
+
self.audio_io_device.readyRead.disconnect(self.process_audio_input)
|
115
|
+
self.audio_source.stop()
|
116
|
+
self.audio_source = None
|
117
|
+
self.audio_io_device = None
|
118
|
+
|
119
|
+
# Save frames to file (if any)
|
120
|
+
if self.frames:
|
121
|
+
self.save_audio_file(self.path)
|
122
|
+
result = True
|
123
|
+
else:
|
124
|
+
print("No audio data recorded")
|
125
|
+
return result
|
126
|
+
|
127
|
+
def has_source(self) -> bool:
|
128
|
+
"""
|
129
|
+
Check if audio source is available
|
130
|
+
|
131
|
+
:return: True if available
|
132
|
+
"""
|
133
|
+
if self.audio_source is not None:
|
134
|
+
return True
|
135
|
+
return False
|
136
|
+
|
137
|
+
def has_frames(self) -> bool:
|
138
|
+
"""
|
139
|
+
Check if audio frames are available
|
140
|
+
|
141
|
+
:return: True if available
|
142
|
+
"""
|
143
|
+
if self.frames:
|
144
|
+
return True
|
145
|
+
return False
|
146
|
+
|
147
|
+
def get_frames(self) -> list:
|
148
|
+
"""
|
149
|
+
Get recorded audio frames
|
150
|
+
|
151
|
+
:return: list of frames
|
152
|
+
"""
|
153
|
+
return self.frames
|
154
|
+
|
155
|
+
def setup_audio_input(self):
|
156
|
+
"""Set up audio input device and start recording"""
|
157
|
+
if not self.selected_device:
|
158
|
+
print("No audio input device selected")
|
159
|
+
return
|
160
|
+
|
161
|
+
# Define audio format
|
162
|
+
audio_format = QAudioFormat()
|
163
|
+
audio_format.setSampleRate(int(self.window.core.config.get('audio.input.rate', 44100)))
|
164
|
+
audio_format.setChannelCount(int(self.window.core.config.get('audio.input.channels', 1)))
|
165
|
+
audio_format.setSampleFormat(QAudioFormat.SampleFormat.Int16)
|
166
|
+
|
167
|
+
# Select default audio input device
|
168
|
+
audio_input_device = self.selected_device
|
169
|
+
|
170
|
+
# Check if the format is supported
|
171
|
+
if not audio_input_device.isFormatSupported(audio_format):
|
172
|
+
print("Requested format not supported, using nearest format.")
|
173
|
+
audio_format = audio_input_device.preferredFormat()
|
174
|
+
|
175
|
+
# Store the actual format being used
|
176
|
+
self.actual_audio_format = audio_format
|
177
|
+
|
178
|
+
# Create QAudioSource object with the device and format
|
179
|
+
try:
|
180
|
+
self.audio_source = QAudioSource(audio_input_device, audio_format)
|
181
|
+
except Exception as e:
|
182
|
+
self.disconnected = True
|
183
|
+
print(f"Failed to create audio source: {e}")
|
184
|
+
|
185
|
+
# Start audio input and obtain the QIODevice
|
186
|
+
try:
|
187
|
+
self.audio_io_device = self.audio_source.start()
|
188
|
+
if self.audio_io_device is None:
|
189
|
+
raise Exception("Unable to access audio input device")
|
190
|
+
except Exception as e:
|
191
|
+
print(f"Failed to start audio input: {e}")
|
192
|
+
self.disconnected = True
|
193
|
+
self.audio_source = None
|
194
|
+
self.audio_io_device = None
|
195
|
+
return
|
196
|
+
|
197
|
+
# Connect the readyRead signal to process incoming data
|
198
|
+
self.audio_io_device.readyRead.connect(self.process_audio_input)
|
199
|
+
|
200
|
+
def process_audio_input(self):
|
201
|
+
"""Process incoming audio data"""
|
202
|
+
data = self.audio_io_device.readAll()
|
203
|
+
if data.isEmpty():
|
204
|
+
return
|
205
|
+
|
206
|
+
# Convert QByteArray to bytes
|
207
|
+
data_bytes = data.data()
|
208
|
+
|
209
|
+
# Append raw data to frames list for saving
|
210
|
+
self.frames.append(data_bytes)
|
211
|
+
|
212
|
+
# Determine the correct dtype and normalization factor
|
213
|
+
sample_format = self.actual_audio_format.sampleFormat()
|
214
|
+
dtype = self.get_dtype_from_sample_format(sample_format)
|
215
|
+
normalization_factor = self.get_normalization_factor(sample_format)
|
216
|
+
|
217
|
+
# Convert bytes to NumPy array of the appropriate type
|
218
|
+
samples = np.frombuffer(data_bytes, dtype=dtype)
|
219
|
+
if samples.size == 0:
|
220
|
+
return
|
221
|
+
|
222
|
+
# For unsigned formats, center the data
|
223
|
+
if sample_format == QAudioFormat.SampleFormat.UInt8:
|
224
|
+
samples = samples.astype(np.int16)
|
225
|
+
samples -= 128
|
226
|
+
|
227
|
+
# Compute RMS of the audio samples as float64 for precision
|
228
|
+
rms = np.sqrt(np.mean(samples.astype(np.float64) ** 2))
|
229
|
+
|
230
|
+
# Normalize RMS value based on the sample format
|
231
|
+
level = rms / normalization_factor
|
232
|
+
|
233
|
+
# Ensure level is within 0.0 to 1.0
|
234
|
+
level = min(max(level, 0.0), 1.0)
|
235
|
+
|
236
|
+
# Scale to 0-100
|
237
|
+
level_percent = level * 100
|
238
|
+
|
239
|
+
# Update the level bar widget
|
240
|
+
self.update_audio_level(level_percent)
|
241
|
+
|
242
|
+
def update_audio_level(self, level: int):
|
243
|
+
"""
|
244
|
+
Update the audio level bar
|
245
|
+
|
246
|
+
:param level: audio level
|
247
|
+
"""
|
248
|
+
if self.bar is not None:
|
249
|
+
self.bar.setLevel(level)
|
250
|
+
|
251
|
+
def reset_audio_level(self):
|
252
|
+
"""Reset the audio level bar"""
|
253
|
+
if self.bar is not None:
|
254
|
+
self.bar.setLevel(0)
|
255
|
+
|
256
|
+
def save_audio_file(self, filename: str):
|
257
|
+
"""
|
258
|
+
Save the recorded audio frames to a WAV file
|
259
|
+
|
260
|
+
:param filename: output file name
|
261
|
+
"""
|
262
|
+
# Define the parameters for the WAV file
|
263
|
+
channels = self.actual_audio_format.channelCount()
|
264
|
+
sample_size = self.actual_audio_format.bytesPerSample()
|
265
|
+
frame_rate = self.actual_audio_format.sampleRate()
|
266
|
+
|
267
|
+
# Open the WAV file
|
268
|
+
wf = wave.open(filename, 'wb')
|
269
|
+
wf.setnchannels(channels)
|
270
|
+
wf.setsampwidth(sample_size)
|
271
|
+
wf.setframerate(frame_rate)
|
272
|
+
|
273
|
+
# Write frames to the file
|
274
|
+
wf.writeframes(b''.join(self.frames))
|
275
|
+
wf.close()
|
276
|
+
|
277
|
+
def get_dtype_from_sample_format(self, sample_format):
|
278
|
+
"""
|
279
|
+
Get the NumPy dtype corresponding to the QAudioFormat sample format
|
280
|
+
|
281
|
+
:param sample_format: QAudioFormat.SampleFormat
|
282
|
+
"""
|
283
|
+
if sample_format == QAudioFormat.SampleFormat.UInt8:
|
284
|
+
return np.uint8
|
285
|
+
elif sample_format == QAudioFormat.SampleFormat.Int16:
|
286
|
+
return np.int16
|
287
|
+
elif sample_format == QAudioFormat.SampleFormat.Int32:
|
288
|
+
return np.int32
|
289
|
+
elif sample_format == QAudioFormat.SampleFormat.Float:
|
290
|
+
return np.float32
|
291
|
+
else:
|
292
|
+
raise ValueError("Unsupported sample format")
|
293
|
+
|
294
|
+
def get_normalization_factor(self, sample_format):
|
295
|
+
"""
|
296
|
+
Get the normalization factor for the QAudioFormat sample format
|
297
|
+
|
298
|
+
:param sample_format: QAudioFormat.SampleFormat
|
299
|
+
"""
|
300
|
+
if sample_format == QAudioFormat.SampleFormat.UInt8:
|
301
|
+
return 255.0
|
302
|
+
elif sample_format == QAudioFormat.SampleFormat.Int16:
|
303
|
+
return 32768.0
|
304
|
+
elif sample_format == QAudioFormat.SampleFormat.Int32:
|
305
|
+
return float(2 ** 31)
|
306
|
+
elif sample_format == QAudioFormat.SampleFormat.Float:
|
307
|
+
return 1.0
|
308
|
+
else:
|
309
|
+
raise ValueError("Unsupported sample format")
|
310
|
+
|
311
|
+
def check_audio_input(self) -> bool:
|
312
|
+
"""
|
313
|
+
Check if default audio input device is working
|
314
|
+
|
315
|
+
:return: True if working
|
316
|
+
"""
|
317
|
+
import pyaudio
|
318
|
+
try:
|
319
|
+
rate = 44100
|
320
|
+
channels = 1
|
321
|
+
p = pyaudio.PyAudio()
|
322
|
+
stream = p.open(format=pyaudio.paInt16,
|
323
|
+
channels=channels,
|
324
|
+
rate=rate,
|
325
|
+
input=True)
|
326
|
+
stream.stop_stream()
|
327
|
+
stream.close()
|
328
|
+
p.terminate()
|
329
|
+
return True
|
330
|
+
except Exception as e:
|
331
|
+
return False
|
pygpt_net/data/config/modes.json
CHANGED
@@ -6,11 +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:
|
9
|
+
# Updated Date: 2025.01.16 17:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
|
-
import pyaudio
|
13
|
-
import wave
|
14
12
|
import os
|
15
13
|
|
16
14
|
from PySide6.QtCore import QTimer
|
@@ -32,9 +30,6 @@ class Simple:
|
|
32
30
|
"""
|
33
31
|
self.plugin = plugin
|
34
32
|
self.is_recording = False
|
35
|
-
self.frames = []
|
36
|
-
self.p = None
|
37
|
-
self.stream = None
|
38
33
|
self.timer = None
|
39
34
|
|
40
35
|
def toggle_recording(self):
|
@@ -61,54 +56,33 @@ class Simple:
|
|
61
56
|
|
62
57
|
def start_recording(self):
|
63
58
|
"""Start recording"""
|
64
|
-
self.frames = [] # clear audio frames
|
65
|
-
|
66
|
-
def callback(in_data, frame_count, time_info, status):
|
67
|
-
self.frames.append(in_data)
|
68
|
-
if self.is_recording:
|
69
|
-
return (in_data, pyaudio.paContinue)
|
70
|
-
else:
|
71
|
-
return (in_data, pyaudio.paComplete)
|
72
|
-
|
73
59
|
try:
|
74
|
-
self.is_recording = True
|
75
|
-
self.switch_btn_stop()
|
76
|
-
|
77
60
|
# stop audio output if playing
|
78
61
|
if self.plugin.window.controller.audio.is_playing():
|
79
62
|
self.plugin.window.controller.audio.stop_output()
|
80
63
|
|
64
|
+
# set audio volume bar
|
65
|
+
self.plugin.window.core.audio.capture.set_bar(
|
66
|
+
self.plugin.window.ui.plugin_addon['audio.input.btn'].bar
|
67
|
+
)
|
68
|
+
|
81
69
|
# start timeout timer to prevent infinite recording
|
82
70
|
if self.timer is None:
|
83
71
|
self.timer = QTimer()
|
84
72
|
self.timer.timeout.connect(self.stop_timeout)
|
85
73
|
self.timer.start(self.TIMEOUT_SECONDS * 1000)
|
86
74
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
err = self.plugin.window.core.audio.get_last_error()
|
93
|
-
message = "Selected audio input device is not compatible. Please select another one. ERROR: " + str(err)
|
94
|
-
self.is_recording = False
|
95
|
-
self.plugin.window.core.debug.log(message)
|
96
|
-
self.plugin.window.ui.dialogs.alert(message)
|
97
|
-
self.switch_btn_start() # switch button to start
|
98
|
-
return
|
99
|
-
|
100
|
-
self.p = pyaudio.PyAudio()
|
101
|
-
self.stream = self.p.open(format=pyaudio.paInt16,
|
102
|
-
channels=channels,
|
103
|
-
rate=rate,
|
104
|
-
input=True,
|
105
|
-
input_device_index=device_id,
|
106
|
-
frames_per_buffer=1024,
|
107
|
-
stream_callback=callback)
|
75
|
+
if not self.plugin.window.core.audio.capture.check_audio_input():
|
76
|
+
raise Exception("Audio input not working.")
|
77
|
+
# IMPORTANT!!!!
|
78
|
+
# Stop here if audio input not working!
|
79
|
+
# This prevents the app from freezing when audio input is not working!
|
108
80
|
|
81
|
+
self.is_recording = True
|
82
|
+
self.switch_btn_stop()
|
83
|
+
self.plugin.window.core.audio.capture.start() # start recording if audio is OK
|
109
84
|
self.plugin.window.update_status(trans('audio.speak.now'))
|
110
85
|
self.plugin.window.dispatch(AppEvent(AppEvent.INPUT_VOICE_LISTEN_STARTED)) # app event
|
111
|
-
self.stream.start_stream()
|
112
86
|
except Exception as e:
|
113
87
|
self.is_recording = False
|
114
88
|
self.plugin.window.core.debug.log(e)
|
@@ -127,6 +101,7 @@ class Simple:
|
|
127
101
|
|
128
102
|
:param timeout: True if stopped due to timeout
|
129
103
|
"""
|
104
|
+
self.plugin.window.core.audio.capture.reset_audio_level()
|
130
105
|
self.is_recording = False
|
131
106
|
self.plugin.window.dispatch(AppEvent(AppEvent.INPUT_VOICE_LISTEN_STOPPED)) # app event
|
132
107
|
if self.timer:
|
@@ -134,30 +109,22 @@ class Simple:
|
|
134
109
|
self.timer = None
|
135
110
|
self.switch_btn_start() # switch button to start
|
136
111
|
path = os.path.join(self.plugin.window.core.config.path, self.plugin.input_file)
|
112
|
+
self.plugin.window.core.audio.capture.set_path(path)
|
137
113
|
|
138
|
-
if self.
|
139
|
-
self.
|
140
|
-
self.stream.close()
|
141
|
-
self.p.terminate()
|
142
|
-
|
114
|
+
if self.plugin.window.core.audio.capture.has_source():
|
115
|
+
self.plugin.window.core.audio.capture.stop() # stop recording
|
143
116
|
# abort if timeout
|
144
117
|
if timeout:
|
145
118
|
self.plugin.window.update_status("Aborted.".format(self.TIMEOUT_SECONDS))
|
146
119
|
return
|
147
120
|
|
148
|
-
if self.
|
149
|
-
|
121
|
+
if self.plugin.window.core.audio.capture.has_frames():
|
122
|
+
frames = self.plugin.window.core.audio.capture.get_frames()
|
123
|
+
if len(frames) < self.MIN_FRAMES:
|
150
124
|
self.plugin.window.update_status(trans("status.audio.too_short"))
|
151
125
|
self.plugin.window.dispatch(AppEvent(AppEvent.VOICE_CONTROL_STOPPED)) # app event
|
152
126
|
return
|
153
|
-
|
154
|
-
channels = int(self.plugin.window.core.config.get('audio.input.channels', 1))
|
155
|
-
rate = int(self.plugin.window.core.config.get('audio.input.rate', 44100))
|
156
|
-
wf.setnchannels(channels)
|
157
|
-
wf.setsampwidth(self.p.get_sample_size(pyaudio.paInt16))
|
158
|
-
wf.setframerate(rate)
|
159
|
-
wf.writeframes(b''.join(self.frames))
|
160
|
-
wf.close()
|
127
|
+
|
161
128
|
self.plugin.handle_thread(True) # handle transcription in simple mode
|
162
129
|
else:
|
163
130
|
self.plugin.window.update_status("")
|
@@ -6,11 +6,12 @@
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
7
7
|
# MIT License #
|
8
8
|
# Created By : Marcin Szczygliński #
|
9
|
-
# Updated Date:
|
9
|
+
# Updated Date: 2025.01.16 17:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
from PySide6.QtCore import Qt
|
13
|
-
from PySide6.
|
13
|
+
from PySide6.QtGui import QPainter
|
14
|
+
from PySide6.QtWidgets import QLabel, QHBoxLayout, QWidget, QPushButton, QVBoxLayout
|
14
15
|
|
15
16
|
from pygpt_net.core.events import Event, AppEvent
|
16
17
|
from pygpt_net.utils import trans
|
@@ -31,14 +32,18 @@ class VoiceControlButton(QWidget):
|
|
31
32
|
self.btn_toggle.setCursor(Qt.PointingHandCursor)
|
32
33
|
self.btn_toggle.setMinimumWidth(200)
|
33
34
|
|
35
|
+
self.bar = LevelBar(self)
|
36
|
+
self.bar.setLevel(0)
|
37
|
+
|
34
38
|
# status
|
35
39
|
self.status = QLabel("")
|
36
40
|
self.status.setStyleSheet("color: #999; font-size: 10px; font-weight: 400; margin: 0; padding: 0; border: 0;")
|
37
41
|
self.status.setMaximumHeight(15)
|
38
42
|
|
39
|
-
self.layout =
|
43
|
+
self.layout = QVBoxLayout(self)
|
40
44
|
self.layout.addWidget(self.btn_toggle)
|
41
|
-
self.layout.addWidget(self.status)
|
45
|
+
# self.layout.addWidget(self.status)
|
46
|
+
self.layout.addWidget(self.bar)
|
42
47
|
|
43
48
|
# self.layout.addWidget(self.stop)
|
44
49
|
self.layout.setAlignment(Qt.AlignCenter)
|
@@ -82,14 +87,18 @@ class AudioInputButton(QWidget):
|
|
82
87
|
self.btn_toggle.setCursor(Qt.PointingHandCursor)
|
83
88
|
self.btn_toggle.setMinimumWidth(200)
|
84
89
|
|
90
|
+
self.bar = LevelBar(self)
|
91
|
+
self.bar.setLevel(0)
|
92
|
+
|
85
93
|
# status
|
86
|
-
self.status = QLabel("")
|
87
|
-
self.status.setStyleSheet("color: #999; font-size: 10px; font-weight: 400; margin: 0; padding: 0; border: 0;")
|
88
|
-
self.status.setMaximumHeight(15)
|
94
|
+
#self.status = QLabel("xxx")
|
95
|
+
#self.status.setStyleSheet("color: #999; font-size: 10px; font-weight: 400; margin: 0; padding: 0; border: 0;")
|
96
|
+
#self.status.setMaximumHeight(15)
|
89
97
|
|
90
|
-
self.layout =
|
98
|
+
self.layout = QVBoxLayout(self)
|
91
99
|
self.layout.addWidget(self.btn_toggle)
|
92
|
-
self.layout.addWidget(self.status)
|
100
|
+
#self.layout.addWidget(self.status)
|
101
|
+
self.layout.addWidget(self.bar)
|
93
102
|
|
94
103
|
# self.layout.addWidget(self.stop)
|
95
104
|
self.layout.setAlignment(Qt.AlignCenter)
|
@@ -116,3 +125,46 @@ class AudioInputButton(QWidget):
|
|
116
125
|
"""Toggle recording"""
|
117
126
|
event = Event(Event.AUDIO_INPUT_RECORD_TOGGLE)
|
118
127
|
self.window.dispatch(event)
|
128
|
+
|
129
|
+
|
130
|
+
class LevelBar(QWidget):
|
131
|
+
def __init__(self, parent=None):
|
132
|
+
super().__init__(parent)
|
133
|
+
self._level = 0.0 # level from 0.0 to 100.0
|
134
|
+
self.setFixedSize(200, 5) # bar size
|
135
|
+
|
136
|
+
def setLevel(self, level):
|
137
|
+
"""
|
138
|
+
Set volume level
|
139
|
+
|
140
|
+
:param level: level
|
141
|
+
"""
|
142
|
+
self._level = level
|
143
|
+
self.update()
|
144
|
+
|
145
|
+
def paintEvent(self, event):
|
146
|
+
"""
|
147
|
+
Paint event
|
148
|
+
|
149
|
+
:param event: event
|
150
|
+
"""
|
151
|
+
painter = QPainter(self)
|
152
|
+
painter.fillRect(self.rect(), Qt.transparent)
|
153
|
+
level_width = (self._level / 100.0) * self.width()
|
154
|
+
painter.setBrush(Qt.green)
|
155
|
+
painter.setPen(Qt.NoPen)
|
156
|
+
painter.drawRect(0, 0, level_width, self.height())
|
157
|
+
|
158
|
+
"""
|
159
|
+
# --- bar from center ---
|
160
|
+
def paintEvent(self, event):
|
161
|
+
painter = QPainter(self)
|
162
|
+
painter.fillRect(self.rect(), Qt.transparent)
|
163
|
+
level_width = (self._level / 100.0) * self.width()
|
164
|
+
half_level_width = level_width / 2
|
165
|
+
center_x = self.width() / 2
|
166
|
+
rect_x = center_x - half_level_width
|
167
|
+
painter.setBrush(Qt.green)
|
168
|
+
painter.setPen(Qt.NoPen)
|
169
|
+
painter.drawRect(rect_x, 0, level_width, self.height())
|
170
|
+
"""
|
@@ -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:
|
9
|
+
# Updated Date: 2025.01.16 17:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
from PySide6.QtWidgets import QDialog, QLabel, QVBoxLayout, QPushButton
|
@@ -55,7 +55,7 @@ class SnapDialogAudioInput(QDialog):
|
|
55
55
|
self.window = window
|
56
56
|
self.setParent(window)
|
57
57
|
self.setWindowTitle("Snap is detected")
|
58
|
-
self.cmd = CmdLabel(self.window, "sudo snap connect pygpt:audio-record :audio-record")
|
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")
|
61
61
|
self.btn.clicked.connect(self.accept)
|
@@ -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:
|
9
|
+
# Updated Date: 2025.01.16 17:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
import os
|
@@ -177,7 +177,8 @@ class UpdateDialog(BaseDialog):
|
|
177
177
|
if self.window.core.platforms.is_windows():
|
178
178
|
self.download_link = download_windows
|
179
179
|
self.download_file.setText("{} .msi ({})".format(trans("action.download"), version))
|
180
|
-
self.download_file.setVisible(
|
180
|
+
self.download_file.setVisible(False) # Windows Store: disabled
|
181
|
+
self.info_upgrade.setVisible(False) # Windows Store: disabled
|
181
182
|
elif self.window.core.platforms.is_linux():
|
182
183
|
self.download_link = download_linux
|
183
184
|
self.download_file.setText("{} .tar.gz ({})".format(trans("action.download"), version))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pygpt-net
|
3
|
-
Version: 2.4.
|
3
|
+
Version: 2.4.50
|
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
|
[](https://snapcraft.io/pygpt)
|
95
95
|
|
96
|
-
Release: **2.4.
|
96
|
+
Release: **2.4.50** | build: **2025.01.16** | Python: **>=3.10, <3.13**
|
97
97
|
|
98
98
|
> Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
|
99
99
|
>
|
@@ -209,6 +209,7 @@ sudo snap connect pygpt:camera
|
|
209
209
|
|
210
210
|
```commandline
|
211
211
|
sudo snap connect pygpt:audio-record :audio-record
|
212
|
+
sudo snap connect pygpt:alsa
|
212
213
|
```
|
213
214
|
|
214
215
|
**Connecting IPython in Docker in Snap version**:
|
@@ -4043,6 +4044,11 @@ may consume additional tokens that are not displayed in the main window.
|
|
4043
4044
|
|
4044
4045
|
## Recent changes:
|
4045
4046
|
|
4047
|
+
**2.4.50 (2025-01-16)**
|
4048
|
+
|
4049
|
+
- Refactored audio input core.
|
4050
|
+
- Added audio input volume progress bar.
|
4051
|
+
|
4046
4052
|
**2.4.49 (2025-01-16)**
|
4047
4053
|
|
4048
4054
|
- Fix: stream render in Assistants mode.
|
@@ -1,16 +1,16 @@
|
|
1
|
-
CHANGELOG.md,sha256=
|
2
|
-
README.md,sha256=
|
1
|
+
CHANGELOG.md,sha256=adPwRkkYgkZHuwQn1IB8ImnyuwtpSXTJoKvdA3zwEzI,81036
|
2
|
+
README.md,sha256=WheYr0os5j1AVG5X4ifEyjK5swLluh01k7PCv8-wj5k,164973
|
3
3
|
icon.png,sha256=CzcINJaU23a9hNjsDlDNbyuiEvKZ4Wg6DQVYF6SpuRg,13970
|
4
|
-
pygpt_net/CHANGELOG.txt,sha256
|
4
|
+
pygpt_net/CHANGELOG.txt,sha256=B1Gu0sgfveaUUwyPeqflV-uSmsdJbE39JSrukL92QM0,79550
|
5
5
|
pygpt_net/LICENSE,sha256=dz9sfFgYahvu2NZbx4C1xCsVn9GVer2wXcMkFRBvqzY,1146
|
6
|
-
pygpt_net/__init__.py,sha256=
|
6
|
+
pygpt_net/__init__.py,sha256=u1pfEPJl3_fBwyjhEFbmNcNZv1jJ250_17nt8EK9XjY,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
|
10
10
|
pygpt_net/controller/__init__.py,sha256=wtlkw4viFVuf2sP0iMSkK0jI1QMa3kcPfvQaqnC-1io,5917
|
11
11
|
pygpt_net/controller/access/__init__.py,sha256=5DzR7zVmFsOICo9I5mEQcIOgsk2WCNi1amUWxExwUiY,2639
|
12
12
|
pygpt_net/controller/access/control.py,sha256=nMGWzg60jNJMVAHIrism0_APzVMpbLAOcXG6mJuOSJ8,17332
|
13
|
-
pygpt_net/controller/access/voice.py,sha256=
|
13
|
+
pygpt_net/controller/access/voice.py,sha256=SJ6AdNOfvWWbegjLuNLrJTZ0K9uxcWx1Q47zVCXOsN8,15184
|
14
14
|
pygpt_net/controller/agent/__init__.py,sha256=bArPuyZOJujcf4l1GKMMQ7Wx--xBoFHXz1A3TDjNjAk,1267
|
15
15
|
pygpt_net/controller/agent/common.py,sha256=55CHhV-dsWeNe5QvdvNoyhEYVhQNrHt_Lv-VDTuiYRc,3871
|
16
16
|
pygpt_net/controller/agent/experts.py,sha256=qllzrjVNfDoueBc0nsQLaWlOY4wpUKENp4l9cjWz0BM,5830
|
@@ -124,7 +124,8 @@ pygpt_net/core/assistants/store.py,sha256=4zz8_10_f6o8gdRekEPo5Ox0tLwuZO8tKyVsz-
|
|
124
124
|
pygpt_net/core/attachments/__init__.py,sha256=bUqvfPqlpdXiGf3GvS1kTE45A0Q1Eo3kpUKypnRwDpk,12919
|
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
|
-
pygpt_net/core/audio/__init__.py,sha256=
|
127
|
+
pygpt_net/core/audio/__init__.py,sha256=uszH6pqMToDzL0WpPeUvVlyJ8RN4gFmQbsL4GFYMIdc,4521
|
128
|
+
pygpt_net/core/audio/capture.py,sha256=BBxXM7KxxCxcvN7cW0b5dsSi8BphiLzEfkIt8PfN77Y,10408
|
128
129
|
pygpt_net/core/audio/context.py,sha256=2XpXWhDC09iUvc0FRMq9BF2_rnQ60ZG4Js6LbO5MohY,1115
|
129
130
|
pygpt_net/core/audio/whisper.py,sha256=WZ_fNQ06s1NBxyoYB-lTFqDO6ARcnq9MZFekRaTNxTo,993
|
130
131
|
pygpt_net/core/bridge/__init__.py,sha256=ezS02_2wUrnV6eTF33wfob8rVWM5snfY92-PF_i15uQ,9568
|
@@ -246,9 +247,9 @@ pygpt_net/css_rc.py,sha256=i13kX7irhbYCWZ5yJbcMmnkFp_UfS4PYnvRFSPF7XXo,11349
|
|
246
247
|
pygpt_net/data/audio/click_off.mp3,sha256=aNiRDP1pt-Jy7ija4YKCNFBwvGWbzU460F4pZWZDS90,65201
|
247
248
|
pygpt_net/data/audio/click_on.mp3,sha256=qfdsSnthAEHVXzeyN4LlC0OvXuyW8p7stb7VXtlvZ1k,65201
|
248
249
|
pygpt_net/data/audio/ok.mp3,sha256=LTiV32pEBkpUGBkKkcOdOFB7Eyt_QoP2Nv6c5AaXftk,32256
|
249
|
-
pygpt_net/data/config/config.json,sha256=
|
250
|
-
pygpt_net/data/config/models.json,sha256=
|
251
|
-
pygpt_net/data/config/modes.json,sha256=
|
250
|
+
pygpt_net/data/config/config.json,sha256=ezfhWuQCpy1HWm69Pc_7eVdtese2vwheLC3tb62h6Mo,19735
|
251
|
+
pygpt_net/data/config/models.json,sha256=lhdi2Y9w3GxpRmpp07embuE4TVixvml9wbcoylMQA_8,61940
|
252
|
+
pygpt_net/data/config/modes.json,sha256=KILDDam2UjJ-dCj8_PF9AnTlj4Xq4Ms9HYxOq2Krx-M,1923
|
252
253
|
pygpt_net/data/config/presets/agent_openai.json,sha256=vMTR-soRBiEZrpJJHuFLWyx8a3Ez_BqtqjyXgxCAM_Q,733
|
253
254
|
pygpt_net/data/config/presets/agent_openai_assistant.json,sha256=awJw9lNTGpKML6SJUShVn7lv8AXh0oic7wBeyoN7AYs,798
|
254
255
|
pygpt_net/data/config/presets/agent_planner.json,sha256=a6Rv58Bnm2STNWB0Rw_dGhnsz6Lb3J8_GwsUVZaTIXc,742
|
@@ -1700,7 +1701,7 @@ pygpt_net/plugin/agent/__init__.py,sha256=GPbnpS9djcfzuPRxM2mu0hpUPRa51KtUR27Al6
|
|
1700
1701
|
pygpt_net/plugin/agent/config.py,sha256=V4M0boutzxFVWTQxM8UA7HgKUR4v_Y-5dX_XfeLzzL8,9792
|
1701
1702
|
pygpt_net/plugin/audio_input/__init__.py,sha256=vGwDtsPkwCEwiz2ePnzW48Tuhr0VHEc7kQua11VE7tI,15714
|
1702
1703
|
pygpt_net/plugin/audio_input/config.py,sha256=x57IVxBapJp9rwos327T6U0jTFSPeRJ6BorqfYxJ4u0,9197
|
1703
|
-
pygpt_net/plugin/audio_input/simple.py,sha256=
|
1704
|
+
pygpt_net/plugin/audio_input/simple.py,sha256=N5jL_hYp4ZvUBXqoiyx3Tt3b4bWKc9SbKOTtD00TNTE,5195
|
1704
1705
|
pygpt_net/plugin/audio_input/worker.py,sha256=kG7r08ot1h0Jyw_UQ_sFN1rOKOkOC-ByLbCp-oZZBr4,11828
|
1705
1706
|
pygpt_net/plugin/audio_output/__init__.py,sha256=wR_gBQgJ_arxHPSvjtT7naNwkNrCJc6KFR7ZUAqYcvc,7896
|
1706
1707
|
pygpt_net/plugin/audio_output/config.py,sha256=IA2K-9fQMZSwYGyi30Uh5qAlYwuqwaHo3dtDJ13vQdo,1208
|
@@ -2073,7 +2074,7 @@ pygpt_net/ui/widget/anims/loader.py,sha256=PzxHraeABUyMIZlg4Rk_tbJnUPmiwxlhdcHaC
|
|
2073
2074
|
pygpt_net/ui/widget/anims/toggles.py,sha256=_L2533IYyDkbnPCok9XBriIaKM5E9sHSznrwVWpKOOs,5755
|
2074
2075
|
pygpt_net/ui/widget/audio/__init__.py,sha256=8HT4tQFqQogEEpGYTv2RplKBthlsFKcl5egnv4lzzEw,488
|
2075
2076
|
pygpt_net/ui/widget/audio/input.py,sha256=t9VAhP15HkSOvNV2crI3Kg6AgrQDj-wSQiiYTMlvK60,1721
|
2076
|
-
pygpt_net/ui/widget/audio/input_button.py,sha256=
|
2077
|
+
pygpt_net/ui/widget/audio/input_button.py,sha256=ykhBIRZ0g2w2f5YxOMkxtOndLDbdW4G8x3UbP1rVKyk,5185
|
2077
2078
|
pygpt_net/ui/widget/audio/output.py,sha256=UxkiCnVT9DNFeByDGTFW_CK0LW8xSvhEK1zygtHvx4k,1586
|
2078
2079
|
pygpt_net/ui/widget/calendar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2079
2080
|
pygpt_net/ui/widget/calendar/select.py,sha256=vSEaQZmyeDhmyOfptWv6me_6IaYpQb5Jgc_mGDIe_4U,9051
|
@@ -2100,8 +2101,8 @@ pygpt_net/ui/widget/dialog/profile.py,sha256=vMYf-9c6HKAOqtSuw-HpBEUgmCSxEQuIrGl
|
|
2100
2101
|
pygpt_net/ui/widget/dialog/rename.py,sha256=HcImKH_gUHcyB1k5llwStmrlXvcSpxgR2V6IuEdMuCU,2191
|
2101
2102
|
pygpt_net/ui/widget/dialog/settings.py,sha256=fKzbme2tdxzTSiQMNnCEgyD3lwCzFjLi85ikWulisGw,1614
|
2102
2103
|
pygpt_net/ui/widget/dialog/settings_plugin.py,sha256=Kf1ZK_RY9CAnfeuzPoQ4wgsFb2yQl7X-VKzsYETA55o,1696
|
2103
|
-
pygpt_net/ui/widget/dialog/snap.py,sha256=
|
2104
|
-
pygpt_net/ui/widget/dialog/update.py,sha256=
|
2104
|
+
pygpt_net/ui/widget/dialog/snap.py,sha256=UKiHOywK1tw0HelSgsUaKXMlageDdzOH_TJot3lUVUg,2756
|
2105
|
+
pygpt_net/ui/widget/dialog/update.py,sha256=aAmk-unSaGie3VXMaXmorza-p9Isrm06d0iSYnfcq3U,6900
|
2105
2106
|
pygpt_net/ui/widget/dialog/url.py,sha256=7I17Pp9P2c3G1pODEY5dum_AF0nFnu2BMfbWTgEES-M,8765
|
2106
2107
|
pygpt_net/ui/widget/dialog/workdir.py,sha256=D-C3YIt-wCoI-Eh7z--Z4R6P1UvtpkxeiaVcI-ycFck,1523
|
2107
2108
|
pygpt_net/ui/widget/draw/__init__.py,sha256=oSYKtNEGNL0vDjn3wCgdnBAbxUqNGIEIf-75I2DIn7Q,488
|
@@ -2174,8 +2175,8 @@ pygpt_net/ui/widget/textarea/web.py,sha256=9FoL02QY6mOxtc4t4fe8X7fVDIdPn9Sb_fwsv
|
|
2174
2175
|
pygpt_net/ui/widget/vision/__init__.py,sha256=8HT4tQFqQogEEpGYTv2RplKBthlsFKcl5egnv4lzzEw,488
|
2175
2176
|
pygpt_net/ui/widget/vision/camera.py,sha256=T8b5cmK6uhf_WSSxzPt_Qod8JgMnst6q8sQqRvgQiSA,2584
|
2176
2177
|
pygpt_net/utils.py,sha256=Gsh_mITVke3bb8o-Ke57l__xA5a9Wv4t7tlsnSQULj8,6655
|
2177
|
-
pygpt_net-2.4.
|
2178
|
-
pygpt_net-2.4.
|
2179
|
-
pygpt_net-2.4.
|
2180
|
-
pygpt_net-2.4.
|
2181
|
-
pygpt_net-2.4.
|
2178
|
+
pygpt_net-2.4.50.dist-info/LICENSE,sha256=GLKQTnJOPK4dDIWfkAIM4GwOxKJXi5zcMGt7FjLR1xk,1126
|
2179
|
+
pygpt_net-2.4.50.dist-info/METADATA,sha256=kttDYqtQSS0wi5lXkI3Y7APenTEcXuIVcRiuspWa_7Q,169852
|
2180
|
+
pygpt_net-2.4.50.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
2181
|
+
pygpt_net-2.4.50.dist-info/entry_points.txt,sha256=qvpII6UHIt8XfokmQWnCYQrTgty8FeJ9hJvOuUFCN-8,43
|
2182
|
+
pygpt_net-2.4.50.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|