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