je-editor 0.0.204__py3-none-any.whl → 0.0.205__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.
Files changed (24) hide show
  1. je_editor/pyside_ui/code/code_process/code_exec.py +1 -1
  2. je_editor/pyside_ui/code/shell_process/shell_exec.py +3 -3
  3. je_editor/pyside_ui/dialog/ai_dialog/__init__.py +0 -0
  4. je_editor/pyside_ui/dialog/ai_dialog/set_ai_dialog.py +44 -0
  5. je_editor/pyside_ui/dialog/file_dialog/create_file_dialog.py +4 -3
  6. je_editor/pyside_ui/dialog/file_dialog/save_file_dialog.py +1 -0
  7. je_editor/pyside_ui/dialog/search_ui/search_error_box.py +2 -2
  8. je_editor/pyside_ui/dialog/search_ui/search_text_box.py +3 -3
  9. je_editor/pyside_ui/main_ui/ai_widget/__init__.py +0 -0
  10. je_editor/pyside_ui/main_ui/ai_widget/ai_config.py +19 -0
  11. je_editor/pyside_ui/main_ui/ai_widget/ask_thread.py +17 -0
  12. je_editor/pyside_ui/main_ui/ai_widget/chat_ui.py +130 -0
  13. je_editor/pyside_ui/main_ui/ai_widget/langchain_interface.py +45 -0
  14. je_editor/pyside_ui/main_ui/main_editor.py +1 -1
  15. je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py +11 -1
  16. je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py +17 -0
  17. je_editor/utils/multi_language/english.py +23 -4
  18. je_editor/utils/multi_language/traditional_chinese.py +23 -4
  19. je_editor/utils/redirect_manager/redirect_manager_class.py +1 -1
  20. {je_editor-0.0.204.dist-info → je_editor-0.0.205.dist-info}/METADATA +4 -1
  21. {je_editor-0.0.204.dist-info → je_editor-0.0.205.dist-info}/RECORD +24 -17
  22. {je_editor-0.0.204.dist-info → je_editor-0.0.205.dist-info}/WHEEL +1 -1
  23. {je_editor-0.0.204.dist-info → je_editor-0.0.205.dist-info}/licenses/LICENSE +0 -0
  24. {je_editor-0.0.204.dist-info → je_editor-0.0.205.dist-info}/top_level.txt +0 -0
@@ -111,7 +111,7 @@ class ExecManager(object):
111
111
  stdout=subprocess.PIPE,
112
112
  stderr=subprocess.PIPE,
113
113
  stdin=subprocess.PIPE,
114
- shell=True
114
+ shell=False
115
115
  )
116
116
  self.still_run_program = True
117
117
  # program output message queue thread
@@ -44,8 +44,8 @@ class ShellManager(object):
44
44
  self.read_program_output_from_thread = None
45
45
  self.main_window: EditorWidget = main_window
46
46
  self.compiler_path = None
47
- self.code_result: [QTextEdit, None] = None
48
- self.timer: [QTimer, None] = None
47
+ self.code_result: Union[QTextEdit, None] = None
48
+ self.timer: Union[QTimer, None] = None
49
49
  self.still_run_shell: bool = True
50
50
  self.process = None
51
51
  self.run_output_queue: queue = queue.Queue()
@@ -157,7 +157,7 @@ class ShellManager(object):
157
157
  if self.after_done_function is not None:
158
158
  self.after_done_function()
159
159
 
160
- # exit program change run flag to false and clean read thread and queue and process
160
+ # exit program change runs flag to false and clean read thread and queue and process
161
161
  def exit_program(self) -> None:
162
162
  jeditor_logger.info("ShellManager exit_program")
163
163
  self.still_run_shell = False
File without changes
@@ -0,0 +1,44 @@
1
+ from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QMessageBox, QGridLayout, QLabel
2
+
3
+ from je_editor.pyside_ui.main_ui.ai_widget.ai_config import ai_config
4
+ from je_editor.utils.logging.loggin_instance import jeditor_logger
5
+ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
6
+
7
+
8
+ class SetAIDialog(QWidget):
9
+
10
+ def __init__(self):
11
+ jeditor_logger.info("Init SetAIDialog")
12
+ super().__init__()
13
+ self.base_url_label = QLabel(language_wrapper.language_word_dict.get("base_url_label"))
14
+ self.base_url_input = QLineEdit()
15
+ self.api_key_label = QLabel(language_wrapper.language_word_dict.get("api_key_label"))
16
+ self.api_key_input = QLineEdit()
17
+ self.chat_model_label = QLabel(language_wrapper.language_word_dict.get("ai_model_label"))
18
+ self.chat_model_input = QLineEdit()
19
+ self.add_ai_info_button = QPushButton()
20
+ self.add_ai_info_button.setText(language_wrapper.language_word_dict.get("add_ai_model_pushbutton"))
21
+ self.add_ai_info_button.clicked.connect(self.update_ai_config)
22
+ self.grid_layout = QGridLayout()
23
+ self.grid_layout.addWidget(self.base_url_label, 0, 0)
24
+ self.grid_layout.addWidget(self.base_url_input, 0, 1)
25
+ self.grid_layout.addWidget(self.api_key_label, 1, 0)
26
+ self.grid_layout.addWidget(self.api_key_input, 1, 1)
27
+ self.grid_layout.addWidget(self.chat_model_label, 2, 0)
28
+ self.grid_layout.addWidget(self.chat_model_input, 2, 1)
29
+ self.grid_layout.addWidget(self.add_ai_info_button, 3, 1)
30
+ self.setWindowTitle(language_wrapper.language_word_dict.get("add_ai_model_title"))
31
+ self.setLayout(self.grid_layout)
32
+
33
+ def update_ai_config(self):
34
+ base_url = self.base_url_input.text().strip()
35
+ api_key = self.api_key_input.text().strip()
36
+ chat_model = self.chat_model_input.text().strip()
37
+ if base_url and chat_model:
38
+ ai_config.choosable_ai.update(
39
+ {"AI_model": {"base_url": base_url, "api_key": api_key, "chat_model": chat_model}}
40
+ )
41
+ else:
42
+ QMessageBox.warning(self,
43
+ language_wrapper.language_word_dict.get("set_ai_model_warring_title"),
44
+ language_wrapper.language_word_dict.get("set_ai_model_warring_text"))
@@ -12,13 +12,13 @@ class CreateFileDialog(QWidget):
12
12
  self.box_layout = QBoxLayout(QBoxLayout.Direction.TopToBottom)
13
13
  self.file_name_input = QLineEdit()
14
14
  self.create_file_button = QPushButton()
15
- self.create_file_button.setText(language_wrapper.language_word_dict.get("dialog_create_file"))
15
+ self.create_file_button.setText(language_wrapper.language_word_dict.get("create_file_dialog_pushbutton"))
16
16
  self.create_file_button.clicked.connect(self.create_file)
17
17
  self.box_h_layout = QHBoxLayout()
18
18
  self.box_h_layout.addWidget(self.create_file_button)
19
19
  self.box_layout.addWidget(self.file_name_input)
20
20
  self.box_layout.addLayout(self.box_h_layout)
21
- self.setWindowTitle(language_wrapper.language_word_dict.get("dialog_create_file"))
21
+ self.setWindowTitle(language_wrapper.language_word_dict.get("create_file_dialog_pushbutton"))
22
22
  self.setLayout(self.box_layout)
23
23
 
24
24
  def create_file(self):
@@ -26,7 +26,8 @@ class CreateFileDialog(QWidget):
26
26
  file_name = self.file_name_input.text().strip()
27
27
  if file_name == "":
28
28
  create_file_message_box = QMessageBox(self)
29
- create_file_message_box.setText(language_wrapper.language_word_dict.get("dialog_input_file_name"))
29
+ create_file_message_box.setText(
30
+ language_wrapper.language_word_dict.get("input_file_name_dialog_pushbutton"))
30
31
  create_file_message_box.show()
31
32
  else:
32
33
  with open(file_name, "w+") as file:
@@ -42,3 +42,4 @@ def choose_file_get_save_file_path(parent_qt_instance: EditorMain) -> bool:
42
42
  widget.rename_self_tab()
43
43
  return True
44
44
  return False
45
+ return False
@@ -12,9 +12,9 @@ class SearchResultBox(QWidget):
12
12
  self.box_layout = QBoxLayout(QBoxLayout.Direction.TopToBottom)
13
13
  self.search_input = QLineEdit()
14
14
  self.search_next_button = QPushButton()
15
- self.search_next_button.setText(language_wrapper.language_word_dict.get("dialog_search_next"))
15
+ self.search_next_button.setText(language_wrapper.language_word_dict.get("search_next_dialog_pushbutton"))
16
16
  self.search_back_button = QPushButton()
17
- self.search_back_button.setText(language_wrapper.language_word_dict.get("dialog_search_back"))
17
+ self.search_back_button.setText(language_wrapper.language_word_dict.get("search_back_dialog_pushbutton"))
18
18
  self.box_h_layout = QHBoxLayout()
19
19
  self.box_h_layout.addWidget(self.search_back_button)
20
20
  self.box_h_layout.addWidget(self.search_next_button)
@@ -12,13 +12,13 @@ class SearchBox(QWidget):
12
12
  self.box_layout = QBoxLayout(QBoxLayout.Direction.TopToBottom)
13
13
  self.search_input = QLineEdit()
14
14
  self.search_next_button = QPushButton()
15
- self.search_next_button.setText(language_wrapper.language_word_dict.get("dialog_search_next"))
15
+ self.search_next_button.setText(language_wrapper.language_word_dict.get("search_next_dialog_pushbutton"))
16
16
  self.search_back_button = QPushButton()
17
- self.search_back_button.setText(language_wrapper.language_word_dict.get("dialog_search_back"))
17
+ self.search_back_button.setText(language_wrapper.language_word_dict.get("search_back_dialog_pushbutton"))
18
18
  self.box_h_layout = QHBoxLayout()
19
19
  self.box_h_layout.addWidget(self.search_back_button)
20
20
  self.box_h_layout.addWidget(self.search_next_button)
21
21
  self.box_layout.addWidget(self.search_input)
22
22
  self.box_layout.addLayout(self.box_h_layout)
23
- self.setWindowTitle("Search Text")
23
+ self.setWindowTitle(language_wrapper.language_word_dict.get("search_box_dialog_title"))
24
24
  self.setLayout(self.box_layout)
File without changes
@@ -0,0 +1,19 @@
1
+ from queue import Queue
2
+
3
+
4
+ class AIConfig(object):
5
+
6
+ def __init__(self):
7
+ self.current_ai_model_system_prompt: str = ""
8
+ self.choosable_ai: dict[str, dict[str, str]] = {
9
+ "AI_model": {
10
+ "ai_base_url": "",
11
+ "ai_api_key": "",
12
+ "chat_model": "",
13
+ "prompt_template": "",
14
+ }
15
+ }
16
+ self.message_queue = Queue()
17
+
18
+
19
+ ai_config = AIConfig()
@@ -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)
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Union
5
+
6
+ from PySide6.QtCore import Qt, QTimer
7
+ from PySide6.QtGui import QFontDatabase
8
+ from PySide6.QtWidgets import QWidget, QPlainTextEdit, QScrollArea, QLabel, QComboBox, QGridLayout, QPushButton, \
9
+ QMessageBox, QSizePolicy, QLineEdit
10
+
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
15
+ from je_editor.utils.json.json_file import read_json
16
+ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
17
+
18
+ if TYPE_CHECKING:
19
+ from je_editor.pyside_ui.main_ui.main_editor import EditorMain
20
+
21
+
22
+ class ChatUI(QWidget):
23
+
24
+ def __init__(self, main_window: EditorMain):
25
+ super().__init__()
26
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
27
+ self.main_window = main_window
28
+ # Chat panel
29
+ self.chat_panel = QPlainTextEdit()
30
+ self.chat_panel.setLineWrapMode(self.chat_panel.LineWrapMode.NoWrap)
31
+ self.chat_panel.setReadOnly(True)
32
+ self.chat_panel_scroll_area = QScrollArea()
33
+ self.chat_panel_scroll_area.setWidgetResizable(True)
34
+ self.chat_panel_scroll_area.setViewportMargins(0, 0, 0, 0)
35
+ self.chat_panel_scroll_area.setWidget(self.chat_panel)
36
+ self.chat_panel.setFont(QFontDatabase.font(self.font().family(), "", 16))
37
+ # Prompt input
38
+ self.prompt_input = QLineEdit()
39
+ self.prompt_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
40
+ self.prompt_input.returnPressed.connect(self.call_ai_model)
41
+ # Font size combobox
42
+ self.font_size_label = QLabel(language_wrapper.language_word_dict.get("font_size"))
43
+ self.font_size_combobox = QComboBox()
44
+ for font_size in range(2, 101, 2):
45
+ self.font_size_combobox.addItem(str(font_size))
46
+ self.font_size_combobox.setCurrentText("16")
47
+ self.font_size_combobox.currentTextChanged.connect(self.update_panel_text_size)
48
+ # Buttons
49
+ self.set_ai_config_button = QPushButton(language_wrapper.language_word_dict.get("chat_ui_set_ai_button"))
50
+ self.set_ai_config_button.clicked.connect(self.set_ai_config)
51
+ self.load_ai_config_button = QPushButton(language_wrapper.language_word_dict.get("chat_ui_load_ai_button"))
52
+ self.load_ai_config_button.clicked.connect(lambda: self.load_ai_config(show_load_complete=True))
53
+ self.call_ai_model_button = QPushButton(language_wrapper.language_word_dict.get("chat_ui_call_ai_model_button"))
54
+ self.call_ai_model_button.clicked.connect(self.call_ai_model)
55
+ # Add to layout
56
+ self.grid_layout = QGridLayout()
57
+ self.grid_layout.addWidget(self.chat_panel_scroll_area, 0, 0, 1, 4)
58
+ self.grid_layout.addWidget(self.call_ai_model_button, 1, 0)
59
+ self.grid_layout.addWidget(self.font_size_combobox, 1, 1)
60
+ self.grid_layout.addWidget(self.set_ai_config_button, 1, 2)
61
+ self.grid_layout.addWidget(self.load_ai_config_button, 1, 3)
62
+ self.grid_layout.addWidget(self.prompt_input, 2, 0, 1, 4)
63
+
64
+ # Variable
65
+ self.ai_config: AIConfig = ai_config
66
+ self.lang_chain_interface: Union[LangChainInterface, None] = None
67
+ self.set_ai_config_dialog = None
68
+ # Timer to pop queue
69
+ self.pull_message_timer = QTimer(self)
70
+ self.pull_message_timer.setInterval(1000)
71
+ self.pull_message_timer.timeout.connect(self.pull_message)
72
+ self.pull_message_timer.start()
73
+
74
+ # Set layout
75
+ self.setLayout(self.grid_layout)
76
+
77
+ self.load_ai_config()
78
+
79
+ def update_panel_text_size(self):
80
+ self.chat_panel.setFont(
81
+ QFontDatabase.font(self.font().family(), "", int(self.font_size_combobox.currentText())))
82
+
83
+ def load_ai_config(self, show_load_complete: bool = False):
84
+ ai_config_file = Path(str(Path.cwd()) + "/" + ".jeditor/ai_config.json")
85
+ if ai_config_file.exists():
86
+ with open(ai_config_file, "r", encoding="utf-8"):
87
+ json_data: dict = read_json(str(ai_config_file))
88
+ if json_data:
89
+ if json_data.get("AI_model") and len(json_data.get("AI_model")) == 4:
90
+ ai_info: dict = json_data.get("AI_model")
91
+ if ai_info.get("ai_base_url") and ai_info.get("chat_model"):
92
+ ai_config.choosable_ai.update(json_data)
93
+ self.lang_chain_interface = LangChainInterface(
94
+ main_window=self,
95
+ api_key=ai_info.get("ai_api_key"),
96
+ base_url=ai_info.get("ai_base_url"),
97
+ chat_model=ai_info.get("chat_model"),
98
+ prompt_template=ai_info.get("prompt_template"),
99
+ )
100
+ if show_load_complete:
101
+ load_complete = QMessageBox(self)
102
+ load_complete.setWindowTitle(language_wrapper.language_word_dict.get("load_ai_messagebox_title"))
103
+ load_complete.setText(language_wrapper.language_word_dict.get("load_ai_messagebox_text"))
104
+ load_complete.exec()
105
+
106
+
107
+ def call_ai_model(self):
108
+ if isinstance(self.lang_chain_interface, LangChainInterface):
109
+ thread = AskThread(lang_chain_interface=self.lang_chain_interface, prompt=self.prompt_input.text())
110
+ thread.start()
111
+ else:
112
+ ai_info = ai_config.choosable_ai.get('AI_model')
113
+ QMessageBox.warning(self,
114
+ language_wrapper.language_word_dict.get("call_ai_model_error_title"),
115
+ language_wrapper.language_word_dict.get(
116
+ f"ai_api_key: {ai_info.get('ai_api_key')}, \n"
117
+ f"ai_base_url: {ai_info.get('ai_base_url')}, \n"
118
+ f"chat_model: {ai_info.get('chat_model')}, \n"
119
+ f"prompt_template: {ai_info.get('prompt_template')}"))
120
+
121
+ def pull_message(self):
122
+ if not ai_config.message_queue.empty():
123
+ ai_response = ai_config.message_queue.get_nowait()
124
+ self.chat_panel.appendPlainText(ai_response)
125
+ self.chat_panel.appendPlainText("\n")
126
+
127
+ def set_ai_config(self):
128
+ # Set and output AI a config file
129
+ self.set_ai_config_dialog = SetAIDialog()
130
+ self.set_ai_config_dialog.show()
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import re
5
+ from typing import Union, TYPE_CHECKING
6
+
7
+ from PySide6.QtWidgets import QMessageBox
8
+ from langchain.prompts.chat import SystemMessagePromptTemplate
9
+ from langchain_openai import ChatOpenAI
10
+ from pydantic import SecretStr
11
+
12
+ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
13
+
14
+ if TYPE_CHECKING:
15
+ from je_editor.pyside_ui.main_ui.ai_widget.chat_ui import ChatUI
16
+
17
+ class LangChainInterface(object):
18
+
19
+ def __init__(self, main_window: ChatUI, prompt_template: str, base_url: str, api_key: Union[SecretStr, str],
20
+ chat_model: str):
21
+ self.system_message_prompt = SystemMessagePromptTemplate.from_template(prompt_template)
22
+ self.base_url = base_url
23
+ self.api_key = api_key
24
+ self.chat_model = chat_model
25
+ self.main_window = main_window
26
+ os.environ["OPENAI_BASE_URL"] = self.base_url
27
+ os.environ["OPENAI_API_KEY"] = self.api_key
28
+ os.environ["CHAT_MODEL"] = self.chat_model
29
+ self.chat_ai = ChatOpenAI(base_url=self.base_url, api_key=self.api_key, model=self.chat_model)
30
+
31
+ def call_ai_model(self, prompt: str) -> str | None:
32
+ message = None
33
+ try:
34
+ message = self.chat_ai.invoke(prompt).text()
35
+ match = re.search(r"</think>\s*(.*)", message, re.DOTALL)
36
+ if match:
37
+ message = match.group(1).strip()
38
+ else:
39
+ message = message
40
+
41
+ except Exception as error:
42
+ QMessageBox.warning(self.main_window,
43
+ language_wrapper.language_word_dict.get("call_ai_model_error_title"),
44
+ str(error))
45
+ return message
@@ -7,7 +7,7 @@ from typing import Dict, Type
7
7
  import jedi.settings
8
8
  from PySide6.QtCore import QTimer, QEvent
9
9
  from PySide6.QtGui import QFontDatabase, QIcon, Qt
10
- from PySide6.QtWidgets import QMainWindow, QWidget, QTabWidget
10
+ from PySide6.QtWidgets import QMainWindow, QWidget, QTabWidget, QMessageBox
11
11
  from frontengine import FrontEngineMainUI
12
12
  from qt_material import QtStyleTools
13
13
 
@@ -9,6 +9,7 @@ from PySide6.QtWidgets import QFileDialog
9
9
  from frontengine import FrontEngineMainUI
10
10
 
11
11
  from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
12
+ from je_editor.pyside_ui.main_ui.ai_widget.chat_ui import ChatUI
12
13
  from je_editor.pyside_ui.main_ui.dock.destroy_dock import DestroyDock
13
14
  from je_editor.pyside_ui.main_ui.editor.editor_widget_dock import FullEditorWidget
14
15
  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:
59
60
  lambda: add_dock_widget(ui_we_want_to_set, "ipython")
60
61
  )
61
62
  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)
63
70
 
64
71
  def add_dock_widget(ui_we_want_to_set: EditorMain, widget_type: str = None):
65
72
  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):
90
97
  elif widget_type == "ipython":
91
98
  dock_widget.setWindowTitle(language_wrapper.language_word_dict.get("dock_ipython_title"))
92
99
  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))
93
103
  else:
94
104
  dock_widget.setWindowTitle(language_wrapper.language_word_dict.get("dock_browser_title"))
95
105
  dock_widget.setWidget(BrowserWidget())
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
5
5
  from frontengine import FrontEngineMainUI
6
6
 
7
7
  from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
8
+ from je_editor.pyside_ui.main_ui.ai_widget.chat_ui import ChatUI
8
9
  from je_editor.pyside_ui.main_ui.editor.editor_widget import EditorWidget
9
10
  from je_editor.pyside_ui.main_ui.ipython_widget.rich_jupyter import IpythonWidget
10
11
  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:
55
56
  lambda: add_ipython_tab(ui_we_want_to_set)
56
57
  )
57
58
  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)
58
66
 
59
67
 
60
68
  def add_editor_tab(ui_we_want_to_set: EditorMain):
@@ -98,3 +106,12 @@ def add_ipython_tab(ui_we_want_to_set: EditorMain):
98
106
  f"{language_wrapper.language_word_dict.get('tab_menu_ipython_tab_name')} "
99
107
  f"{ui_we_want_to_set.tab_widget.count()}")
100
108
 
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
+
@@ -14,10 +14,13 @@ english_word_dict = {
14
14
  "browser_reload_button": "Reload",
15
15
  "browser_search_button": "Search",
16
16
  # Dialog
17
- "dialog_create_file": "Create File",
18
- "dialog_input_file_name": "Please enter right file name",
19
- "dialog_search_next": "Search next",
20
- "dialog_search_back": "Search back",
17
+ "create_file_dialog_pushbutton": "Create File",
18
+ "input_file_name_dialog_pushbutton": "Please enter right file name",
19
+ "search_next_dialog_pushbutton": "Search next",
20
+ "search_back_dialog_pushbutton": "Search back",
21
+ "search_box_dialog_title": "Search Text",
22
+ "add_ai_model_title": "Please fill in AI model information",
23
+ "add_ai_model_pushbutton": "Add AI Model",
21
24
  # Editor
22
25
  "editor_code_result": "Code result",
23
26
  "editor_format_check": "Format checker",
@@ -106,6 +109,7 @@ please make sure that the current encoding is consistent with the default encodi
106
109
  "tab_menu_web_tab_name": "WEB Browser",
107
110
  "tab_menu_stackoverflow_tab_name": "Stackoverflow",
108
111
  "tab_menu_ipython_tab_name": "IPython(Jupyter)",
112
+ "tab_menu_chat_ui_tab_name": "ChatUI",
109
113
  # Text Menu
110
114
  "text_menu_label": "Text",
111
115
  "text_menu_label_font": "Font",
@@ -122,4 +126,19 @@ please make sure that the current encoding is consistent with the default encodi
122
126
  "language_menu_bar_please_restart_messagebox": "Please restart",
123
127
  # Qtconsole
124
128
  "please_install_qtcontsole_label": "Please python -m pip install qtconsole first.",
129
+ # Chat UI
130
+ "font_size": "Font Size",
131
+ "set_ai_model_waring_title": "Can not set AI Model",
132
+ "set_ai_model_waring_text": "Please check [ai_base_url, ai_api_key, chat_model]",
133
+ "call_ai_model_error_title": "Can't connect to AI 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",
139
+ "base_url_label": "AI server URL",
140
+ "api_key_label": "AI server API Key",
141
+ "ai_model_label": "AI Model",
142
+ "load_ai_messagebox_title": "Load complete",
143
+ "load_ai_messagebox_text": "Load complete",
125
144
  }
@@ -14,10 +14,13 @@ traditional_chinese_word_dict = {
14
14
  "browser_reload_button": "重新載入",
15
15
  "browser_search_button": "搜尋",
16
16
  # Dialog
17
- "dialog_create_file": "建立檔案",
18
- "dialog_input_file_name": "請輸入正確格式的檔案名稱",
19
- "dialog_search_next": "搜尋下一個",
20
- "dialog_search_back": "搜尋上一個",
17
+ "create_file_dialog_pushbutton": "建立檔案",
18
+ "input_file_name_dialog_pushbutton": "請輸入正確格式的檔案名稱",
19
+ "search_next_dialog_pushbutton": "搜尋下一個",
20
+ "search_back_dialog_pushbutton": "搜尋上一個",
21
+ "search_box_dialog_title": "搜尋文字",
22
+ "add_ai_model_title": "請填寫 AI 模型資訊",
23
+ "add_ai_model_pushbutton": "新增 AI 模型",
21
24
  # Editor
22
25
  "editor_code_result": "程式運行結果",
23
26
  "editor_format_check": "程式格式檢查",
@@ -104,6 +107,7 @@ traditional_chinese_word_dict = {
104
107
  "tab_menu_web_tab_name": "瀏覽器",
105
108
  "tab_menu_stackoverflow_tab_name": "Stackoverflow",
106
109
  "tab_menu_ipython_tab_name": "IPython(Jupyter)",
110
+ "tab_menu_chat_ui_tab_name": "ChatUI",
107
111
  # Text Menu
108
112
  "text_menu_label": "文字",
109
113
  "text_menu_label_font": "字體",
@@ -120,4 +124,19 @@ traditional_chinese_word_dict = {
120
124
  "language_menu_bar_please_restart_messagebox": "請重啟以應用更改",
121
125
  # Qtconsole
122
126
  "please_install_qtcontsole_label": "請先 python -m pip install qtconsole",
127
+ # Chat UI
128
+ "font_size": "字體大小",
129
+ "set_ai_model_waring_title": "無法設置 AI Model",
130
+ "set_ai_model_warring_text": "請檢查 [ai_base_url, ai_api_key, chat_model]",
131
+ "call_ai_model_error_title": "無法連線到 AI 模型",
132
+ "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",
137
+ "base_url_label":"AI 伺服器 URL",
138
+ "api_key_label": "AI 伺服器 API Key",
139
+ "ai_model_label": "AI Model",
140
+ "load_ai_messagebox_title": "載入成功",
141
+ "load_ai_messagebox_text": "載入成功",
123
142
  }
@@ -53,7 +53,7 @@ class RedirectManager(object):
53
53
  default_logger = logging.getLogger("RedirectManager")
54
54
  default_logger.addHandler(redirect_err)
55
55
  skip_logger_list = ["JEditor", "FrontEngine",
56
- "AutomationIDE", "TestPioneer"]
56
+ "AutomationIDE", "TestPioneer", "langchain", "langchain_core", "langchain_openai"]
57
57
  for name in logging.root.manager.loggerDict.keys():
58
58
  if name in skip_logger_list:
59
59
  continue
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: je_editor
3
- Version: 0.0.204
3
+ Version: 0.0.205
4
4
  Summary: JEditor is basic but powerful editor include GPT
5
5
  Author-email: JE-Chen <jechenmailman@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/JE-Chen/je_editor
@@ -23,6 +23,9 @@ Requires-Dist: frontengine
23
23
  Requires-Dist: pycodestyle
24
24
  Requires-Dist: jedi
25
25
  Requires-Dist: qtconsole
26
+ Requires-Dist: langchain_openai
27
+ Requires-Dist: langchain
28
+ Requires-Dist: pydantic
26
29
  Dynamic: license-file
27
30
 
28
31
  # je_editor
@@ -15,26 +15,33 @@ je_editor/pyside_ui/code/auto_save/auto_save_thread.py,sha256=V_A8_4JZdHdF9e2E_r
15
15
  je_editor/pyside_ui/code/code_format/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  je_editor/pyside_ui/code/code_format/pep8_format.py,sha256=zrCkUdz2SG4bnMSkXfccvpHAmUOxXLedSBxL_nkdaoE,3574
17
17
  je_editor/pyside_ui/code/code_process/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- je_editor/pyside_ui/code/code_process/code_exec.py,sha256=mdkHe2MBBvgZbRjmSRgcu9KEFG_d0oyPWedYFUCZaxU,9840
18
+ je_editor/pyside_ui/code/code_process/code_exec.py,sha256=yrkWz7lqWmbpfuNUHavFfyisKuVfhHSHS6D95Oq52Dk,9841
19
19
  je_editor/pyside_ui/code/plaintext_code_edit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  je_editor/pyside_ui/code/plaintext_code_edit/code_edit_plaintext.py,sha256=lcwmBemFenH-uDGe6fo70MS47emzPFgy9XL7cipwryY,14249
21
21
  je_editor/pyside_ui/code/shell_process/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- je_editor/pyside_ui/code/shell_process/shell_exec.py,sha256=WpWORZ-1T4G_SdnJ4K_6THycNOpQbsIFgjSQFQOJ_1M,8730
22
+ je_editor/pyside_ui/code/shell_process/shell_exec.py,sha256=y1SxB2NnblB3U7qOwQxuJv7aMgBiBSMTY9gP5X8-j4E,8741
23
23
  je_editor/pyside_ui/code/syntax/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  je_editor/pyside_ui/code/syntax/python_syntax.py,sha256=9rK7_DYAANgf28PUdVl9xs42b76Re_SWwIpijEGBuzQ,3054
25
25
  je_editor/pyside_ui/code/syntax/syntax_setting.py,sha256=oaLRF_83Oa6bcAorf7AlZG0Mrt4FmsK7b4aA-LS_bRo,2284
26
26
  je_editor/pyside_ui/code/textedit_code_result/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  je_editor/pyside_ui/code/textedit_code_result/code_record.py,sha256=pT-CmcADEaXLZ0ih6D6rpe1XywgszrEpcJxtJYgopJE,2162
28
28
  je_editor/pyside_ui/dialog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ je_editor/pyside_ui/dialog/ai_dialog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ je_editor/pyside_ui/dialog/ai_dialog/set_ai_dialog.py,sha256=w_cku2dQthtk0TDSxEeDB-tvzdHV4vmUhFVDlb2ynO8,2392
29
31
  je_editor/pyside_ui/dialog/file_dialog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- je_editor/pyside_ui/dialog/file_dialog/create_file_dialog.py,sha256=XwVmVYv5SdZXHb_dHD2qfGf6q8LzxmD-6PD16W8cLm0,1599
32
+ je_editor/pyside_ui/dialog/file_dialog/create_file_dialog.py,sha256=3NvGVld_If2TsM_Xt0pgWDTcj51szS8MxG1raU-tP2A,1650
31
33
  je_editor/pyside_ui/dialog/file_dialog/open_file_dialog.py,sha256=wTI4TwMfJ-960c_C0cLy2EhWbZosewSmpyWOrcf9yLE,3806
32
- je_editor/pyside_ui/dialog/file_dialog/save_file_dialog.py,sha256=ON8brmfaBn7ySFob-dU58zn5BFPy-7mFtCDe9RaFWko,1763
34
+ je_editor/pyside_ui/dialog/file_dialog/save_file_dialog.py,sha256=pyPfwgrDiDpMKk8T_4oBukgJLxdWnzIKSziwFt4YuUU,1781
33
35
  je_editor/pyside_ui/dialog/search_ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- je_editor/pyside_ui/dialog/search_ui/search_error_box.py,sha256=GzMXMnIL1k8ZwG3A2a_oMgiUiXXqd7HKW45YlVlL_eo,1168
35
- je_editor/pyside_ui/dialog/search_ui/search_text_box.py,sha256=H2MbC-RGpKg9TzjV4MZj7Bw7qs9a9E4cjA4GI5zsv4U,1154
36
+ je_editor/pyside_ui/dialog/search_ui/search_error_box.py,sha256=jSa423cQW_xQ2WAj46CCWPsCP0Gy7g-UDe5o8LQyKQk,1190
37
+ je_editor/pyside_ui/dialog/search_ui/search_text_box.py,sha256=ul-98FVByq_TNkkLZcBSF4VSwIQNBBj-4e6jYlh6YEA,1229
36
38
  je_editor/pyside_ui/main_ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- je_editor/pyside_ui/main_ui/main_editor.py,sha256=S2NOZ7NaLtDM-sesxcPaN_FAYu69e92ZkKdBUILY1PM,11062
39
+ je_editor/pyside_ui/main_ui/main_editor.py,sha256=NCyNb20qLg-PRgjuyHB3UxOcIWfSDgvO0mn7SF2Jdgk,11075
40
+ je_editor/pyside_ui/main_ui/ai_widget/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
+ je_editor/pyside_ui/main_ui/ai_widget/ai_config.py,sha256=Uc9UkrIeALqjmZCF5ukXtSgU1EB4xa5223gk3CfYXX4,459
42
+ je_editor/pyside_ui/main_ui/ai_widget/ask_thread.py,sha256=ma5Cnwlr0RbU-sytmodcYabWueyGE8DKKISZ_0zf3Eo,571
43
+ je_editor/pyside_ui/main_ui/ai_widget/chat_ui.py,sha256=yBoWXvGvgUCWjmPoBcOhj0mtOm0R42mYEs7FokYQmnE,6763
44
+ je_editor/pyside_ui/main_ui/ai_widget/langchain_interface.py,sha256=OgBaUcUMR0Z2VQUO9QfCX2wJc_qBFw3IvoMCiEGBTug,1768
38
45
  je_editor/pyside_ui/main_ui/dock/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
46
  je_editor/pyside_ui/main_ui/dock/destroy_dock.py,sha256=MTN45BykNm6FA4gMW7gI4Kr9orTdcxVTm7mch3DUhaw,604
40
47
  je_editor/pyside_ui/main_ui/editor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -48,7 +55,7 @@ je_editor/pyside_ui/main_ui/menu/set_menu_bar.py,sha256=I12DXLyRO4cKe17fQY-QDazK
48
55
  je_editor/pyside_ui/main_ui/menu/check_style_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
56
  je_editor/pyside_ui/main_ui/menu/check_style_menu/build_check_style_menu.py,sha256=1wD6FI21Dw3IcQONuTkHP9rUPGwY70OvCtSc-ZcTKMk,3793
50
57
  je_editor/pyside_ui/main_ui/menu/dock_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
- je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py,sha256=IBRpoZxsyrbeWdz86KNyfmkwa-q4_tThf9IpCy-Aqa0,4983
58
+ je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py,sha256=74djXCmevdbGs61EBiIuvWUnHrN41DCZJw6aPEkLiK8,5600
52
59
  je_editor/pyside_ui/main_ui/menu/file_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
60
  je_editor/pyside_ui/main_ui/menu/file_menu/build_file_menu.py,sha256=YT6kvXShRbfM4Qo51VveO_IK8uDqlNrGwHZUiM6_Tes,6881
54
61
  je_editor/pyside_ui/main_ui/menu/help_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -67,7 +74,7 @@ je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/utils.py,sha256=zk5adQ7
67
74
  je_editor/pyside_ui/main_ui/menu/style_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
75
  je_editor/pyside_ui/main_ui/menu/style_menu/build_style_menu.py,sha256=OjfcjQ7fA1Z1rMwIS9oGkIkC-jy-e0jdGoYo1btTqUQ,1852
69
76
  je_editor/pyside_ui/main_ui/menu/tab_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
- je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py,sha256=-vi3E259R8c9RQPjudAr7SoJ_X2ocBHPEAlTeihPoG0,4877
77
+ je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py,sha256=ZP_HHXmQYguSVQpzK-WI0Jb-PgLvKRMW5GlfGXBPQcA,5691
71
78
  je_editor/pyside_ui/main_ui/menu/text_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
79
  je_editor/pyside_ui/main_ui/menu/text_menu/build_text_menu.py,sha256=k3pOpK4i8ccs3D8VIrNx8g3kUDp1fZdqv9H5uEvNl5c,3558
73
80
  je_editor/pyside_ui/main_ui/save_settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -94,15 +101,15 @@ je_editor/utils/json_format/json_process.py,sha256=tszo48OnVivVkuuPrl-FtGaaq-1YO
94
101
  je_editor/utils/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
102
  je_editor/utils/logging/loggin_instance.py,sha256=OFdWgra201B4f1vDM35M9L-47DK-8j-5HQEAq_ySSKM,841
96
103
  je_editor/utils/multi_language/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
- je_editor/utils/multi_language/english.py,sha256=gR0utQPeyNhcpRNaaaa05mFC1ErGWPvC4nEQzAL_3D8,5642
104
+ je_editor/utils/multi_language/english.py,sha256=A9I54gmcCNPEcFwNH_EMUkGYasSusiNUPLbFUTJewsE,6637
98
105
  je_editor/utils/multi_language/multi_language_wrapper.py,sha256=_MtYrE51poQ1p_iWvT2eg_604uj6IsiBgGpifWpYLGc,1050
99
- je_editor/utils/multi_language/traditional_chinese.py,sha256=Vf6tpdE0Rb8hiVBGHLpSioi4QrRXSsP5-iktePZGk0w,5668
106
+ je_editor/utils/multi_language/traditional_chinese.py,sha256=MPCBCwd7KnSHgq86z77jS1EECnlFnpmRMKeqRO_fUM4,6667
100
107
  je_editor/utils/redirect_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
- je_editor/utils/redirect_manager/redirect_manager_class.py,sha256=2tLTOnxpvcYvtO3R91iHaaeN7EOE3uTgLg-UlASy5Ac,2218
108
+ je_editor/utils/redirect_manager/redirect_manager_class.py,sha256=CHASOUD0Uob5jZrxK_lK9SE1TE1WCZEUfDPRGeJNSEs,2269
102
109
  je_editor/utils/venv_check/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
110
  je_editor/utils/venv_check/check_venv.py,sha256=oCrMdue4NYUUGrVifh_iHFwIgxVx9azYN4jz3Xiulgg,999
104
- je_editor-0.0.204.dist-info/licenses/LICENSE,sha256=KMhUHh6pnIUvmXDW-49L_Sz63bqkOlPDqsecaqKiitU,1091
105
- je_editor-0.0.204.dist-info/METADATA,sha256=DkmoT7tw_70DnSWlAx_ZlXcOkMnWkPRdfHOpSyCCrpA,3314
106
- je_editor-0.0.204.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
107
- je_editor-0.0.204.dist-info/top_level.txt,sha256=_9YA7BgxpkmdLs-5V_UQILxClcMRgPyG1a3qaE-Bkcs,10
108
- je_editor-0.0.204.dist-info/RECORD,,
111
+ je_editor-0.0.205.dist-info/licenses/LICENSE,sha256=KMhUHh6pnIUvmXDW-49L_Sz63bqkOlPDqsecaqKiitU,1091
112
+ je_editor-0.0.205.dist-info/METADATA,sha256=7oCIeF9Yky6gvTDFWrgkxwTSWHNjkWC_9iEJY1tg6dk,3398
113
+ je_editor-0.0.205.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
114
+ je_editor-0.0.205.dist-info/top_level.txt,sha256=_9YA7BgxpkmdLs-5V_UQILxClcMRgPyG1a3qaE-Bkcs,10
115
+ je_editor-0.0.205.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5