Skip to content

Commit ac020e5

Browse files
committed
Add chat ui
Add chat ui * Add Chat with AI ui and classes
1 parent c93e855 commit ac020e5

File tree

10 files changed

+160
-36
lines changed

10 files changed

+160
-36
lines changed

je_editor/pyside_ui/dialog/ai_dialog/set_ai_dialog.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from PySide6.QtWidgets import QWidget, QBoxLayout, QLineEdit, QHBoxLayout, QPushButton
1+
from PySide6.QtWidgets import QWidget, QBoxLayout, QLineEdit, QHBoxLayout, QPushButton, QMessageBox
22

3+
from je_editor.pyside_ui.main_ui.ai_widget.ai_config import ai_config
34
from je_editor.utils.logging.loggin_instance import jeditor_logger
45
from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
56

@@ -26,4 +27,14 @@ def __init__(self):
2627
self.setLayout(self.box_layout)
2728

2829
def update_ai_config(self):
29-
pass
30+
base_url = self.base_url_input.text().strip()
31+
api_key = self.api_key_input.text().strip()
32+
chat_model = self.chat_model_input.text().strip()
33+
if base_url and chat_model:
34+
ai_config.choosable_ai.update(
35+
{"AI_model": {"base_url": base_url, "api_key": api_key, "chat_model": chat_model}}
36+
)
37+
else:
38+
QMessageBox.warning(self,
39+
language_wrapper.language_word_dict.get("set_ai_model_warring_title"),
40+
language_wrapper.language_word_dict.get("set_ai_model_warring_text"))
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
from pathlib import Path
2-
3-
from je_editor.utils.json.json_file import read_json
1+
from queue import Queue
42

53

64
class AIConfig(object):
75

86
def __init__(self):
97
self.current_ai_model_system_prompt: str = ""
108
self.choosable_ai: dict[str, dict[str, str]] = {
11-
"AI_model": {"ai_base_url": "", "ai_api_key": "", "chat_model": ""}
9+
"AI_model": {
10+
"ai_base_url": "",
11+
"ai_api_key": "",
12+
"chat_model": "",
13+
"prompt_template": "",
14+
}
1215
}
16+
self.message_queue = Queue()
17+
1318

19+
ai_config = AIConfig()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from threading import Thread
2+
3+
from je_editor.pyside_ui.main_ui.ai_widget.ai_config import ai_config
4+
from je_editor.pyside_ui.main_ui.ai_widget.langchain_interface import LangChainInterface
5+
6+
7+
class AskThread(Thread):
8+
9+
def __init__(self, lang_chain_interface: LangChainInterface, prompt):
10+
super().__init__()
11+
self.lang_chain_interface = lang_chain_interface
12+
self.prompt = prompt
13+
14+
15+
def run(self):
16+
ai_response = self.lang_chain_interface.call_ai_model(prompt=self.prompt)
17+
ai_config.message_queue.put(ai_response)
Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
from __future__ import annotations
22

33
from pathlib import Path
4-
from typing import TYPE_CHECKING
4+
from typing import TYPE_CHECKING, Union
55

6-
from PySide6.QtCore import Qt
6+
from PySide6.QtCore import Qt, QTimer
77
from PySide6.QtGui import QFontDatabase
8-
from PySide6.QtWidgets import QWidget, QPlainTextEdit, QScrollArea, QLabel, QComboBox, QGridLayout, QMessageBox
8+
from PySide6.QtWidgets import QWidget, QPlainTextEdit, QScrollArea, QLabel, QComboBox, QGridLayout, QPushButton, \
9+
QMessageBox, QSizePolicy, QLineEdit
910

10-
from je_editor.pyside_ui.main_ui.ai_widget.ai_config import AIConfig
11+
from je_editor.pyside_ui.dialog.ai_dialog.set_ai_dialog import SetAIDialog
12+
from je_editor.pyside_ui.main_ui.ai_widget.ai_config import AIConfig, ai_config
13+
from je_editor.pyside_ui.main_ui.ai_widget.ask_thread import AskThread
14+
from je_editor.pyside_ui.main_ui.ai_widget.langchain_interface import LangChainInterface
1115
from je_editor.utils.json.json_file import read_json
1216
from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
1317

1418
if TYPE_CHECKING:
1519
from je_editor.pyside_ui.main_ui.main_editor import EditorMain
1620

1721

22+
def set_ai_config():
23+
# Set and output AI a config file
24+
set_ai_config_dialog = SetAIDialog()
25+
set_ai_config_dialog.show()
26+
27+
1828
class ChatUI(QWidget):
1929

2030
def __init__(self, main_window: EditorMain):
@@ -30,41 +40,84 @@ def __init__(self, main_window: EditorMain):
3040
self.chat_panel_scroll_area.setViewportMargins(0, 0, 0, 0)
3141
self.chat_panel_scroll_area.setWidget(self.chat_panel)
3242
self.chat_panel.setFont(QFontDatabase.font(self.font().family(), "", 16))
43+
# Prompt input
44+
self.prompt_input = QLineEdit()
45+
self.prompt_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
46+
self.prompt_input.returnPressed.connect(self.call_ai_model)
3347
# Font size combobox
3448
self.font_size_label = QLabel(language_wrapper.language_word_dict.get("font_size"))
3549
self.font_size_combobox = QComboBox()
3650
for font_size in range(2, 101, 2):
3751
self.font_size_combobox.addItem(str(font_size))
3852
self.font_size_combobox.setCurrentText("16")
3953
self.font_size_combobox.currentTextChanged.connect(self.update_panel_text_size)
54+
# Buttons
55+
self.set_ai_config_button = QPushButton(language_wrapper.language_word_dict.get("chat_ui_set_ai_button"))
56+
self.set_ai_config_button.clicked.connect(set_ai_config)
57+
self.load_ai_config_button = QPushButton(language_wrapper.language_word_dict.get("chat_ui_load_ai_button"))
58+
self.load_ai_config_button.clicked.connect(self.load_ai_config)
59+
self.call_ai_model_button = QPushButton(language_wrapper.language_word_dict.get("chat_ui_call_ai_model_button"))
60+
self.call_ai_model_button.clicked.connect(self.call_ai_model)
4061
# Add to layout
4162
self.grid_layout = QGridLayout()
42-
self.grid_layout.addWidget(self.chat_panel_scroll_area, 1, 0, -1, -1)
43-
self.grid_layout.addWidget(self.font_size_combobox, 0, 1)
44-
ai_config = AIConfig()
45-
# Load AI config if exists
63+
self.grid_layout.addWidget(self.chat_panel_scroll_area, 0, 0, 1, 4)
64+
self.grid_layout.addWidget(self.call_ai_model_button, 1, 0)
65+
self.grid_layout.addWidget(self.font_size_combobox, 1, 1)
66+
self.grid_layout.addWidget(self.set_ai_config_button, 1, 2)
67+
self.grid_layout.addWidget(self.load_ai_config_button, 1, 3)
68+
self.grid_layout.addWidget(self.prompt_input, 2, 0, 1, 4)
69+
70+
# Variable
71+
self.ai_config: AIConfig = ai_config
72+
self.lang_chain_interface: Union[LangChainInterface, None] = None
73+
self.pull_message_timer = QTimer(self)
74+
self.pull_message_timer.setInterval(1000)
75+
self.pull_message_timer.timeout.connect(self.pull_message)
76+
self.pull_message_timer.start()
77+
78+
# Set layout
79+
self.setLayout(self.grid_layout)
80+
81+
self.load_ai_config()
82+
83+
def update_panel_text_size(self):
84+
self.chat_panel.setFont(
85+
QFontDatabase.font(self.font().family(), "", int(self.font_size_combobox.currentText())))
86+
87+
def load_ai_config(self):
4688
ai_config_file = Path(str(Path.cwd()) + "/" + ".jeditor/ai_config.json")
4789
if ai_config_file.exists():
48-
with open(ai_config_file, "r", encoding="utf-8") as file:
90+
with open(ai_config_file, "r", encoding="utf-8"):
4991
json_data: dict = read_json(str(ai_config_file))
5092
if json_data:
51-
if json_data.get("AI_model") and len(json_data.get("AI_model")) == 3:
93+
if json_data.get("AI_model") and len(json_data.get("AI_model")) == 4:
5294
ai_info: dict = json_data.get("AI_model")
5395
if ai_info.get("ai_base_url") and ai_info.get("chat_model"):
5496
ai_config.choosable_ai.update(json_data)
55-
else:
56-
QMessageBox.warning(self.main_window,
57-
language_wrapper.language_word_dict.get("set_ai_model_warring_title"),
58-
language_wrapper.language_word_dict.get("set_ai_model_warring_text"))
59-
60-
def update_panel_text_size(self):
61-
self.chat_panel.setFont(
62-
QFontDatabase.font(self.font().family(), "", int(self.font_size_combobox.currentText())))
97+
self.lang_chain_interface = LangChainInterface(
98+
main_window=self,
99+
api_key=ai_info.get("ai_api_key"),
100+
base_url=ai_info.get("ai_base_url"),
101+
chat_model=ai_info.get("chat_model"),
102+
prompt_template=ai_info.get("prompt_template"),
103+
)
63104

64-
def set_ai_config(self):
65-
# Set and output AI a config file
66-
pass
105+
def call_ai_model(self):
106+
if isinstance(self.lang_chain_interface, LangChainInterface):
107+
thread = AskThread(lang_chain_interface=self.lang_chain_interface, prompt=self.prompt_input.text())
108+
thread.start()
109+
else:
110+
ai_info = ai_config.choosable_ai.get('AI_model')
111+
QMessageBox.warning(self,
112+
language_wrapper.language_word_dict.get("call_ai_model_error_title"),
113+
language_wrapper.language_word_dict.get(
114+
f"ai_api_key: {ai_info.get('ai_api_key')}, \n"
115+
f"ai_base_url: {ai_info.get('ai_base_url')}, \n"
116+
f"chat_model: {ai_info.get('chat_model')}, \n"
117+
f"prompt_template: {ai_info.get('prompt_template')}"))
67118

68-
def load_ai_config(self):
69-
# Load exists an AI config file
70-
pass
119+
def pull_message(self):
120+
if not ai_config.message_queue.empty():
121+
ai_response = ai_config.message_queue.get_nowait()
122+
self.chat_panel.appendPlainText(ai_response)
123+
self.chat_panel.appendPlainText("\n")

je_editor/pyside_ui/main_ui/ai_widget/langchain_interface.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
1313

1414
if TYPE_CHECKING:
15-
from je_editor.pyside_ui.main_ui.main_editor import EditorMain
15+
from je_editor.pyside_ui.main_ui.ai_widget.chat_ui import ChatUI
1616

1717
class LangChainInterface(object):
1818

19-
def __init__(self, main_window: EditorMain, prompt_template: str, base_url: str, api_key: Union[SecretStr, str],
19+
def __init__(self, main_window: ChatUI, prompt_template: str, base_url: str, api_key: Union[SecretStr, str],
2020
chat_model: str):
2121
self.system_message_prompt = SystemMessagePromptTemplate.from_template(prompt_template)
2222
self.base_url = base_url
@@ -41,5 +41,5 @@ def call_ai_model(self, prompt: str) -> str | None:
4141
except Exception as error:
4242
QMessageBox.warning(self.main_window,
4343
language_wrapper.language_word_dict.get("call_ai_model_error_title"),
44-
language_wrapper.language_word_dict.get(repr(error)))
44+
str(error))
4545
return message

je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from frontengine import FrontEngineMainUI
1010

1111
from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
12+
from je_editor.pyside_ui.main_ui.ai_widget.chat_ui import ChatUI
1213
from je_editor.pyside_ui.main_ui.dock.destroy_dock import DestroyDock
1314
from je_editor.pyside_ui.main_ui.editor.editor_widget_dock import FullEditorWidget
1415
from je_editor.pyside_ui.main_ui.ipython_widget.rich_jupyter import IpythonWidget
@@ -59,7 +60,13 @@ def set_dock_menu(ui_we_want_to_set: EditorMain) -> None:
5960
lambda: add_dock_widget(ui_we_want_to_set, "ipython")
6061
)
6162
ui_we_want_to_set.dock_menu.addAction(ui_we_want_to_set.dock_menu.new_ipython)
62-
63+
# ChatUI
64+
ui_we_want_to_set.dock_menu.new_chat_ui = QAction(
65+
language_wrapper.language_word_dict.get("chat_ui_dock_label"))
66+
ui_we_want_to_set.dock_menu.new_chat_ui.triggered.connect(
67+
lambda: add_dock_widget(ui_we_want_to_set, "chat_ui")
68+
)
69+
ui_we_want_to_set.dock_menu.addAction(ui_we_want_to_set.dock_menu.new_chat_ui)
6370

6471
def add_dock_widget(ui_we_want_to_set: EditorMain, widget_type: str = None):
6572
jeditor_logger.info("build_dock_menu.py add_dock_widget "
@@ -90,6 +97,9 @@ def add_dock_widget(ui_we_want_to_set: EditorMain, widget_type: str = None):
9097
elif widget_type == "ipython":
9198
dock_widget.setWindowTitle(language_wrapper.language_word_dict.get("dock_ipython_title"))
9299
dock_widget.setWidget(IpythonWidget(ui_we_want_to_set))
100+
elif widget_type == "chat_ui":
101+
dock_widget.setWindowTitle(language_wrapper.language_word_dict.get("chat_ui_dock_label"))
102+
dock_widget.setWidget(ChatUI(ui_we_want_to_set))
93103
else:
94104
dock_widget.setWindowTitle(language_wrapper.language_word_dict.get("dock_browser_title"))
95105
dock_widget.setWidget(BrowserWidget())

je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from frontengine import FrontEngineMainUI
66

77
from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
8+
from je_editor.pyside_ui.main_ui.ai_widget.chat_ui import ChatUI
89
from je_editor.pyside_ui.main_ui.editor.editor_widget import EditorWidget
910
from je_editor.pyside_ui.main_ui.ipython_widget.rich_jupyter import IpythonWidget
1011
from je_editor.utils.logging.loggin_instance import jeditor_logger
@@ -55,6 +56,13 @@ def set_tab_menu(ui_we_want_to_set: EditorMain) -> None:
5556
lambda: add_ipython_tab(ui_we_want_to_set)
5657
)
5758
ui_we_want_to_set.tab_menu.addAction(ui_we_want_to_set.tab_menu.add_ipython_action)
59+
# ChatUI
60+
ui_we_want_to_set.tab_menu.add_chat_ui_action = QAction(
61+
language_wrapper.language_word_dict.get("tab_menu_chat_ui_tab_name"))
62+
ui_we_want_to_set.tab_menu.add_chat_ui_action.triggered.connect(
63+
lambda: add_chat_ui_tab(ui_we_want_to_set)
64+
)
65+
ui_we_want_to_set.tab_menu.addAction(ui_we_want_to_set.tab_menu.add_chat_ui_action)
5866

5967

6068
def add_editor_tab(ui_we_want_to_set: EditorMain):
@@ -98,3 +106,12 @@ def add_ipython_tab(ui_we_want_to_set: EditorMain):
98106
f"{language_wrapper.language_word_dict.get('tab_menu_ipython_tab_name')} "
99107
f"{ui_we_want_to_set.tab_widget.count()}")
100108

109+
110+
def add_chat_ui_tab(ui_we_want_to_set: EditorMain):
111+
jeditor_logger.info(f"build_tab_menu.py add_ipython_tab ui_we_want_to_set: {ui_we_want_to_set}")
112+
ui_we_want_to_set.tab_widget.addTab(
113+
ChatUI(ui_we_want_to_set),
114+
f"{language_wrapper.language_word_dict.get('tab_menu_chat_ui_tab_name')} "
115+
f"{ui_we_want_to_set.tab_widget.count()}")
116+
117+

je_editor/utils/multi_language/english.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
"tab_menu_web_tab_name": "WEB Browser",
110110
"tab_menu_stackoverflow_tab_name": "Stackoverflow",
111111
"tab_menu_ipython_tab_name": "IPython(Jupyter)",
112+
"tab_menu_chat_ui_tab_name": "ChatUI",
112113
# Text Menu
113114
"text_menu_label": "Text",
114115
"text_menu_label_font": "Font",
@@ -130,5 +131,9 @@
130131
"set_ai_model_waring_title": "Can not set AI Model",
131132
"set_ai_model_waring_text": "Please check [ai_base_url, ai_api_key, chat_model]",
132133
"call_ai_model_error_title": "Can't connect to AI Model",
133-
"call_ai_model_error_text": "Please check [ai_base_url, ai_api_key, chat_model]"
134+
"call_ai_model_error_text": "Please check [ai_base_url, ai_api_key, chat_model]",
135+
"chat_ui_dock_label": "Chat UI",
136+
"chat_ui_set_ai_button": "Set AI setting",
137+
"chat_ui_load_ai_button": "Load AI setting",
138+
"chat_ui_call_ai_model_button": "Send prompt",
134139
}

je_editor/utils/multi_language/traditional_chinese.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"tab_menu_web_tab_name": "瀏覽器",
108108
"tab_menu_stackoverflow_tab_name": "Stackoverflow",
109109
"tab_menu_ipython_tab_name": "IPython(Jupyter)",
110+
"tab_menu_chat_ui_tab_name": "ChatUI",
110111
# Text Menu
111112
"text_menu_label": "文字",
112113
"text_menu_label_font": "字體",
@@ -129,4 +130,8 @@
129130
"set_ai_model_warring_text": "請檢查 [ai_base_url, ai_api_key, chat_model]",
130131
"call_ai_model_error_title": "無法連線到 AI 模型",
131132
"call_ai_model_error_text": "請檢查 [ai_base_url, ai_api_key, chat_model]",
133+
"chat_ui_dock_label": "聊天 UI",
134+
"chat_ui_set_ai_button": "設定 AI 設定",
135+
"chat_ui_load_ai_button": "載入 AI 設定",
136+
"chat_ui_call_ai_model_button": "傳送 prompt",
132137
}

je_editor/utils/redirect_manager/redirect_manager_class.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def set_redirect() -> None:
5353
default_logger = logging.getLogger("RedirectManager")
5454
default_logger.addHandler(redirect_err)
5555
skip_logger_list = ["JEditor", "FrontEngine",
56-
"AutomationIDE", "TestPioneer"]
56+
"AutomationIDE", "TestPioneer", "langchain", "langchain_core", "langchain_openai"]
5757
for name in logging.root.manager.loggerDict.keys():
5858
if name in skip_logger_list:
5959
continue

0 commit comments

Comments
 (0)