je-editor 0.0.223__py3-none-any.whl → 0.0.224__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.
Potentially problematic release.
This version of je-editor might be problematic. Click here for more details.
- je_editor/git_client/commit_graph.py +7 -7
- je_editor/git_client/git_action.py +0 -7
- je_editor/pyside_ui/browser/browser_widget.py +24 -11
- je_editor/pyside_ui/browser/main_browser_widget.py +40 -27
- je_editor/pyside_ui/code/auto_save/auto_save_manager.py +34 -2
- je_editor/pyside_ui/code/auto_save/auto_save_thread.py +19 -6
- je_editor/pyside_ui/code/code_format/pep8_format.py +53 -9
- je_editor/pyside_ui/code/code_process/code_exec.py +88 -52
- je_editor/pyside_ui/code/plaintext_code_edit/code_edit_plaintext.py +116 -55
- je_editor/pyside_ui/code/running_process_manager.py +19 -1
- je_editor/pyside_ui/code/shell_process/shell_exec.py +71 -48
- je_editor/pyside_ui/code/syntax/python_syntax.py +45 -10
- je_editor/pyside_ui/code/syntax/syntax_setting.py +40 -12
- je_editor/pyside_ui/code/textedit_code_result/code_record.py +34 -12
- je_editor/pyside_ui/code/variable_inspector/inspector_gui.py +53 -6
- je_editor/pyside_ui/dialog/ai_dialog/set_ai_dialog.py +30 -3
- je_editor/pyside_ui/dialog/file_dialog/create_file_dialog.py +35 -2
- je_editor/pyside_ui/dialog/file_dialog/open_file_dialog.py +33 -5
- je_editor/pyside_ui/dialog/file_dialog/save_file_dialog.py +25 -3
- je_editor/pyside_ui/dialog/search_ui/search_error_box.py +26 -1
- je_editor/pyside_ui/dialog/search_ui/search_text_box.py +26 -1
- je_editor/pyside_ui/git_ui/code_diff_compare/code_diff_viewer_widget.py +11 -11
- je_editor/pyside_ui/git_ui/git_client/commit_table.py +46 -8
- je_editor/pyside_ui/git_ui/git_client/git_branch_tree_widget.py +49 -15
- je_editor/pyside_ui/git_ui/git_client/graph_view.py +64 -20
- je_editor/pyside_ui/main_ui/ai_widget/ai_config.py +20 -5
- je_editor/pyside_ui/main_ui/ai_widget/ask_thread.py +20 -1
- je_editor/pyside_ui/main_ui/ai_widget/chat_ui.py +56 -41
- je_editor/pyside_ui/main_ui/ai_widget/langchain_interface.py +45 -6
- je_editor/pyside_ui/main_ui/console_widget/console_gui.py +44 -12
- je_editor/pyside_ui/main_ui/console_widget/qprocess_adapter.py +34 -13
- je_editor/pyside_ui/main_ui/dock/destroy_dock.py +33 -2
- je_editor/pyside_ui/main_ui/editor/editor_widget.py +104 -20
- je_editor/pyside_ui/main_ui/editor/editor_widget_dock.py +34 -7
- je_editor/pyside_ui/main_ui/editor/process_input.py +38 -11
- je_editor/pyside_ui/main_ui/ipython_widget/rich_jupyter.py +46 -11
- je_editor/pyside_ui/main_ui/main_editor.py +175 -37
- je_editor/pyside_ui/main_ui/menu/check_style_menu/build_check_style_menu.py +51 -28
- je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py +80 -22
- je_editor/pyside_ui/main_ui/menu/file_menu/build_file_menu.py +70 -17
- je_editor/pyside_ui/main_ui/menu/help_menu/build_help_menu.py +34 -3
- je_editor/pyside_ui/main_ui/menu/language_menu/build_language_server.py +41 -1
- je_editor/pyside_ui/main_ui/menu/python_env_menu/build_venv_menu.py +100 -42
- je_editor/pyside_ui/main_ui/menu/run_menu/build_run_menu.py +57 -7
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_debug_menu.py +50 -4
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_program_menu.py +52 -6
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_shell_menu.py +44 -4
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/utils.py +23 -1
- je_editor/pyside_ui/main_ui/menu/set_menu_bar.py +37 -12
- je_editor/pyside_ui/main_ui/menu/style_menu/build_style_menu.py +44 -7
- je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py +126 -28
- je_editor/pyside_ui/main_ui/menu/text_menu/build_text_menu.py +65 -1
- je_editor/pyside_ui/main_ui/save_settings/setting_utils.py +18 -1
- je_editor/pyside_ui/main_ui/save_settings/user_color_setting_file.py +33 -3
- je_editor/pyside_ui/main_ui/save_settings/user_setting_file.py +38 -11
- je_editor/pyside_ui/main_ui/system_tray/extend_system_tray.py +39 -2
- je_editor/start_editor.py +26 -1
- je_editor/utils/encodings/python_encodings.py +101 -98
- je_editor/utils/file/open/open_file.py +36 -19
- je_editor/utils/file/save/save_file.py +35 -14
- je_editor/utils/json/json_file.py +29 -14
- je_editor/utils/json_format/json_process.py +33 -2
- je_editor/utils/logging/loggin_instance.py +38 -8
- je_editor/utils/multi_language/multi_language_wrapper.py +29 -4
- je_editor/utils/redirect_manager/redirect_manager_class.py +49 -11
- je_editor/utils/venv_check/check_venv.py +45 -15
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/METADATA +1 -1
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/RECORD +71 -71
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/WHEEL +0 -0
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/licenses/LICENSE +0 -0
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/top_level.txt +0 -0
|
@@ -3,25 +3,63 @@ from PySide6.QtWidgets import QTableView
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class CommitTable(QTableView):
|
|
6
|
+
"""
|
|
7
|
+
CommitTable 類別:用來顯示 Git Commit 紀錄的表格
|
|
8
|
+
CommitTable class: A table view to display Git commit history
|
|
9
|
+
"""
|
|
10
|
+
|
|
6
11
|
def __init__(self, parent=None):
|
|
7
12
|
super().__init__(parent)
|
|
8
|
-
|
|
13
|
+
|
|
14
|
+
# 建立一個 QStandardItemModel,初始為 0 行 5 欄
|
|
15
|
+
# Create a QStandardItemModel with 0 rows and 5 columns
|
|
16
|
+
self.model_data = QStandardItemModel(0, 5, self) # 多一欄 / 5 columns
|
|
17
|
+
|
|
18
|
+
# 設定表頭標籤
|
|
19
|
+
# Set horizontal header labels
|
|
9
20
|
self.model_data.setHorizontalHeaderLabels(["#", "SHA", "Message", "Author", "Date"])
|
|
21
|
+
|
|
22
|
+
# 將模型綁定到 QTableView
|
|
23
|
+
# Set the model for QTableView
|
|
10
24
|
self.setModel(self.model_data)
|
|
25
|
+
|
|
26
|
+
# 設定選擇行為為整列選取
|
|
27
|
+
# Set selection behavior to select entire rows
|
|
11
28
|
self.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows)
|
|
29
|
+
|
|
30
|
+
# 禁止編輯表格內容
|
|
31
|
+
# Disable editing of table cells
|
|
12
32
|
self.setEditTriggers(QTableView.EditTrigger.NoEditTriggers)
|
|
33
|
+
|
|
34
|
+
# 讓最後一欄自動延展填滿
|
|
35
|
+
# Stretch the last column to fill available space
|
|
13
36
|
self.horizontalHeader().setStretchLastSection(True)
|
|
14
37
|
|
|
15
38
|
def set_commits(self, commits):
|
|
39
|
+
"""
|
|
40
|
+
將 commit 資料填入表格
|
|
41
|
+
Populate the table with commit data
|
|
42
|
+
:param commits: commit 資料清單 (list of dicts with sha, message, author, date)
|
|
43
|
+
"""
|
|
44
|
+
# 清空現有資料
|
|
45
|
+
# Clear existing rows
|
|
16
46
|
self.model_data.setRowCount(0)
|
|
17
|
-
|
|
47
|
+
|
|
48
|
+
# 遍歷 commit 清單,逐行加入表格
|
|
49
|
+
# Iterate over commits and append rows
|
|
50
|
+
for index, commit in enumerate(commits, start=1):
|
|
18
51
|
row = [
|
|
19
|
-
QStandardItem(str(
|
|
20
|
-
QStandardItem(
|
|
21
|
-
QStandardItem(
|
|
22
|
-
QStandardItem(
|
|
23
|
-
QStandardItem(
|
|
52
|
+
QStandardItem(str(index)), # 行號 / row number
|
|
53
|
+
QStandardItem(commit["sha"][:7]), # SHA 短碼 / short SHA
|
|
54
|
+
QStandardItem(commit["message"]), # 提交訊息 / commit message
|
|
55
|
+
QStandardItem(commit["author"]), # 作者 / author
|
|
56
|
+
QStandardItem(commit["date"]), # 日期 / date
|
|
24
57
|
]
|
|
58
|
+
# 設定每個欄位不可編輯
|
|
59
|
+
# Make each item non-editable
|
|
25
60
|
for item in row:
|
|
26
61
|
item.setEditable(False)
|
|
27
|
-
|
|
62
|
+
|
|
63
|
+
# 將這一列加入模型
|
|
64
|
+
# Append row to the model
|
|
65
|
+
self.model_data.appendRow(row)
|
|
@@ -19,64 +19,89 @@ log = logging.getLogger(__name__)
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class GitTreeViewGUI(QWidget):
|
|
22
|
+
"""
|
|
23
|
+
GitTreeViewGUI: Git 提交歷史圖形化檢視器
|
|
24
|
+
GitTreeViewGUI: A graphical viewer for Git commit history
|
|
25
|
+
"""
|
|
26
|
+
|
|
22
27
|
def __init__(self):
|
|
23
28
|
super().__init__()
|
|
24
29
|
self.language_wrapper_get = language_wrapper.language_word_dict.get
|
|
25
30
|
self.setWindowTitle(self.language_wrapper_get("git_graph_title"))
|
|
26
31
|
|
|
32
|
+
# === 主版面配置 / Main layout ===
|
|
27
33
|
git_treeview_main_layout = QVBoxLayout(self)
|
|
28
34
|
git_treeview_main_layout.setContentsMargins(0, 0, 0, 0)
|
|
29
35
|
git_treeview_main_layout.setSpacing(0)
|
|
30
36
|
|
|
37
|
+
# === 工具列 / Toolbar ===
|
|
31
38
|
git_treeview_toolbar = QToolBar()
|
|
39
|
+
|
|
40
|
+
# 開啟 Git 專案動作 / Action to open Git repo
|
|
32
41
|
open_repo_action = QAction(self.language_wrapper_get("git_graph_toolbar_open"), self)
|
|
33
42
|
open_repo_action.triggered.connect(self.open_repo)
|
|
34
43
|
git_treeview_toolbar.addAction(open_repo_action)
|
|
35
44
|
|
|
45
|
+
# 刷新圖表動作 / Action to refresh graph
|
|
36
46
|
act_refresh = QAction(self.language_wrapper_get("git_graph_toolbar_refresh"), self)
|
|
37
47
|
act_refresh.triggered.connect(self.refresh_graph)
|
|
38
48
|
git_treeview_toolbar.addAction(act_refresh)
|
|
39
49
|
|
|
40
50
|
git_treeview_main_layout.addWidget(git_treeview_toolbar)
|
|
41
51
|
|
|
52
|
+
# === 分割器 (左:圖形檢視 / 右:提交表格) ===
|
|
42
53
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
43
|
-
self.graph_view = CommitGraphView()
|
|
44
|
-
self.commit_table = CommitTable()
|
|
54
|
+
self.graph_view = CommitGraphView() # 提交圖形檢視器 / commit graph view
|
|
55
|
+
self.commit_table = CommitTable() # 提交表格 / commit table
|
|
45
56
|
splitter.addWidget(self.graph_view)
|
|
46
57
|
splitter.addWidget(self.commit_table)
|
|
47
|
-
splitter.setSizes([600, 400])
|
|
48
|
-
git_treeview_main_layout.addWidget(splitter, 1) # 1 表示可伸縮
|
|
58
|
+
splitter.setSizes([600, 400]) # 左右比例 / left-right ratio
|
|
59
|
+
git_treeview_main_layout.addWidget(splitter, 1) # stretch=1 表示可伸縮
|
|
49
60
|
|
|
61
|
+
# === 狀態列 / Status bar ===
|
|
50
62
|
self.status = QStatusBar()
|
|
51
63
|
git_treeview_main_layout.addWidget(self.status)
|
|
52
64
|
|
|
65
|
+
# === Git 相關屬性 ===
|
|
53
66
|
self.repo_path = None
|
|
54
67
|
self.git = None
|
|
55
68
|
|
|
69
|
+
# === 檔案監控器 / File system watcher ===
|
|
56
70
|
self.watcher = QFileSystemWatcher()
|
|
57
71
|
self.watcher.directoryChanged.connect(self._on_git_changed)
|
|
58
72
|
self.watcher.fileChanged.connect(self._on_git_changed)
|
|
59
73
|
|
|
74
|
+
# === 定時器 (延遲刷新) / Timer for delayed refresh ===
|
|
60
75
|
self.refresh_timer = QTimer()
|
|
61
76
|
self.refresh_timer.setSingleShot(True)
|
|
62
77
|
self.refresh_timer.timeout.connect(self.refresh_graph)
|
|
63
78
|
|
|
79
|
+
# === 表格選擇事件 / Table selection event ===
|
|
64
80
|
self.commit_table.selectionModel().selectionChanged.connect(self._on_table_selection)
|
|
65
81
|
|
|
66
82
|
def _on_table_selection(self, selected, _):
|
|
83
|
+
"""
|
|
84
|
+
當使用者在提交表格中選擇某一列時,讓圖形檢視器聚焦到該行
|
|
85
|
+
When user selects a row in commit table, focus graph view on that row
|
|
86
|
+
"""
|
|
67
87
|
if not selected.indexes():
|
|
68
88
|
return
|
|
69
89
|
row = selected.indexes()[0].row()
|
|
70
90
|
self.graph_view.focus_row(row)
|
|
71
91
|
|
|
72
92
|
def open_repo(self):
|
|
73
|
-
|
|
93
|
+
"""
|
|
94
|
+
開啟 Git 專案並初始化圖形檢視
|
|
95
|
+
Open a Git repository and set up the graph view
|
|
96
|
+
"""
|
|
74
97
|
path = QFileDialog.getExistingDirectory(self, self.language_wrapper_get("git_graph_menu_open_repo"))
|
|
75
98
|
if not path:
|
|
76
99
|
return
|
|
77
100
|
repo_path = Path(path)
|
|
78
101
|
git = GitCLI(repo_path)
|
|
79
102
|
if not git.is_git_repo():
|
|
103
|
+
# 若不是 Git 專案,顯示警告
|
|
104
|
+
# Show warning if not a Git repo
|
|
80
105
|
QMessageBox.warning(self, self.language_wrapper_get("git_graph_title"),
|
|
81
106
|
self.language_wrapper_get("git_graph_error_not_git"))
|
|
82
107
|
return
|
|
@@ -86,11 +111,15 @@ class GitTreeViewGUI(QWidget):
|
|
|
86
111
|
self.refresh_graph()
|
|
87
112
|
|
|
88
113
|
def _setup_watcher(self):
|
|
114
|
+
"""
|
|
115
|
+
設定檔案監控,監控 .git 目錄與相關檔案
|
|
116
|
+
Setup file watcher to monitor .git directory and related files
|
|
117
|
+
"""
|
|
89
118
|
self.watcher.removePaths(self.watcher.files())
|
|
90
119
|
self.watcher.removePaths(self.watcher.directories())
|
|
91
120
|
if not self.repo_path:
|
|
92
121
|
return
|
|
93
|
-
git_dir = self.repo_path / ".git_client"
|
|
122
|
+
git_dir = self.repo_path / ".git_client" # ⚠️ 這裡應該是 ".git",可能是自訂路徑
|
|
94
123
|
if git_dir.exists():
|
|
95
124
|
self.watcher.addPath(str(git_dir))
|
|
96
125
|
for f in ["HEAD", "packed-refs"]:
|
|
@@ -102,21 +131,26 @@ class GitTreeViewGUI(QWidget):
|
|
|
102
131
|
self.watcher.addPath(str(refs_dir))
|
|
103
132
|
|
|
104
133
|
def _on_git_changed(self):
|
|
105
|
-
|
|
134
|
+
"""
|
|
135
|
+
當 Git 目錄變更時,啟動延遲刷新
|
|
136
|
+
When Git directory changes, start delayed refresh
|
|
137
|
+
"""
|
|
106
138
|
self.refresh_timer.start(500)
|
|
107
139
|
|
|
108
140
|
def refresh_graph(self):
|
|
109
|
-
|
|
141
|
+
"""
|
|
142
|
+
刷新 Git 提交圖與提交表格
|
|
143
|
+
Refresh Git commit graph and commit table
|
|
144
|
+
"""
|
|
110
145
|
if not self.git:
|
|
111
146
|
return
|
|
112
147
|
try:
|
|
113
|
-
refs = self.git.get_all_refs()
|
|
114
|
-
commits = self.git.get_commits(max_count=500)
|
|
148
|
+
refs = self.git.get_all_refs() # 取得所有 refs
|
|
149
|
+
commits = self.git.get_commits(max_count=500) # 取得最近 500 筆提交
|
|
115
150
|
graph = CommitGraph()
|
|
116
|
-
graph.build(commits, refs)
|
|
117
|
-
self.graph_view.set_graph(graph)
|
|
118
|
-
self.commit_table.set_commits(commits)
|
|
151
|
+
graph.build(commits, refs) # 建立提交圖
|
|
152
|
+
self.graph_view.set_graph(graph) # 更新圖形檢視
|
|
153
|
+
self.commit_table.set_commits(commits) # 更新提交表格
|
|
119
154
|
except Exception as e:
|
|
120
155
|
QMessageBox.critical(self, self.language_wrapper_get("git_graph_title"),
|
|
121
|
-
f"{self.language_wrapper_get('git_graph_error_exec_failed')}\n{e}")
|
|
122
|
-
|
|
156
|
+
f"{self.language_wrapper_get('git_graph_error_exec_failed')}\n{e}")
|
|
@@ -14,18 +14,18 @@ from PySide6.QtWidgets import (
|
|
|
14
14
|
from je_editor.git_client.commit_graph import CommitGraph
|
|
15
15
|
from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
|
|
16
16
|
|
|
17
|
-
# Layout constants
|
|
18
|
-
NODE_RADIUS = 6
|
|
19
|
-
ROW_HEIGHT = 28
|
|
20
|
-
LANE_WIDTH = 22
|
|
21
|
-
LEFT_MARGIN = 220
|
|
22
|
-
|
|
23
|
-
# Colors
|
|
24
|
-
EDGE_COLOR = QColor("#888")
|
|
25
|
-
TEXT_COLOR = QColor("#222")
|
|
26
|
-
BG_COLOR = QColor("#ffffff")
|
|
27
|
-
|
|
28
|
-
#
|
|
17
|
+
# === 版面配置常數 / Layout constants ===
|
|
18
|
+
NODE_RADIUS = 6 # 節點半徑 / node radius
|
|
19
|
+
ROW_HEIGHT = 28 # 每一列高度 / row height
|
|
20
|
+
LANE_WIDTH = 22 # 每一條 lane 的寬度 / lane width
|
|
21
|
+
LEFT_MARGIN = 220 # 左側 SHA/訊息文字區域保留空間 / left margin for SHA/message text
|
|
22
|
+
|
|
23
|
+
# === 顏色設定 / Colors ===
|
|
24
|
+
EDGE_COLOR = QColor("#888") # 邊線顏色 / edge color
|
|
25
|
+
TEXT_COLOR = QColor("#222") # 文字顏色 / text color
|
|
26
|
+
BG_COLOR = QColor("#ffffff") # 背景顏色 / background color
|
|
27
|
+
|
|
28
|
+
# Lane 顏色調色盤 / Lane color palette
|
|
29
29
|
LANE_COLORS = [
|
|
30
30
|
QColor("#4C78A8"),
|
|
31
31
|
QColor("#F58518"),
|
|
@@ -41,24 +41,38 @@ LANE_COLORS = [
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def lane_color(lane: int) -> QColor:
|
|
44
|
+
"""
|
|
45
|
+
根據 lane index 回傳顏色
|
|
46
|
+
Return color based on lane index
|
|
47
|
+
"""
|
|
44
48
|
if lane < 0:
|
|
45
49
|
return QColor("#999")
|
|
46
50
|
return LANE_COLORS[lane % len(LANE_COLORS)]
|
|
47
51
|
|
|
48
52
|
|
|
49
53
|
class CommitGraphView(QGraphicsView):
|
|
54
|
+
"""
|
|
55
|
+
Git Commit Graph 視覺化檢視器
|
|
56
|
+
Git commit graph visualization view
|
|
57
|
+
"""
|
|
58
|
+
|
|
50
59
|
def __init__(self, parent=None):
|
|
51
60
|
super().__init__(parent)
|
|
52
61
|
self.language_wrapper_get = language_wrapper.language_word_dict.get
|
|
62
|
+
|
|
63
|
+
# 設定背景與抗鋸齒 / set background and antialiasing
|
|
53
64
|
self.setBackgroundBrush(QBrush(BG_COLOR))
|
|
54
65
|
self.setRenderHints(
|
|
55
66
|
self.renderHints()
|
|
56
67
|
| QPainter.RenderHint.Antialiasing
|
|
57
68
|
| QPainter.RenderHint.TextAntialiasing
|
|
58
69
|
)
|
|
70
|
+
|
|
71
|
+
# 啟用拖曳與縮放模式 / enable drag and zoom mode
|
|
59
72
|
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
|
|
60
73
|
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
|
|
61
74
|
|
|
75
|
+
# 建立場景 / create scene
|
|
62
76
|
self._scene = QGraphicsScene(self)
|
|
63
77
|
self.setScene(self._scene)
|
|
64
78
|
|
|
@@ -67,21 +81,32 @@ class CommitGraphView(QGraphicsView):
|
|
|
67
81
|
self._zoom = 1.0
|
|
68
82
|
|
|
69
83
|
def set_graph(self, graph: CommitGraph):
|
|
84
|
+
"""
|
|
85
|
+
設定 commit graph 並重新繪製
|
|
86
|
+
Set commit graph and redraw
|
|
87
|
+
"""
|
|
70
88
|
self.graph = graph
|
|
71
89
|
self._redraw()
|
|
72
90
|
|
|
73
91
|
def _lane_x(self, lane: int) -> float:
|
|
92
|
+
# 計算 lane 的 X 座標 / calculate X position for lane
|
|
74
93
|
return lane * LANE_WIDTH + NODE_RADIUS * 2
|
|
75
94
|
|
|
76
95
|
def _row_y(self, row: int) -> float:
|
|
96
|
+
# 計算 row 的 Y 座標 / calculate Y position for row
|
|
77
97
|
return row * ROW_HEIGHT + NODE_RADIUS * 2
|
|
78
98
|
|
|
79
99
|
def _redraw(self):
|
|
100
|
+
"""
|
|
101
|
+
重新繪製整個 commit graph
|
|
102
|
+
Redraw the entire commit graph
|
|
103
|
+
"""
|
|
80
104
|
self._scene.clear()
|
|
81
105
|
if not self.graph or not self.graph.nodes:
|
|
82
106
|
self._scene.setSceneRect(QRectF(0, 0, 800, 400))
|
|
83
107
|
return
|
|
84
108
|
|
|
109
|
+
# === 繪製邊線 (父子關係) / Draw edges (parent-child relationships) ===
|
|
85
110
|
edge_pen = QPen(EDGE_COLOR, 2)
|
|
86
111
|
for row, node in enumerate(self.graph.nodes):
|
|
87
112
|
if not node.parent_shas:
|
|
@@ -95,6 +120,8 @@ class CommitGraphView(QGraphicsView):
|
|
|
95
120
|
parent_node = self.graph.nodes[prow]
|
|
96
121
|
x1 = self._lane_x(parent_node.lane_index)
|
|
97
122
|
y1 = self._row_y(prow)
|
|
123
|
+
|
|
124
|
+
# 使用三次貝茲曲線繪製邊線 / cubic Bezier curve for edge
|
|
98
125
|
path = QPainterPath(QPointF(x0, y0))
|
|
99
126
|
ctrl_y = (y0 + y1) / 2.0
|
|
100
127
|
path.cubicTo(QPointF(x0, ctrl_y), QPointF(x1, ctrl_y), QPointF(x1, y1))
|
|
@@ -102,6 +129,7 @@ class CommitGraphView(QGraphicsView):
|
|
|
102
129
|
edge_item.setPen(edge_pen)
|
|
103
130
|
self._scene.addItem(edge_item)
|
|
104
131
|
|
|
132
|
+
# === 繪製節點 (commit 圓點) / Draw nodes (commit circles) ===
|
|
105
133
|
for row, node in enumerate(self.graph.nodes):
|
|
106
134
|
cx = self._lane_x(node.lane_index)
|
|
107
135
|
cy = self._row_y(row)
|
|
@@ -111,6 +139,7 @@ class CommitGraphView(QGraphicsView):
|
|
|
111
139
|
)
|
|
112
140
|
circle.setBrush(QBrush(lane_color(node.lane_index)))
|
|
113
141
|
circle.setPen(QPen(Qt.PenStyle.NoPen))
|
|
142
|
+
# 設定 tooltip 顯示 commit 資訊 / tooltip with commit info
|
|
114
143
|
circle.setToolTip(self.language_wrapper_get(
|
|
115
144
|
"git_graph_tooltip_commit"
|
|
116
145
|
).format(short=node.commit_sha[:7],
|
|
@@ -119,11 +148,13 @@ class CommitGraphView(QGraphicsView):
|
|
|
119
148
|
msg=node.commit_message))
|
|
120
149
|
self._scene.addItem(circle)
|
|
121
150
|
|
|
151
|
+
# 在左側顯示行號 / show row number on the left
|
|
122
152
|
label_item = QGraphicsSimpleTextItem(str(row + 1))
|
|
123
153
|
label_item.setBrush(QBrush(TEXT_COLOR))
|
|
124
154
|
label_item.setPos(-30, cy - NODE_RADIUS * 2)
|
|
125
155
|
self._scene.addItem(label_item)
|
|
126
156
|
|
|
157
|
+
# 設定場景大小 / set scene rect
|
|
127
158
|
self._scene.setSceneRect(
|
|
128
159
|
QRectF(-40, 0,
|
|
129
160
|
self._lane_x(max(n.lane_index for n in self.graph.nodes) + 1) + self._padding,
|
|
@@ -131,15 +162,19 @@ class CommitGraphView(QGraphicsView):
|
|
|
131
162
|
)
|
|
132
163
|
|
|
133
164
|
def _apply_initial_view(self):
|
|
134
|
-
|
|
165
|
+
"""
|
|
166
|
+
初始縮放設定
|
|
167
|
+
Apply initial zoom setting
|
|
168
|
+
"""
|
|
135
169
|
self._zoom = 0.9
|
|
136
170
|
self._apply_zoom_transform()
|
|
137
171
|
|
|
138
|
-
#
|
|
172
|
+
# === 滑鼠滾輪縮放 / Mouse wheel zoom ===
|
|
139
173
|
def wheelEvent(self, event):
|
|
140
174
|
if not self._scene.items():
|
|
141
175
|
return
|
|
142
176
|
|
|
177
|
+
# Ctrl+滾輪縮放,否則正常滾動
|
|
143
178
|
# Ctrl+Wheel to zoom, else scroll normally
|
|
144
179
|
if event.modifiers() & Qt.KeyboardModifier.ControlModifier:
|
|
145
180
|
angle = event.angleDelta().y()
|
|
@@ -151,24 +186,33 @@ class CommitGraphView(QGraphicsView):
|
|
|
151
186
|
super().wheelEvent(event)
|
|
152
187
|
|
|
153
188
|
def _apply_zoom_transform(self):
|
|
189
|
+
"""
|
|
190
|
+
套用縮放轉換
|
|
191
|
+
Apply zoom transform
|
|
192
|
+
"""
|
|
154
193
|
t = QTransform()
|
|
155
194
|
t.scale(self._zoom, self._zoom)
|
|
156
195
|
self.setTransform(t)
|
|
157
196
|
|
|
158
197
|
def resizeEvent(self, event):
|
|
198
|
+
"""
|
|
199
|
+
視窗大小改變時的事件
|
|
200
|
+
Handle resize event (keep scene rect, no auto-fit)
|
|
201
|
+
"""
|
|
159
202
|
super().resizeEvent(event)
|
|
160
|
-
#
|
|
161
|
-
#
|
|
203
|
+
# 保持場景大小,不自動縮放
|
|
204
|
+
# Keep scene rect; do not auto-fit
|
|
162
205
|
|
|
163
|
-
#
|
|
206
|
+
# === 外部控制輔助方法 / Helper for external controllers ===
|
|
164
207
|
def focus_row(self, row: int):
|
|
165
208
|
"""
|
|
166
|
-
|
|
209
|
+
將檢視器聚焦到指定的 row
|
|
210
|
+
Center the view around a specific row
|
|
167
211
|
"""
|
|
168
212
|
if not self.graph or row < 0 or row >= len(self.graph.nodes):
|
|
169
213
|
return
|
|
170
214
|
y = self._row_y(row)
|
|
171
215
|
rect = QRectF(0, y - ROW_HEIGHT * 2, self._scene.width(), ROW_HEIGHT * 4)
|
|
172
216
|
self.fitInView(rect, Qt.AspectRatioMode.KeepAspectRatio)
|
|
173
|
-
#
|
|
174
|
-
self._apply_zoom_transform()
|
|
217
|
+
# 聚焦後恢復使用者縮放比例 / restore user zoom after focusing
|
|
218
|
+
self._apply_zoom_transform()
|
|
@@ -2,18 +2,33 @@ from queue import Queue
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class AIConfig(object):
|
|
5
|
+
"""
|
|
6
|
+
AIConfig 類別:用來管理 AI 模型的設定與訊息佇列
|
|
7
|
+
AIConfig class: manages AI model configuration and message queue
|
|
8
|
+
"""
|
|
5
9
|
|
|
6
10
|
def __init__(self):
|
|
11
|
+
# 當前 AI 模型的系統提示詞 (system prompt)
|
|
12
|
+
# Current AI model system prompt
|
|
7
13
|
self.current_ai_model_system_prompt: str = ""
|
|
14
|
+
|
|
15
|
+
# 可選擇的 AI 模型設定字典
|
|
16
|
+
# Dictionary of choosable AI model configurations
|
|
17
|
+
# 結構: { "AI_model": { "ai_base_url": ..., "ai_api_key": ..., "chat_model": ..., "prompt_template": ... } }
|
|
8
18
|
self.choosable_ai: dict[str, dict[str, str]] = {
|
|
9
19
|
"AI_model": {
|
|
10
|
-
"ai_base_url": "",
|
|
11
|
-
"ai_api_key": "",
|
|
12
|
-
"chat_model": "",
|
|
13
|
-
"prompt_template": "",
|
|
20
|
+
"ai_base_url": "", # AI 服務的基礎 URL / Base URL of AI service
|
|
21
|
+
"ai_api_key": "", # API 金鑰 / API key
|
|
22
|
+
"chat_model": "", # 模型名稱 / Model name
|
|
23
|
+
"prompt_template": "", # 提示詞模板 / Prompt template
|
|
14
24
|
}
|
|
15
25
|
}
|
|
26
|
+
|
|
27
|
+
# 訊息佇列,用來暫存與 AI 的互動訊息
|
|
28
|
+
# Message queue for storing AI interaction messages
|
|
16
29
|
self.message_queue = Queue()
|
|
17
30
|
|
|
18
31
|
|
|
19
|
-
|
|
32
|
+
# 建立全域唯一的 AIConfig 實例
|
|
33
|
+
# Create a global singleton instance of AIConfig
|
|
34
|
+
ai_config = AIConfig()
|
|
@@ -5,13 +5,32 @@ from je_editor.pyside_ui.main_ui.ai_widget.langchain_interface import LangChainI
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class AskThread(Thread):
|
|
8
|
+
"""
|
|
9
|
+
AskThread 類別:用來在背景執行緒中呼叫 AI 模型,避免阻塞主執行緒
|
|
10
|
+
AskThread class: runs AI model calls in a background thread to avoid blocking the main thread
|
|
11
|
+
"""
|
|
8
12
|
|
|
9
13
|
def __init__(self, lang_chain_interface: LangChainInterface, prompt):
|
|
14
|
+
"""
|
|
15
|
+
初始化 AskThread
|
|
16
|
+
Initialize AskThread
|
|
17
|
+
:param lang_chain_interface: LangChainInterface 實例,用來呼叫 AI 模型
|
|
18
|
+
LangChainInterface instance for calling AI model
|
|
19
|
+
:param prompt: 傳給 AI 模型的提示詞
|
|
20
|
+
Prompt to send to the AI model
|
|
21
|
+
"""
|
|
10
22
|
super().__init__()
|
|
11
23
|
self.lang_chain_interface = lang_chain_interface
|
|
12
24
|
self.prompt = prompt
|
|
13
25
|
|
|
14
|
-
|
|
15
26
|
def run(self):
|
|
27
|
+
"""
|
|
28
|
+
執行緒的主要邏輯:
|
|
29
|
+
1. 呼叫 AI 模型並取得回應
|
|
30
|
+
2. 將回應放入 ai_config 的 message_queue 中,供主程式使用
|
|
31
|
+
Thread main logic:
|
|
32
|
+
1. Call AI model and get response
|
|
33
|
+
2. Put response into ai_config.message_queue for main program to consume
|
|
34
|
+
"""
|
|
16
35
|
ai_response = self.lang_chain_interface.call_ai_model(prompt=self.prompt)
|
|
17
36
|
ai_config.message_queue.put(ai_response)
|
|
@@ -23,73 +23,86 @@ class ChatUI(QWidget):
|
|
|
23
23
|
|
|
24
24
|
def __init__(self, main_window: EditorMain):
|
|
25
25
|
super().__init__()
|
|
26
|
-
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
|
26
|
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) # 關閉視窗時自動釋放資源 / Auto delete on close
|
|
27
27
|
self.main_window = main_window
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
self.chat_panel
|
|
31
|
-
self.chat_panel.
|
|
32
|
-
self.
|
|
28
|
+
|
|
29
|
+
# ---------------- Chat Panel 聊天面板 ----------------
|
|
30
|
+
self.chat_panel = QPlainTextEdit() # 顯示聊天訊息的文字框 / Text area for chat messages
|
|
31
|
+
self.chat_panel.setLineWrapMode(self.chat_panel.LineWrapMode.NoWrap) # 不自動換行 / Disable line wrap
|
|
32
|
+
self.chat_panel.setReadOnly(True) # 設為唯讀,避免使用者直接輸入 / Read-only
|
|
33
|
+
self.chat_panel_scroll_area = QScrollArea() # 加入滾動區域 / Scroll area for chat panel
|
|
33
34
|
self.chat_panel_scroll_area.setWidgetResizable(True)
|
|
34
35
|
self.chat_panel_scroll_area.setViewportMargins(0, 0, 0, 0)
|
|
35
36
|
self.chat_panel_scroll_area.setWidget(self.chat_panel)
|
|
36
|
-
self.chat_panel.setFont(QFontDatabase.font(self.font().family(), "", 16))
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
self.chat_panel.setFont(QFontDatabase.font(self.font().family(), "", 16)) # 設定字體大小 / Set font size
|
|
38
|
+
|
|
39
|
+
# ---------------- Prompt Input 輸入框 ----------------
|
|
40
|
+
self.prompt_input = QLineEdit() # 使用者輸入提示詞 / Input field for prompts
|
|
39
41
|
self.prompt_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
|
40
|
-
self.prompt_input.returnPressed.connect(self.call_ai_model)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
self.
|
|
44
|
-
|
|
42
|
+
self.prompt_input.returnPressed.connect(self.call_ai_model) # 按 Enter 時呼叫 AI / Call AI on Enter
|
|
43
|
+
|
|
44
|
+
# ---------------- Font Size Combobox 字體大小選單 ----------------
|
|
45
|
+
self.font_size_label = QLabel(language_wrapper.language_word_dict.get("font_size")) # 標籤 / Label
|
|
46
|
+
self.font_size_combobox = QComboBox() # 下拉選單 / Dropdown for font size
|
|
47
|
+
for font_size in range(2, 101, 2): # 提供 2~100 的字體大小選項 / Font size options
|
|
45
48
|
self.font_size_combobox.addItem(str(font_size))
|
|
46
|
-
self.font_size_combobox.setCurrentText("16")
|
|
49
|
+
self.font_size_combobox.setCurrentText("16") # 預設字體大小 / Default font size
|
|
47
50
|
self.font_size_combobox.currentTextChanged.connect(self.update_panel_text_size)
|
|
48
|
-
|
|
51
|
+
|
|
52
|
+
# ---------------- Buttons 按鈕 ----------------
|
|
49
53
|
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)
|
|
54
|
+
self.set_ai_config_button.clicked.connect(self.set_ai_config) # 開啟 AI 設定視窗 / Open AI config dialog
|
|
55
|
+
|
|
51
56
|
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))
|
|
57
|
+
self.load_ai_config_button.clicked.connect(lambda: self.load_ai_config(show_load_complete=True)) # 載入設定 / Load config
|
|
58
|
+
|
|
53
59
|
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
|
-
|
|
60
|
+
self.call_ai_model_button.clicked.connect(self.call_ai_model) # 呼叫 AI / Call AI
|
|
61
|
+
|
|
62
|
+
# ---------------- Layout 版面配置 ----------------
|
|
56
63
|
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
|
-
#
|
|
65
|
-
self.ai_config: AIConfig = ai_config
|
|
66
|
-
self.lang_chain_interface: Union[LangChainInterface, None] = None
|
|
67
|
-
self.set_ai_config_dialog = None
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
self.pull_message_timer
|
|
64
|
+
self.grid_layout.addWidget(self.chat_panel_scroll_area, 0, 0, 1, 4) # 聊天面板 / Chat panel
|
|
65
|
+
self.grid_layout.addWidget(self.call_ai_model_button, 1, 0) # 呼叫 AI 按鈕 / Call AI button
|
|
66
|
+
self.grid_layout.addWidget(self.font_size_combobox, 1, 1) # 字體大小選單 / Font size combobox
|
|
67
|
+
self.grid_layout.addWidget(self.set_ai_config_button, 1, 2) # 設定 AI 按鈕 / Set AI config button
|
|
68
|
+
self.grid_layout.addWidget(self.load_ai_config_button, 1, 3) # 載入設定按鈕 / Load AI config button
|
|
69
|
+
self.grid_layout.addWidget(self.prompt_input, 2, 0, 1, 4) # 輸入框 / Prompt input
|
|
70
|
+
|
|
71
|
+
# ---------------- Variables 變數 ----------------
|
|
72
|
+
self.ai_config: AIConfig = ai_config # AI 設定物件 / AI config object
|
|
73
|
+
self.lang_chain_interface: Union[LangChainInterface, None] = None # LangChain 介面 / LangChain interface
|
|
74
|
+
self.set_ai_config_dialog = None # 設定對話框 / Config dialog
|
|
75
|
+
|
|
76
|
+
# ---------------- Timer 計時器 ----------------
|
|
77
|
+
self.pull_message_timer = QTimer(self) # 定時檢查訊息佇列 / Timer to pull messages
|
|
78
|
+
self.pull_message_timer.setInterval(1000) # 每秒檢查一次 / Check every 1 second
|
|
71
79
|
self.pull_message_timer.timeout.connect(self.pull_message)
|
|
72
80
|
self.pull_message_timer.start()
|
|
73
81
|
|
|
74
|
-
# Set
|
|
82
|
+
# ---------------- Set Layout 設定版面 ----------------
|
|
75
83
|
self.setLayout(self.grid_layout)
|
|
76
84
|
|
|
85
|
+
# ---------------- Load AI Config 載入 AI 設定 ----------------
|
|
77
86
|
self.load_ai_config()
|
|
78
87
|
|
|
88
|
+
# 更新聊天面板字體大小 / Update chat panel font size
|
|
79
89
|
def update_panel_text_size(self):
|
|
80
90
|
self.chat_panel.setFont(
|
|
81
91
|
QFontDatabase.font(self.font().family(), "", int(self.font_size_combobox.currentText())))
|
|
82
92
|
|
|
93
|
+
# 載入 AI 設定檔 / Load AI configuration file
|
|
83
94
|
def load_ai_config(self, show_load_complete: bool = False):
|
|
84
95
|
ai_config_file = Path(str(Path.cwd()) + "/" + ".jeditor/ai_config.json")
|
|
85
96
|
if ai_config_file.exists():
|
|
86
97
|
with open(ai_config_file, "r", encoding="utf-8"):
|
|
87
98
|
json_data: dict = read_json(str(ai_config_file))
|
|
88
99
|
if json_data:
|
|
100
|
+
# 確認 AI_model 設定存在且格式正確 / Ensure AI_model config exists and valid
|
|
89
101
|
if json_data.get("AI_model") and len(json_data.get("AI_model")) == 4:
|
|
90
102
|
ai_info: dict = json_data.get("AI_model")
|
|
91
103
|
if ai_info.get("ai_base_url") and ai_info.get("chat_model"):
|
|
92
|
-
ai_config.choosable_ai.update(json_data)
|
|
104
|
+
ai_config.choosable_ai.update(json_data) # 更新全域設定 / Update global config
|
|
105
|
+
# 建立 LangChain 介面 / Initialize LangChain interface
|
|
93
106
|
self.lang_chain_interface = LangChainInterface(
|
|
94
107
|
main_window=self,
|
|
95
108
|
api_key=ai_info.get("ai_api_key"),
|
|
@@ -103,12 +116,14 @@ class ChatUI(QWidget):
|
|
|
103
116
|
load_complete.setText(language_wrapper.language_word_dict.get("load_ai_messagebox_text"))
|
|
104
117
|
load_complete.exec()
|
|
105
118
|
|
|
106
|
-
|
|
119
|
+
# 呼叫 AI 模型 / Call AI model
|
|
107
120
|
def call_ai_model(self):
|
|
108
121
|
if isinstance(self.lang_chain_interface, LangChainInterface):
|
|
122
|
+
# 建立新執行緒處理 AI 請求 / Start a new thread for AI request
|
|
109
123
|
thread = AskThread(lang_chain_interface=self.lang_chain_interface, prompt=self.prompt_input.text())
|
|
110
124
|
thread.start()
|
|
111
125
|
else:
|
|
126
|
+
# 若未正確設定 AI,顯示錯誤訊息 / Show error if AI not configured
|
|
112
127
|
ai_info = ai_config.choosable_ai.get('AI_model')
|
|
113
128
|
QMessageBox.warning(self,
|
|
114
129
|
language_wrapper.language_word_dict.get("call_ai_model_error_title"),
|
|
@@ -118,13 +133,13 @@ class ChatUI(QWidget):
|
|
|
118
133
|
f"chat_model: {ai_info.get('chat_model')}, \n"
|
|
119
134
|
f"prompt_template: {ai_info.get('prompt_template')}"))
|
|
120
135
|
|
|
136
|
+
# 從訊息佇列中取出 AI 回覆並顯示 / Pull AI response from queue
|
|
121
137
|
def pull_message(self):
|
|
122
138
|
if not ai_config.message_queue.empty():
|
|
123
139
|
ai_response = ai_config.message_queue.get_nowait()
|
|
124
|
-
self.chat_panel.appendPlainText(ai_response)
|
|
140
|
+
self.chat_panel.appendPlainText(ai_response) # 顯示回覆 / Display response
|
|
125
141
|
self.chat_panel.appendPlainText("\n")
|
|
126
142
|
|
|
143
|
+
# 開啟 AI 設定對話框 / Open AI config dialog
|
|
127
144
|
def set_ai_config(self):
|
|
128
|
-
|
|
129
|
-
self.set_ai_config_dialog = SetAIDialog()
|
|
130
|
-
self.set_ai_config_dialog.show()
|
|
145
|
+
self.set_ai_config
|