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.

Files changed (71) hide show
  1. je_editor/git_client/commit_graph.py +7 -7
  2. je_editor/git_client/git_action.py +0 -7
  3. je_editor/pyside_ui/browser/browser_widget.py +24 -11
  4. je_editor/pyside_ui/browser/main_browser_widget.py +40 -27
  5. je_editor/pyside_ui/code/auto_save/auto_save_manager.py +34 -2
  6. je_editor/pyside_ui/code/auto_save/auto_save_thread.py +19 -6
  7. je_editor/pyside_ui/code/code_format/pep8_format.py +53 -9
  8. je_editor/pyside_ui/code/code_process/code_exec.py +88 -52
  9. je_editor/pyside_ui/code/plaintext_code_edit/code_edit_plaintext.py +116 -55
  10. je_editor/pyside_ui/code/running_process_manager.py +19 -1
  11. je_editor/pyside_ui/code/shell_process/shell_exec.py +71 -48
  12. je_editor/pyside_ui/code/syntax/python_syntax.py +45 -10
  13. je_editor/pyside_ui/code/syntax/syntax_setting.py +40 -12
  14. je_editor/pyside_ui/code/textedit_code_result/code_record.py +34 -12
  15. je_editor/pyside_ui/code/variable_inspector/inspector_gui.py +53 -6
  16. je_editor/pyside_ui/dialog/ai_dialog/set_ai_dialog.py +30 -3
  17. je_editor/pyside_ui/dialog/file_dialog/create_file_dialog.py +35 -2
  18. je_editor/pyside_ui/dialog/file_dialog/open_file_dialog.py +33 -5
  19. je_editor/pyside_ui/dialog/file_dialog/save_file_dialog.py +25 -3
  20. je_editor/pyside_ui/dialog/search_ui/search_error_box.py +26 -1
  21. je_editor/pyside_ui/dialog/search_ui/search_text_box.py +26 -1
  22. je_editor/pyside_ui/git_ui/code_diff_compare/code_diff_viewer_widget.py +11 -11
  23. je_editor/pyside_ui/git_ui/git_client/commit_table.py +46 -8
  24. je_editor/pyside_ui/git_ui/git_client/git_branch_tree_widget.py +49 -15
  25. je_editor/pyside_ui/git_ui/git_client/graph_view.py +64 -20
  26. je_editor/pyside_ui/main_ui/ai_widget/ai_config.py +20 -5
  27. je_editor/pyside_ui/main_ui/ai_widget/ask_thread.py +20 -1
  28. je_editor/pyside_ui/main_ui/ai_widget/chat_ui.py +56 -41
  29. je_editor/pyside_ui/main_ui/ai_widget/langchain_interface.py +45 -6
  30. je_editor/pyside_ui/main_ui/console_widget/console_gui.py +44 -12
  31. je_editor/pyside_ui/main_ui/console_widget/qprocess_adapter.py +34 -13
  32. je_editor/pyside_ui/main_ui/dock/destroy_dock.py +33 -2
  33. je_editor/pyside_ui/main_ui/editor/editor_widget.py +104 -20
  34. je_editor/pyside_ui/main_ui/editor/editor_widget_dock.py +34 -7
  35. je_editor/pyside_ui/main_ui/editor/process_input.py +38 -11
  36. je_editor/pyside_ui/main_ui/ipython_widget/rich_jupyter.py +46 -11
  37. je_editor/pyside_ui/main_ui/main_editor.py +175 -37
  38. je_editor/pyside_ui/main_ui/menu/check_style_menu/build_check_style_menu.py +51 -28
  39. je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py +80 -22
  40. je_editor/pyside_ui/main_ui/menu/file_menu/build_file_menu.py +70 -17
  41. je_editor/pyside_ui/main_ui/menu/help_menu/build_help_menu.py +34 -3
  42. je_editor/pyside_ui/main_ui/menu/language_menu/build_language_server.py +41 -1
  43. je_editor/pyside_ui/main_ui/menu/python_env_menu/build_venv_menu.py +100 -42
  44. je_editor/pyside_ui/main_ui/menu/run_menu/build_run_menu.py +57 -7
  45. je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_debug_menu.py +50 -4
  46. je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_program_menu.py +52 -6
  47. je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_shell_menu.py +44 -4
  48. je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/utils.py +23 -1
  49. je_editor/pyside_ui/main_ui/menu/set_menu_bar.py +37 -12
  50. je_editor/pyside_ui/main_ui/menu/style_menu/build_style_menu.py +44 -7
  51. je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py +126 -28
  52. je_editor/pyside_ui/main_ui/menu/text_menu/build_text_menu.py +65 -1
  53. je_editor/pyside_ui/main_ui/save_settings/setting_utils.py +18 -1
  54. je_editor/pyside_ui/main_ui/save_settings/user_color_setting_file.py +33 -3
  55. je_editor/pyside_ui/main_ui/save_settings/user_setting_file.py +38 -11
  56. je_editor/pyside_ui/main_ui/system_tray/extend_system_tray.py +39 -2
  57. je_editor/start_editor.py +26 -1
  58. je_editor/utils/encodings/python_encodings.py +101 -98
  59. je_editor/utils/file/open/open_file.py +36 -19
  60. je_editor/utils/file/save/save_file.py +35 -14
  61. je_editor/utils/json/json_file.py +29 -14
  62. je_editor/utils/json_format/json_process.py +33 -2
  63. je_editor/utils/logging/loggin_instance.py +38 -8
  64. je_editor/utils/multi_language/multi_language_wrapper.py +29 -4
  65. je_editor/utils/redirect_manager/redirect_manager_class.py +49 -11
  66. je_editor/utils/venv_check/check_venv.py +45 -15
  67. {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/METADATA +1 -1
  68. {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/RECORD +71 -71
  69. {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/WHEEL +0 -0
  70. {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/licenses/LICENSE +0 -0
  71. {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
- self.model_data = QStandardItemModel(0, 5, self) # 多一欄
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
- for idx, c in enumerate(commits, start=1):
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(idx)), # 行號
20
- QStandardItem(c["sha"][:7]),
21
- QStandardItem(c["message"]),
22
- QStandardItem(c["author"]),
23
- QStandardItem(c["date"]),
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
- self.model_data.appendRow(row)
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
- # Open repo and set up the graph.
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
- # Refresh git tree timer
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
- # Refresh git tree
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 # space for SHA/message text area on the left
22
-
23
- # Colors
24
- EDGE_COLOR = QColor("#888")
25
- TEXT_COLOR = QColor("#222")
26
- BG_COLOR = QColor("#ffffff")
27
-
28
- # A small, repeatable lane palette
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
- # Start with a mild zoom to fit rows comfortably
165
+ """
166
+ 初始縮放設定
167
+ Apply initial zoom setting
168
+ """
135
169
  self._zoom = 0.9
136
170
  self._apply_zoom_transform()
137
171
 
138
- # Zoom and interaction
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
- # Keep scene rect; do not auto-fit. Users control zoom.
161
- # Nothing else needed here.
203
+ # 保持場景大小,不自動縮放
204
+ # Keep scene rect; do not auto-fit
162
205
 
163
- # Optional helpers for external controllers
206
+ # === 外部控制輔助方法 / Helper for external controllers ===
164
207
  def focus_row(self, row: int):
165
208
  """
166
- Center the view around a specific row.
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
- # Restore user zoom preference after focusing
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
- ai_config = AIConfig()
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
- # 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()
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
- # Prompt input
38
- self.prompt_input = QLineEdit()
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
- # 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):
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
- # Buttons
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
- # Add to layout
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
- # 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)
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 layout
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
- # Set and output AI a config file
129
- self.set_ai_config_dialog = SetAIDialog()
130
- self.set_ai_config_dialog.show()
145
+ self.set_ai_config