Skip to content

Commit d2f3488

Browse files
authored
Merge pull request #154 from yjg30737/Dev
v1.3.0
2 parents d628eaf + ea1c729 commit d2f3488

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+591
-456
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div align="center">
33
<img src="https://github.com/user-attachments/assets/ab169535-8af0-40c7-848d-59a7e5e4b304"/>
44

5-
<b>A cross-platform desktop app for LLM such as GPT, Claude, Gemini, Llama chatbot interaction and image generation, offering customizable features, local chat history, and enhanced performance—no browser required!</b>
5+
<b>A cross-platform AI desktop chatbot application for LLM such as GPT, Claude, Gemini, Llama chatbot interaction and image generation, offering customizable features, local chat history, and enhanced performance—no browser required!</b>
66

77
<hr>
88

pyproject.toml

Lines changed: 4 additions & 1 deletion
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.2.0"
7+
version = "1.3.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 = "[email protected]" }]
1010
license = { text = "MIT" }
@@ -21,6 +21,9 @@ dependencies = [
2121
"google-generativeai",
2222
"llama-index",
2323
"replicate",
24+
25+
"g4f",
26+
"curl_cffi"
2427
]
2528
keywords = ['openai', 'pyqt', 'pyqt5', 'pyqt6', 'pyside6', 'desktop', 'app', 'chatbot', 'gpt', 'replicate', 'gemini', 'claude', 'llama', 'llm']
2629

pyqt_openai/__init__.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ def move_updater():
9797
TRANSPARENT_RANGE = 20, 100
9898
TRANSPARENT_INIT_VAL = 100
9999

100+
LARGE_LABEL_PARAM = ('Arial', 32)
101+
MEDIUM_LABEL_PARAM = ('Arial', 16)
102+
SMALL_LABEL_PARAM = ('Arial', 10)
103+
100104
LICENSE = pyproject_data["project"]["license"]['text']
101105
LICENSE_URL = 'https://github.com/yjg30737/pyqt-openai/blob/main/LICENSE'
102106
KOFI_URL = 'https://ko-fi.com/junggyuyoon'
@@ -109,7 +113,7 @@ def move_updater():
109113
HOW_TO_GET_OPENAI_API_KEY_URL = 'https://medium.com/@yjg30737/how-to-get-your-openai-api-key-e2193850932e'
110114
HOW_TO_EXPORT_CHATGPT_CONVERSATION_HISTORY_URL = 'https://medium.com/@yjg30737/how-to-export-your-chatgpt-conversation-history-caa0946d6349'
111115
HOW_TO_REPLICATE = 'https://medium.com/@yjg30737/10a2cb983ceb'
112-
HOW_TO_GET_CLAUDE_API_KEY_URL = 'https://medium.com/@yjg30737/e771e42c182a'
116+
HOW_TO_GET_CLAUDE_API_KEY_URL = 'https://medium.com/@yjg30737/how-to-get-started-with-claude-api-step-by-step-guide-92b5e35ae0a0'
113117
HOW_TO_GET_GEMINI_API_KEY_URL = 'https://medium.com/@yjg30737/e61a84d64c69'
114118
HOW_TO_GET_LLAMA_API_KEY_URL = 'https://medium.com/@yjg30737/0dc1900e3606'
115119

@@ -335,7 +339,7 @@ def move_updater():
335339
# Endpoint
336340
# https://platform.openai.com/docs/models/model-endpoint-compatibility
337341
OPENAI_ENDPOINT_DICT = {
338-
'/v1/chat/completions': ['gpt-4o', 'gpt-4o-mini'],
342+
'/v1/chat/completions': ['gpt-4o', 'gpt-4o-mini', 'o1-preview', 'o1-mini'],
339343
'/v1/completions': [
340344
'text-davinci-003', 'text-davinci-002', 'text-curie-001', 'text-babbage-001', 'text-ada-001', 'davinci',
341345
'curie', 'babbage', 'ada'
@@ -381,9 +385,16 @@ def move_updater():
381385
}
382386
]
383387

388+
# This has to be managed separately since some of the arguments are different with usual models
389+
O1_MODELS = ['o1-preview', 'o1-mini']
390+
391+
DEFAULT_LLM = 'gpt-4o'
392+
393+
G4F_PROVIDER_DEFAULT = 'Auto'
394+
384395
# Dictionary that stores the platform and model pairs
385-
PLATFORM_MODEL_DICT = {
386-
'OpenAI': ['gpt-4o', 'gpt-4o-mini'],
396+
PROVIDER_MODEL_DICT = {
397+
'OpenAI': ['gpt-4o', 'gpt-4o-mini']+O1_MODELS,
387398
'Gemini': ['gemini-1.5-flash'],
388399
'Claude': ['claude-3-5-sonnet-20240620'],
389400
'Llama': ['llama3-70b']
@@ -483,7 +494,7 @@ def move_updater():
483494
'show_chat_list': True,
484495
'stream': True,
485496
'db': 'conv',
486-
'model': 'gpt-4o',
497+
'model': DEFAULT_LLM,
487498
'show_setting': True,
488499
'use_llama_index': False,
489500
'do_not_ask_again': False,

pyqt_openai/aboutDialog.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __initUi(self):
4242
descWidget3.setText(f'''
4343
<br/><br/>Contact: {CONTACT}<br/>
4444
<p>Powered by PySide6</p>
45+
<p>Powered by GPT4Free</p>
4546
''')
4647

4748
descWidget1.setAlignment(Qt.AlignmentFlag.AlignTop)

pyqt_openai/gpt_widget/center/aiChatUnit.py renamed to pyqt_openai/chat_widget/center/aiChatUnit.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
from pyqt_openai import ICON_FAVORITE_NO, ICON_INFO, ICON_FAVORITE_YES, ICON_SPEAKER, WHISPER_TTS_MODEL
55
from pyqt_openai.config_loader import CONFIG_MANAGER
6-
from pyqt_openai.gpt_widget.center.chatUnit import ChatUnit
7-
from pyqt_openai.gpt_widget.center.responseInfoDialog import ResponseInfoDialog
6+
from pyqt_openai.chat_widget.center.chatUnit import ChatUnit
7+
from pyqt_openai.chat_widget.center.responseInfoDialog import ResponseInfoDialog
88
from pyqt_openai.models import ChatMessageContainer
99
from pyqt_openai.globals import DB, stream_to_speakers
1010
from pyqt_openai.widgets.button import Button

pyqt_openai/gpt_widget/center/chatBrowser.py renamed to pyqt_openai/chat_widget/center/chatBrowser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from PySide6.QtWidgets import QScrollArea, QVBoxLayout, QWidget, QLabel
77

88
from pyqt_openai import MAXIMUM_MESSAGES_IN_PARAMETER, DEFAULT_FOUND_TEXT_BG_COLOR, DEFAULT_FOUND_TEXT_COLOR
9-
from pyqt_openai.gpt_widget.center.aiChatUnit import AIChatUnit
10-
from pyqt_openai.gpt_widget.center.userChatUnit import UserChatUnit
9+
from pyqt_openai.chat_widget.center.aiChatUnit import AIChatUnit
10+
from pyqt_openai.chat_widget.center.userChatUnit import UserChatUnit
1111
from pyqt_openai.models import ChatMessageContainer
1212
from pyqt_openai.globals import DB
1313
from pyqt_openai.util.script import is_valid_regex

pyqt_openai/gpt_widget/center/gptHome.py renamed to pyqt_openai/chat_widget/center/chatHome.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,38 @@
44
from PySide6.QtGui import QFont, QPixmap
55
from PySide6.QtWidgets import QLabel, QWidget, QVBoxLayout, QScrollArea
66

7-
from pyqt_openai import DEFAULT_APP_NAME, HOW_TO_GET_OPENAI_API_KEY_URL
7+
from pyqt_openai import DEFAULT_APP_NAME, HOW_TO_GET_OPENAI_API_KEY_URL, LARGE_LABEL_PARAM, MEDIUM_LABEL_PARAM
88
from pyqt_openai.lang.translations import LangClass
99
from pyqt_openai.widgets.linkLabel import LinkLabel
1010

1111

12-
class GPTHome(QScrollArea):
12+
class ChatHome(QScrollArea):
1313
def __init__(self, parent=None):
1414
super().__init__(parent)
1515
self.__initUi()
1616

1717
def __initUi(self):
18-
title = QLabel(f"Welcome to {DEFAULT_APP_NAME}\n"
19-
f"main page!", self)
20-
title.setFont(QFont('Arial', 32))
18+
title = QLabel(f"Welcome to {DEFAULT_APP_NAME}!", self)
19+
title.setFont(QFont(*LARGE_LABEL_PARAM))
2120
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
2221

2322
description = QLabel(LangClass.TRANSLATIONS['Enjoy convenient chatting, all day long!'])
2423

2524
self.__quickStartManualLbl = LinkLabel()
2625
self.__quickStartManualLbl.setText(LangClass.TRANSLATIONS['Quick Start Manual'])
2726
self.__quickStartManualLbl.setUrl(HOW_TO_GET_OPENAI_API_KEY_URL)
28-
self.__quickStartManualLbl.setFont(QFont('Arial', 16))
27+
self.__quickStartManualLbl.setFont(QFont(*MEDIUM_LABEL_PARAM))
2928
self.__quickStartManualLbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
3029

3130
self.__openaiApiManualLbl = LinkLabel()
3231
self.__openaiApiManualLbl.setText(LangClass.TRANSLATIONS['How to get OpenAI API Key?'])
3332
self.__openaiApiManualLbl.setUrl(HOW_TO_GET_OPENAI_API_KEY_URL)
34-
self.__openaiApiManualLbl.setFont(QFont('Arial', 16))
33+
self.__openaiApiManualLbl.setFont(QFont(*MEDIUM_LABEL_PARAM))
3534
self.__openaiApiManualLbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
3635

3736
self.__background_image = QLabel()
3837

39-
description.setFont(QFont('Arial', 16))
38+
description.setFont(QFont(*MEDIUM_LABEL_PARAM))
4039
description.setAlignment(Qt.AlignmentFlag.AlignCenter)
4140

4241
lay = QVBoxLayout()

pyqt_openai/gpt_widget/center/chatUnit.py renamed to pyqt_openai/chat_widget/center/chatUnit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy
55

66
from pyqt_openai import DEFAULT_ICON_SIZE, ICON_COPY
7-
from pyqt_openai.gpt_widget.center.messageTextBrowser import MessageTextBrowser
7+
from pyqt_openai.chat_widget.center.messageTextBrowser import MessageTextBrowser
88
from pyqt_openai.widgets.button import Button
99
from pyqt_openai.widgets.circleProfileImage import RoundedImage
1010

pyqt_openai/gpt_widget/center/chatWidget.py renamed to pyqt_openai/chat_widget/center/chatWidget.py

Lines changed: 56 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77
from pyqt_openai.config_loader import CONFIG_MANAGER
88
from pyqt_openai.globals import LLAMAINDEX_WRAPPER, DB, get_openai_chat_model, get_argument, ChatThread
9-
from pyqt_openai.gpt_widget.center.chatBrowser import ChatBrowser
10-
from pyqt_openai.gpt_widget.center.gptHome import GPTHome
11-
from pyqt_openai.gpt_widget.center.menuWidget import MenuWidget
12-
from pyqt_openai.gpt_widget.center.prompt import Prompt
13-
from pyqt_openai.gpt_widget.gptThread import LlamaOpenAIThread, GPTThread
9+
from pyqt_openai.chat_widget.center.chatBrowser import ChatBrowser
10+
from pyqt_openai.chat_widget.center.chatHome import ChatHome
11+
from pyqt_openai.chat_widget.center.menuWidget import MenuWidget
12+
from pyqt_openai.chat_widget.center.prompt import Prompt
13+
from pyqt_openai.chat_widget.chatThread import LlamaOpenAIThread
1414
from pyqt_openai.lang.translations import LangClass
1515
from pyqt_openai.models import ChatMessageContainer
1616
from pyqt_openai.widgets.notifier import NotifierWidget
@@ -28,14 +28,15 @@ def __init__(self, parent=None):
2828
def __initVal(self):
2929
self.__cur_id = 0
3030
self.__notify_finish = CONFIG_MANAGER.get_general_property('notify_finish')
31+
self.__is_g4f = False
3132

3233
def __initUi(self):
3334
# Main widget
3435
# This contains home page (at the beginning of the stack) and
3536
# widget for main view
3637
self.__mainWidget = QStackedWidget()
3738

38-
self.__homePage = GPTHome()
39+
self.__homePage = ChatHome()
3940
self.__browser = ChatBrowser()
4041
self.__browser.onReplacedCurrentPage.connect(self.__mainWidget.setCurrentIndex)
4142

@@ -158,90 +159,54 @@ def __chat(self):
158159
QMessageBox.critical(self, LangClass.TRANSLATIONS["Error"], f'{LangClass.TRANSLATIONS["JSON content is not valid. Please check the JSON content field."]}\n\n{e}')
159160
return
160161

161-
# Get parameters for OpenAI
162-
if model in get_openai_chat_model():
163-
param = get_argument(model, system, messages, cur_text, temperature, top_p, frequency_penalty, presence_penalty, stream,
164-
use_max_tokens, max_tokens,
165-
images,
166-
is_llama_available, is_json_response_available, json_content)
167-
168-
# If there is no current conversation selected on the list to the left, make a new one.
169-
if self.__mainWidget.currentIndex() == 0:
170-
self.addThread.emit()
171-
172-
# Additional information of user's input
173-
additional_info = {
174-
'role': 'user',
175-
'content': cur_text,
176-
'model_name': param['model'],
177-
'finish_reason': '',
178-
'prompt_tokens': '',
179-
'completion_tokens': '',
180-
'total_tokens': '',
181-
182-
'is_json_response_available': is_json_response_available,
183-
}
184-
185-
container_param = {k: v for k, v in {**param, **additional_info}.items() if
186-
k in ChatMessageContainer.get_keys()}
187-
188-
# Create a container for the user's input and output from the chatbot
189-
container = ChatMessageContainer(**container_param)
190-
191-
query_text = self.__prompt.getContent()
192-
self.__browser.showLabel(query_text, False, container)
162+
param = get_argument(model, system, messages, cur_text, temperature, top_p, frequency_penalty,
163+
presence_penalty, stream,
164+
use_max_tokens, max_tokens,
165+
images,
166+
is_llama_available, is_json_response_available, json_content,
167+
self.__is_g4f)
168+
169+
# If there is no current conversation selected on the list to the left, make a new one.
170+
if self.__mainWidget.currentIndex() == 0:
171+
self.addThread.emit()
172+
173+
# Additional information of user's input
174+
additional_info = {
175+
'role': 'user',
176+
'content': cur_text,
177+
'model_name': param['model'],
178+
'finish_reason': '',
179+
'prompt_tokens': '',
180+
'completion_tokens': '',
181+
'total_tokens': '',
182+
183+
'is_json_response_available': is_json_response_available,
184+
}
185+
186+
container_param = {k: v for k, v in {**param, **additional_info}.items() if
187+
k in ChatMessageContainer.get_keys()}
188+
189+
# Create a container for the user's input and output from the chatbot
190+
container = ChatMessageContainer(**container_param)
191+
192+
query_text = self.__prompt.getContent()
193+
self.__browser.showLabel(query_text, False, container)
193194

195+
# Get parameters for OpenAI
196+
if is_llama_available:
194197
# Run a different thread based on whether the llama-index is enabled or not.
195-
if is_llama_available:
196-
self.__t = LlamaOpenAIThread(param, container, LLAMAINDEX_WRAPPER, query_text)
197-
else:
198-
self.__t = GPTThread(param, info=container)
199-
self.__t.started.connect(self.__beforeGenerated)
200-
self.__t.replyGenerated.connect(self.__browser.showLabel)
201-
self.__t.streamFinished.connect(self.__browser.streamFinished)
202-
self.__t.start()
203-
self.__t.finished.connect(self.__afterGenerated)
204-
205-
# Remove image files widget from the window
206-
self.__prompt.resetUploadImageFileWidget()
198+
self.__t = LlamaOpenAIThread(param, container, LLAMAINDEX_WRAPPER, query_text)
207199
else:
208-
param = get_argument(model, system, messages, cur_text, temperature, top_p, frequency_penalty, presence_penalty, stream,
209-
use_max_tokens, max_tokens,
210-
images,
211-
is_llama_available, is_json_response_available, json_content)
212-
213-
# If there is no current conversation selected on the list to the left, make a new one.
214-
if self.__mainWidget.currentIndex() == 0:
215-
self.addThread.emit()
216-
217-
# Additional information of user's input
218-
additional_info = {
219-
'role': 'user',
220-
'content': cur_text,
221-
'model_name': param['model'],
222-
'finish_reason': '',
223-
'prompt_tokens': '',
224-
'completion_tokens': '',
225-
'total_tokens': '',
226-
227-
'is_json_response_available': is_json_response_available,
228-
}
229-
230-
container_param = {k: v for k, v in {**param, **additional_info}.items() if
231-
k in ChatMessageContainer.get_keys()}
232-
233-
# Create a container for the user's input and output from the chatbot
234-
container = ChatMessageContainer(**container_param)
235-
236-
query_text = self.__prompt.getContent()
237-
self.__browser.showLabel(query_text, False, container)
238-
239-
self.__t = ChatThread(param, info=container)
240-
self.__t.started.connect(self.__beforeGenerated)
241-
self.__t.replyGenerated.connect(self.__browser.showLabel)
242-
self.__t.streamFinished.connect(self.__browser.streamFinished)
243-
self.__t.start()
244-
self.__t.finished.connect(self.__afterGenerated)
200+
self.__t = ChatThread(param, info=container, is_g4f=self.__is_g4f)
201+
202+
self.__t.started.connect(self.__beforeGenerated)
203+
self.__t.replyGenerated.connect(self.__browser.showLabel)
204+
self.__t.streamFinished.connect(self.__browser.streamFinished)
205+
self.__t.start()
206+
self.__t.finished.connect(self.__afterGenerated)
207+
208+
# Remove image files widget from the window
209+
self.__prompt.resetUploadImageFileWidget()
245210

246211
except Exception as e:
247212
# get the line of error and filename
@@ -286,6 +251,10 @@ def __bringWindowToFront(self):
286251
window.raise_()
287252
window.activateWindow()
288253

254+
def setG4F(self, idx):
255+
# Decide whether to use G4F based on the current tab index
256+
self.__is_g4f = idx == 0
257+
289258
def toggleJSON(self, f):
290259
self.__prompt.toggleJSON(f)
291260

0 commit comments

Comments
 (0)