Skip to content

Commit 549783f

Browse files
authored
Merge pull request #174 from yjg30737/feature/general
v1.6.0
2 parents 668ebb2 + f69e022 commit 549783f

24 files changed

+441
-174
lines changed

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies = [
1616
"requests",
1717
"pyaudio",
1818
"pillow",
19+
"psutil",
1920

2021
"openai",
2122
"anthropic",
@@ -24,7 +25,9 @@ dependencies = [
2425
"replicate",
2526

2627
"g4f",
27-
"curl_cffi"
28+
"curl_cffi",
29+
30+
"edge-tts"
2831
]
2932
keywords = ['openai', 'pyqt', 'pyqt5', 'pyqt6', 'pyside6', 'desktop', 'app', 'chatbot', 'gpt', 'replicate', 'gemini', 'claude', 'llama', 'llm', 'gpt4free']
3033

pyqt_openai/__init__.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ def move_updater():
199199
ICON_PAYPAL = os.path.join(ICON_PATH, "paypal.png")
200200
ICON_KOFI = os.path.join(ICON_PATH, "kofi.png")
201201
ICON_PATREON = os.path.join(ICON_PATH, "patreon.svg")
202+
ICON_SHORTCUT = os.path.join(ICON_PATH, "shortcut.svg")
203+
ICON_REALTIME_API = os.path.join(ICON_PATH, "realtime_api.svg")
202204

203205
## CUSTOMIZE
204206
DEFAULT_ICON_SIZE = (24, 24)
@@ -243,7 +245,6 @@ def move_updater():
243245
DEFAULT_SHORTCUT_PROMPT_ENDING = "Ctrl+E"
244246
DEFAULT_SHORTCUT_SUPPORT_PROMPT_COMMAND = "Ctrl+Shift+P"
245247
DEFAULT_SHORTCUT_STACK_ON_TOP = "Ctrl+Shift+S"
246-
DEFAULT_SHORTCUT_SHOW_TOOLBAR = "Ctrl+T"
247248
DEFAULT_SHORTCUT_SHOW_SECONDARY_TOOLBAR = "Ctrl+Shift+T"
248249
DEFAULT_SHORTCUT_FOCUS_MODE = "F10"
249250
DEFAULT_SHORTCUT_FULL_SCREEN = "F11"
@@ -288,7 +289,6 @@ def move_updater():
288289
PROMPT_JSON_KEY_NAME = "prompt_json"
289290
PROMPT_MAIN_KEY_NAME = "prompt_main"
290291
PROMPT_END_KEY_NAME = "prompt_ending"
291-
PROMPT_NAME_REGEX = "^[a-zA-Z_0-9]+$"
292292
INDENT_SIZE = 4
293293
NOTIFIER_MAX_CHAR = 100
294294

@@ -355,8 +355,16 @@ def move_updater():
355355
WHISPER_TTS_VOICE_TYPE = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"]
356356
WHISPER_TTS_VOICE_SPEED_RANGE = 0.25, 4.0
357357
WHISPER_TTS_MODEL = "tts-1"
358-
WHISPER_TTS_DEFAULT_VOICE = "alloy"
359-
WHISPER_TTS_DEFAULT_SPEED = 1.0
358+
359+
## EDGE-TTS (TTS)
360+
EDGE_TTS_VOICE_TYPE = ["en-GB-SoniaNeural", "en-US-GuyNeural", "en-US-JennyNeural", "en-US-AvaMultilingualNeural"]
361+
362+
# TTS in general
363+
TTS_DEFAULT_PROVIDER = "OpenAI"
364+
TTS_DEFAULT_VOICE = "alloy"
365+
TTS_DEFAULT_SPEED = 1.0
366+
TTS_DEFAULT_AUTO_PLAY = False
367+
TTS_DEFAULT_AUTO_STOP_SILENCE_DURATION = 3
360368

361369
STT_MODEL = "whisper-1"
362370

@@ -413,6 +421,8 @@ def move_updater():
413421

414422
G4F_USE_CHAT_HISTORY = True
415423

424+
G4F_DEFAULT_IMAGE_MODEL = "flux"
425+
416426
# Dictionary that stores the platform and model pairs
417427
PROVIDER_MODEL_DICT = {
418428
"OpenAI": ["gpt-4o", "gpt-4o-mini"] + O1_MODELS,
@@ -628,7 +638,6 @@ def move_updater():
628638
"notify_finish": True,
629639
"temperature": 1,
630640
"max_tokens": -1,
631-
"show_toolbar": True,
632641
"show_secondary_toolbar": True,
633642
"top_p": 1,
634643
"chat_column_to_show": ["id", "name", "insert_dt", "update_dt"],
@@ -662,16 +671,23 @@ def move_updater():
662671
"llama_index_directory": "",
663672
"apply_user_defined_styles": False,
664673
"focus_mode": False,
665-
"voice": WHISPER_TTS_DEFAULT_VOICE,
666-
"voice_speed": WHISPER_TTS_DEFAULT_SPEED,
667674
"OPENAI_API_KEY": "",
668675
"GEMINI_API_KEY": "",
669676
"CLAUDE_API_KEY": "",
670677
"LLAMA_API_KEY": "",
678+
'show_realtime_api': False,
679+
671680
# G4F
672681
"g4f_model": DEFAULT_LLM,
673682
"provider": G4F_PROVIDER_DEFAULT,
674683
"g4f_use_chat_history": G4F_USE_CHAT_HISTORY,
684+
685+
# STT and TTS settings
686+
"voice_provider": TTS_DEFAULT_PROVIDER,
687+
"voice": TTS_DEFAULT_VOICE,
688+
"voice_speed": TTS_DEFAULT_SPEED,
689+
"auto_play_voice": TTS_DEFAULT_AUTO_PLAY,
690+
"auto_stop_silence_duration": TTS_DEFAULT_AUTO_STOP_SILENCE_DURATION
675691
},
676692
"DALLE": {
677693
"quality": "standard",
@@ -712,7 +728,8 @@ def move_updater():
712728
"negative_prompt": "ugly, deformed, noisy, blurry, distorted",
713729
},
714730
"G4F_IMAGE": {
715-
"model": "flux",
731+
"model": G4F_DEFAULT_IMAGE_MODEL,
732+
"provider": G4F_PROVIDER_DEFAULT,
716733

717734
"show_history": True,
718735
"show_setting": True,

pyqt_openai/chat_widget/center/aiChatUnit.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,15 @@ def __speak(self, f):
110110
if f:
111111
text = self._lbl.toPlainText()
112112
if text:
113+
voice_provider = CONFIG_MANAGER.get_general_property("voice_provider")
114+
113115
args = {
114116
"model": WHISPER_TTS_MODEL,
115117
"voice": CONFIG_MANAGER.get_general_property("voice"),
116118
"input": text,
117119
"speed": CONFIG_MANAGER.get_general_property("voice_speed"),
118120
}
119-
self.thread = stream_to_speakers(args)
121+
self.thread = stream_to_speakers(voice_provider, args)
120122
self.thread.finished.connect(self.__on_thread_complete)
121123
self.thread.errorGenerated.connect(
122124
lambda x: QMessageBox.critical(self, "Error", x)

pyqt_openai/chat_widget/center/commandSuggestionWidget.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
from PySide6.QtCore import Signal
12
from PySide6.QtWidgets import QListWidget, QWidget, QVBoxLayout, QLabel
23

34
from pyqt_openai.lang.translations import LangClass
45

56

67
class CommandSuggestionWidget(QWidget):
8+
toggleCommandSuggestion = Signal(bool)
9+
710
def __init__(self, parent=None):
811
super().__init__(parent)
912
self.__initUi()
@@ -29,3 +32,9 @@ def onCountChanged(self):
2932
scrollbarHeight = self.__commandList.verticalScrollBar().sizeHint().height()
3033
totalHeight = contentHeight + itemHeight
3134
self.setMaximumHeight(totalHeight + scrollbarHeight)
35+
36+
def showEvent(self, event):
37+
self.toggleCommandSuggestion.emit(True)
38+
39+
def hideEvent(self, event):
40+
self.toggleCommandSuggestion.emit(False)

pyqt_openai/chat_widget/center/prompt.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
STTThread,
4848
)
4949
from pyqt_openai.widgets.button import Button
50+
from pyqt_openai.widgets.jsonEditor import JSONEditor
5051
from pyqt_openai.widgets.toolButton import ToolButton
5152

5253

@@ -86,6 +87,9 @@ def __initUi(self):
8687

8788
# Create the command suggestion list
8889
self.__suggestionWidget = CommandSuggestionWidget()
90+
self.__suggestionWidget.toggleCommandSuggestion.connect(
91+
self.__setCommandSuggestionEnabled
92+
)
8993
self.__suggestion_list = self.__suggestionWidget.getCommandList()
9094

9195
self.__uploadedImageFileWidget = UploadedImageFileWidget()
@@ -246,6 +250,13 @@ def __getEveryPromptCommands(self, get_name_only=False):
246250
return [obj["name"] for obj in self.__p_grp]
247251
return self.__p_grp
248252

253+
def __setCommandSuggestionEnabled(self, f):
254+
for w in self.__textEditGroup.getGroup().values():
255+
if isinstance(w, JSONEditor):
256+
pass
257+
else:
258+
w.setCommandSuggestionEnabled(f)
259+
249260
def __updateSuggestions(self):
250261
w = self.__textEditGroup.getCurrentTextEdit()
251262
if w and self.__commandEnabled:
@@ -256,7 +267,6 @@ def __updateSuggestions(self):
256267
input_text_chunk = input_text_chunk[-1]
257268
starts_with_f = input_text_chunk.startswith("/")
258269
self.__suggestionWidget.setVisible(starts_with_f)
259-
w.setCommandSuggestionEnabled(starts_with_f)
260270
if starts_with_f:
261271
command_word = input_text_chunk[1:]
262272
# Set every prompt commands first
@@ -299,7 +309,7 @@ def showWidgetInPromptDuringResponse(self, f):
299309
self.__controlWidgetDuringGeneration.setVisible(f)
300310

301311
def executeCommand(self, item):
302-
self.__textEditGroup.executeCommand(item, self.__p_grp)
312+
self.__textEditGroup.showPromptContent(item, self.__p_grp)
303313

304314
def updateHeight(self):
305315
overallHeight = self.__textEditGroup.adjustHeight()
@@ -338,7 +348,6 @@ def __showEnding(self, f):
338348

339349
def __supportPromptCommand(self, f):
340350
self.__commandEnabled = f
341-
self.__textEditGroup.setCommandEnabled(f)
342351

343352
def __readingFiles(self):
344353
filenames = QFileDialog.getOpenFileNames(
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel
2+
from PySide6.QtGui import QFont
3+
from PySide6.QtCore import Qt
4+
5+
from pyqt_openai import LARGE_LABEL_PARAM
6+
7+
8+
class RealtimeApiWidget(QWidget):
9+
def __init__(self, parent=None):
10+
super().__init__(parent)
11+
self.initUI()
12+
13+
def initUI(self):
14+
lay = QVBoxLayout()
15+
16+
title = QLabel('Coming Soon...', self)
17+
title.setFont(QFont(*LARGE_LABEL_PARAM))
18+
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
19+
20+
lay.addWidget(title)
21+
22+
self.setLayout(lay)

pyqt_openai/chat_widget/center/textEditPromptGroup.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ def __initUi(self):
5050
lay = QVBoxLayout()
5151
for w in self.__textGroup.values():
5252
# Connect every group text edit widget to the signal
53-
w.textChanged.connect(self.onUpdateSuggestion)
53+
if isinstance(w, JSONEditor):
54+
pass
55+
else:
56+
w.textChanged.connect(self.onUpdateSuggestion)
5457
w.textChanged.connect(self.textChanged)
5558
w.moveCursorToOtherPrompt.connect(self.__moveCursorToOtherPrompt)
5659

@@ -70,7 +73,7 @@ def __initUi(self):
7073
self.__textGroup[PROMPT_MAIN_KEY_NAME].installEventFilter(self)
7174
self.__textGroup[PROMPT_MAIN_KEY_NAME].handleDrop.connect(self.handleDrop)
7275

73-
def executeCommand(self, item, grp):
76+
def showPromptContent(self, item, grp):
7477
command_key = item.text()
7578
command = ""
7679
for i in range(len(grp)):
@@ -81,7 +84,7 @@ def executeCommand(self, item, grp):
8184
if w:
8285
cursor = w.textCursor()
8386
cursor.deletePreviousChar()
84-
cursor.select(QTextCursor.WordUnderCursor)
87+
cursor.select(QTextCursor.SelectionType.WordUnderCursor)
8588
w.setTextCursor(cursor)
8689
w.insertPlainText(command)
8790

@@ -92,11 +95,6 @@ def getCurrentTextEdit(self):
9295
if w.hasFocus():
9396
return w
9497

95-
def setCommandEnabled(self, f: bool):
96-
for w in self.__textGroup.values():
97-
if isinstance(w, TextEditPrompt):
98-
w.setCommandSuggestionEnabled(f)
99-
10098
def adjustHeight(self) -> int:
10199
"""
102100
Adjust overall height of text edit group based on their contents and return adjusted height

pyqt_openai/chat_widget/chatMainWidget.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
QSplitter,
99
QFileDialog,
1010
QMessageBox,
11-
QPushButton,
11+
QPushButton, QStackedWidget,
1212
)
1313

1414
from pyqt_openai import (
@@ -22,8 +22,9 @@
2222
DEFAULT_SHORTCUT_LEFT_SIDEBAR_WINDOW,
2323
DEFAULT_SHORTCUT_CONTROL_PROMPT_WINDOW,
2424
DEFAULT_SHORTCUT_RIGHT_SIDEBAR_WINDOW,
25-
QFILEDIALOG_DEFAULT_DIRECTORY,
25+
QFILEDIALOG_DEFAULT_DIRECTORY, ICON_REALTIME_API,
2626
)
27+
from pyqt_openai.chat_widget.center.realtimeApiWidget import RealtimeApiWidget
2728
from pyqt_openai.config_loader import CONFIG_MANAGER
2829
from pyqt_openai.chat_widget.center.chatWidget import ChatWidget
2930
from pyqt_openai.chat_widget.left_sidebar.chatNavWidget import ChatNavWidget
@@ -61,6 +62,7 @@ def __initVal(self):
6162
self.__notify_finish = CONFIG_MANAGER.get_general_property("notify_finish")
6263

6364
self.__show_chat_list = CONFIG_MANAGER.get_general_property("show_chat_list")
65+
self.__show_realtime_api = CONFIG_MANAGER.get_general_property("show_realtime_api")
6466
self.__show_setting = CONFIG_MANAGER.get_general_property("show_setting")
6567
self.__show_prompt = CONFIG_MANAGER.get_general_property("show_prompt")
6668

@@ -83,6 +85,8 @@ def __initUi(self):
8385
self.__chatWidget.addThread.connect(self.__addThread)
8486
self.__chatWidget.onMenuCloseClicked.connect(self.__onMenuCloseClicked)
8587

88+
self.__realtimeApiWidget = RealtimeApiWidget()
89+
8690
self.__browser = self.__chatWidget.getChatBrowser()
8791

8892
self.__chatRightSideBarWidget = ChatRightSideBarWidget()
@@ -112,6 +116,15 @@ def __initUi(self):
112116
self.__sideBarBtn.toggled.connect(self.toggleSideBar)
113117
self.__sideBarBtn.setShortcut(DEFAULT_SHORTCUT_LEFT_SIDEBAR_WINDOW)
114118

119+
self.__useRealtimeApiBtn = Button()
120+
self.__useRealtimeApiBtn.setStyleAndIcon(ICON_REALTIME_API)
121+
self.__useRealtimeApiBtn.setToolTip(
122+
LangClass.TRANSLATIONS["Use Realtime API"]
123+
)
124+
self.__useRealtimeApiBtn.setCheckable(True)
125+
self.__useRealtimeApiBtn.setChecked(self.__show_realtime_api)
126+
self.__useRealtimeApiBtn.toggled.connect(self.toggleRealtimeApiScreen)
127+
115128
self.__settingBtn = Button()
116129
self.__settingBtn.setStyleAndIcon(ICON_SETTING)
117130
self.__settingBtn.setToolTip(
@@ -146,6 +159,7 @@ def __initUi(self):
146159

147160
lay = QHBoxLayout()
148161
lay.addWidget(self.__sideBarBtn)
162+
lay.addWidget(self.__useRealtimeApiBtn)
149163
lay.addWidget(self.__settingBtn)
150164
lay.addWidget(self.__promptBtn)
151165
lay.addWidget(sep)
@@ -181,9 +195,14 @@ def __initUi(self):
181195
"""
182196
)
183197

198+
self.__centerWidget = QStackedWidget()
199+
self.__centerWidget.addWidget(self.__chatWidget)
200+
self.__centerWidget.addWidget(self.__realtimeApiWidget)
201+
self.__centerWidget.setCurrentIndex(1 if self.__show_realtime_api else 0)
202+
184203
mainWidget = QSplitter()
185204
mainWidget.addWidget(self.__chatNavWidget)
186-
mainWidget.addWidget(self.__chatWidget)
205+
mainWidget.addWidget(self.__centerWidget)
187206
mainWidget.addWidget(self.__rightSideBar)
188207
mainWidget.setSizes([100, 500, 400])
189208
mainWidget.setChildrenCollapsible(False)
@@ -221,6 +240,11 @@ def toggleSideBar(self, x):
221240
self.__show_chat_list = x
222241
CONFIG_MANAGER.set_general_property("show_chat_list", self.__show_chat_list)
223242

243+
def toggleRealtimeApiScreen(self, x):
244+
self.__centerWidget.setCurrentIndex(1 if x else 0)
245+
self.__show_realtime_api = x
246+
CONFIG_MANAGER.set_general_property("show_realtime_api", self.__show_realtime_api)
247+
224248
def toggleSetting(self, x):
225249
self.__chatRightSideBarWidget.setVisible(x)
226250
self.__show_setting = x

pyqt_openai/chat_widget/prompt_gen_widget/promptEntryDirectInputDialog.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,9 @@ def __initUi(self):
2929

3030
self.__name = QLineEdit()
3131
self.__name.setPlaceholderText(LangClass.TRANSLATIONS["Name"])
32-
self.__name.textChanged.connect(
33-
lambda x: self.__okBtn.setEnabled(x.strip() != "")
34-
)
3532

3633
self.__content = QPlainTextEdit()
3734
self.__content.setPlaceholderText(LangClass.TRANSLATIONS["Content"])
38-
self.__content.textChanged.connect(
39-
lambda x: self.__name.text().strip() != ""
40-
and self.__okBtn.setEnabled(x.strip() != "")
41-
)
4235

4336
sep = getSeparator("horizontal")
4437

@@ -68,6 +61,9 @@ def __initUi(self):
6861
def getPromptName(self):
6962
return self.__name.text()
7063

64+
def getPromptContent(self):
65+
return self.__content.toPlainText()
66+
7167
def __accept(self):
7268
exists_f = is_prompt_entry_name_valid(self.__group_id, self.__name.text())
7369
if exists_f:

0 commit comments

Comments
 (0)