Skip to content

Commit c24f841

Browse files
authored
Merge pull request #175 from yjg30737/Dev
v1.6.0
2 parents 668ebb2 + a78d7dd commit c24f841

27 files changed

+532
-199
lines changed

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pyqt-openai"
7-
version = "1.5.0"
7+
version = "1.6.0"
88
description = "Python multipurpose chatbot that user can use GPT, other AI models altogether (Release Name: VividNode)"
99
authors = [{ name = "Jung Gyu Yoon", email = "yjg30737@gmail.com" }]
1010
license = { text = "MIT" }
@@ -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: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
# For the sake of following the PEP8 standard, we will declare module-level dunder names.
2424
# PEP8 standard about dunder names: https://peps.python.org/pep-0008/#module-level-dunder-names
2525

26-
__version__ = "1.5.0"
26+
__version__ = "1.6.0"
2727
__author__ = "Jung Gyu Yoon"
2828

2929
# Constants
@@ -37,6 +37,7 @@
3737
# For Windows
3838
AUTOSTART_REGISTRY_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run"
3939

40+
4041
# Check if the application is frozen (compiled with PyInstaller)
4142
# If this is main.py, it will return False, that means it is not frozen.
4243
def is_frozen():
@@ -81,6 +82,7 @@ def get_config_directory():
8182
# The default updater path (relative to the application's root directory) - For Windows
8283
UPDATER_PATH = os.path.join(UPDATE_DIR, "Updater.exe")
8384

85+
8486
# Move the Updater.exe to the config folder
8587
def move_updater():
8688
original_updater_path = os.path.join(ROOT_DIR, "Updater.exe")
@@ -199,6 +201,8 @@ def move_updater():
199201
ICON_PAYPAL = os.path.join(ICON_PATH, "paypal.png")
200202
ICON_KOFI = os.path.join(ICON_PATH, "kofi.png")
201203
ICON_PATREON = os.path.join(ICON_PATH, "patreon.svg")
204+
ICON_SHORTCUT = os.path.join(ICON_PATH, "shortcut.svg")
205+
ICON_REALTIME_API = os.path.join(ICON_PATH, "realtime_api.svg")
202206

203207
## CUSTOMIZE
204208
DEFAULT_ICON_SIZE = (24, 24)
@@ -243,7 +247,6 @@ def move_updater():
243247
DEFAULT_SHORTCUT_PROMPT_ENDING = "Ctrl+E"
244248
DEFAULT_SHORTCUT_SUPPORT_PROMPT_COMMAND = "Ctrl+Shift+P"
245249
DEFAULT_SHORTCUT_STACK_ON_TOP = "Ctrl+Shift+S"
246-
DEFAULT_SHORTCUT_SHOW_TOOLBAR = "Ctrl+T"
247250
DEFAULT_SHORTCUT_SHOW_SECONDARY_TOOLBAR = "Ctrl+Shift+T"
248251
DEFAULT_SHORTCUT_FOCUS_MODE = "F10"
249252
DEFAULT_SHORTCUT_FULL_SCREEN = "F11"
@@ -288,7 +291,6 @@ def move_updater():
288291
PROMPT_JSON_KEY_NAME = "prompt_json"
289292
PROMPT_MAIN_KEY_NAME = "prompt_main"
290293
PROMPT_END_KEY_NAME = "prompt_ending"
291-
PROMPT_NAME_REGEX = "^[a-zA-Z_0-9]+$"
292294
INDENT_SIZE = 4
293295
NOTIFIER_MAX_CHAR = 100
294296

@@ -355,8 +357,21 @@ def move_updater():
355357
WHISPER_TTS_VOICE_TYPE = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"]
356358
WHISPER_TTS_VOICE_SPEED_RANGE = 0.25, 4.0
357359
WHISPER_TTS_MODEL = "tts-1"
358-
WHISPER_TTS_DEFAULT_VOICE = "alloy"
359-
WHISPER_TTS_DEFAULT_SPEED = 1.0
360+
361+
## EDGE-TTS (TTS)
362+
EDGE_TTS_VOICE_TYPE = [
363+
"en-GB-SoniaNeural",
364+
"en-US-GuyNeural",
365+
"en-US-JennyNeural",
366+
"en-US-AvaMultilingualNeural",
367+
]
368+
369+
# TTS in general
370+
TTS_DEFAULT_PROVIDER = "OpenAI"
371+
TTS_DEFAULT_VOICE = "alloy"
372+
TTS_DEFAULT_SPEED = 1.0
373+
TTS_DEFAULT_AUTO_PLAY = False
374+
TTS_DEFAULT_AUTO_STOP_SILENCE_DURATION = 3
360375

361376
STT_MODEL = "whisper-1"
362377

@@ -413,6 +428,8 @@ def move_updater():
413428

414429
G4F_USE_CHAT_HISTORY = True
415430

431+
G4F_DEFAULT_IMAGE_MODEL = "flux"
432+
416433
# Dictionary that stores the platform and model pairs
417434
PROVIDER_MODEL_DICT = {
418435
"OpenAI": ["gpt-4o", "gpt-4o-mini"] + O1_MODELS,
@@ -628,7 +645,6 @@ def move_updater():
628645
"notify_finish": True,
629646
"temperature": 1,
630647
"max_tokens": -1,
631-
"show_toolbar": True,
632648
"show_secondary_toolbar": True,
633649
"top_p": 1,
634650
"chat_column_to_show": ["id", "name", "insert_dt", "update_dt"],
@@ -662,16 +678,21 @@ def move_updater():
662678
"llama_index_directory": "",
663679
"apply_user_defined_styles": False,
664680
"focus_mode": False,
665-
"voice": WHISPER_TTS_DEFAULT_VOICE,
666-
"voice_speed": WHISPER_TTS_DEFAULT_SPEED,
667681
"OPENAI_API_KEY": "",
668682
"GEMINI_API_KEY": "",
669683
"CLAUDE_API_KEY": "",
670684
"LLAMA_API_KEY": "",
685+
"show_realtime_api": False,
671686
# G4F
672687
"g4f_model": DEFAULT_LLM,
673688
"provider": G4F_PROVIDER_DEFAULT,
674689
"g4f_use_chat_history": G4F_USE_CHAT_HISTORY,
690+
# STT and TTS settings
691+
"voice_provider": TTS_DEFAULT_PROVIDER,
692+
"voice": TTS_DEFAULT_VOICE,
693+
"voice_speed": TTS_DEFAULT_SPEED,
694+
"auto_play_voice": TTS_DEFAULT_AUTO_PLAY,
695+
"auto_stop_silence_duration": TTS_DEFAULT_AUTO_STOP_SILENCE_DURATION,
675696
},
676697
"DALLE": {
677698
"quality": "standard",
@@ -682,7 +703,6 @@ def move_updater():
682703
"width": 1024,
683704
"height": 1024,
684705
"prompt_type": 1,
685-
686706
"show_history": True,
687707
"show_setting": True,
688708
"prompt": "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
@@ -698,7 +718,6 @@ def move_updater():
698718
"model": "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b",
699719
"width": 768,
700720
"height": 768,
701-
702721
"show_history": True,
703722
"show_setting": True,
704723
"prompt": "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
@@ -708,12 +727,11 @@ def move_updater():
708727
"number_of_images_to_create": 2,
709728
"save_prompt_as_text": True,
710729
"show_prompt_on_image": False,
711-
712730
"negative_prompt": "ugly, deformed, noisy, blurry, distorted",
713731
},
714732
"G4F_IMAGE": {
715-
"model": "flux",
716-
733+
"model": G4F_DEFAULT_IMAGE_MODEL,
734+
"provider": G4F_PROVIDER_DEFAULT,
717735
"show_history": True,
718736
"show_setting": True,
719737
"prompt": "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
@@ -723,11 +741,11 @@ def move_updater():
723741
"number_of_images_to_create": 2,
724742
"save_prompt_as_text": True,
725743
"show_prompt_on_image": False,
726-
727744
"negative_prompt": "ugly, deformed, noisy, blurry, distorted",
728-
}
745+
},
729746
}
730747

748+
731749
# Dynamically add the API keys to the configuration data
732750
def update_general_config_with_api_keys(config_data, api_configs):
733751
for config in api_configs:
@@ -737,7 +755,7 @@ def update_general_config_with_api_keys(config_data, api_configs):
737755
update_general_config_with_api_keys(CONFIG_DATA, DEFAULT_API_CONFIGS)
738756

739757
# Set the default llama index cache directory for preventing any issues such as PermissionError
740-
os.environ['NLTK_DATA'] = os.path.join(get_config_directory(), "llama_index_cache")
758+
os.environ["NLTK_DATA"] = os.path.join(get_config_directory(), "llama_index_cache")
741759

742760
# Update the __all__ list with the PEP8 standard dunder names
743761
__all__ = ["__version__", "__author__"]

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

0 commit comments

Comments
 (0)