From bb0b2f3a32b3b5fbf95b85a7421ad03e7a0b0745 Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 02:16:31 +0800 Subject: [PATCH 01/12] Update dev version Update dev version --- .../connect_gui/__init__.py | 0 .../connect_gui/ssh/__init__.py | 0 .../connect_gui/ssh/ssh_command_widget.py | 250 +++++++++ .../connect_gui/ssh/ssh_file_viewer_widget.py | 507 ++++++++++++++++++ .../connect_gui/ssh/ssh_login_widget.py | 67 +++ .../connect_gui/ssh/ssh_main_widget.py | 49 ++ .../connect_gui/url/__init__.py | 0 .../connect_gui/url/ai_code_review_gui.py | 180 +++++++ .../editor_main/main_ui.py | 6 - .../extend_multi_language/extend_english.py | 93 ++++ .../extend_traditional_chinese.py | 92 ++++ .../menu/build_menubar.py | 4 + .../menu/tools/__init__.py | 0 .../menu/tools/tools_menu.py | 75 +++ .../prompt_edit_gui/__init__.py | 0 .../main_prompt_edit_widget.py | 167 ++++++ .../prompt_templates/__init__.py | 0 .../prompt_templates/first_code_review.py | 1 + .../prompt_templates/first_summary_prompt.py | 1 + .../prompt_templates/global_rule.py | 1 + .../prompt_edit_gui/prompt_templates/judge.py | 1 + .../prompt_templates/total_summary.py | 1 + pyproject.toml | 13 +- dev.toml => stable.toml | 13 +- 24 files changed, 1502 insertions(+), 19 deletions(-) create mode 100644 automation_ide/automation_editor_ui/connect_gui/__init__.py create mode 100644 automation_ide/automation_editor_ui/connect_gui/ssh/__init__.py create mode 100644 automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py create mode 100644 automation_ide/automation_editor_ui/connect_gui/ssh/ssh_file_viewer_widget.py create mode 100644 automation_ide/automation_editor_ui/connect_gui/ssh/ssh_login_widget.py create mode 100644 automation_ide/automation_editor_ui/connect_gui/ssh/ssh_main_widget.py create mode 100644 automation_ide/automation_editor_ui/connect_gui/url/__init__.py create mode 100644 automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py create mode 100644 automation_ide/automation_editor_ui/menu/tools/__init__.py create mode 100644 automation_ide/automation_editor_ui/menu/tools/tools_menu.py create mode 100644 automation_ide/automation_editor_ui/prompt_edit_gui/__init__.py create mode 100644 automation_ide/automation_editor_ui/prompt_edit_gui/main_prompt_edit_widget.py create mode 100644 automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/__init__.py create mode 100644 automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/first_code_review.py create mode 100644 automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/first_summary_prompt.py create mode 100644 automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/global_rule.py create mode 100644 automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/judge.py create mode 100644 automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/total_summary.py rename dev.toml => stable.toml (80%) diff --git a/automation_ide/automation_editor_ui/connect_gui/__init__.py b/automation_ide/automation_editor_ui/connect_gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/__init__.py b/automation_ide/automation_editor_ui/connect_gui/ssh/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py new file mode 100644 index 0000000..1dc0fdb --- /dev/null +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py @@ -0,0 +1,250 @@ +import os +import re + +import paramiko +from PySide6.QtCore import QThread, Signal +from PySide6.QtWidgets import ( + QWidget, QLineEdit, QPushButton, + QPlainTextEdit, QHBoxLayout, QVBoxLayout, + QMessageBox +) +from je_editor import language_wrapper + +from automation_ide.automation_editor_ui.connect_gui.ssh.ssh_login_widget import LoginWidget + +ANSI_ESCAPE_PATTERN = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') + + +class SSHReaderThread(QThread): + data_received = Signal(bytes) + closed = Signal(str) + + def __init__(self, chan: paramiko.Channel, parent=None): + super().__init__(parent) + self.chan = chan + self._running = True + + def run(self): + try: + while self._running: + if self.chan.recv_ready(): + data = self.chan.recv(4096) + if data: + self.data_received.emit(data) + + if self.chan.recv_stderr_ready(): + err = self.chan.recv_stderr(4096) + if err: + self.data_received.emit(err) + + if self.chan.closed or self.chan.exit_status_ready(): + break + + self.msleep(10) + except Exception as e: + self.closed.emit( + f"{language_wrapper.language_word_dict.get('ssh_command_widget_error_message_reader_failed')} {e}") + finally: + self.closed.emit( + language_wrapper.language_word_dict.get("ssh_command_widget_log_message_reader_closed")) + + def stop(self): + self._running = False + + +class SSHCommandWidget(QWidget): + def __init__(self, external_login_widget: LoginWidget = None, add_login_widget: bool = True): + super().__init__() + self.setWindowTitle( + language_wrapper.language_word_dict.get("ssh_command_widget_window_title_ssh_command_widget")) + + self.add_login_widget = add_login_widget + + # SSH 相關物件 + self.ssh_client: paramiko.SSHClient | None = None + self.shell_channel: paramiko.Channel | None = None + self.reader_thread: SSHReaderThread | None = None + + if self.add_login_widget: + # 使用獨立的登入介面 + self.login_widget = LoginWidget() + else: + if external_login_widget is None: + external_login_widget = LoginWidget() + self.login_widget = external_login_widget + + # 其他 UI 控制元件 + self.terminal = QPlainTextEdit() + self.terminal.setReadOnly(True) + self.command_input_edit = QLineEdit() + self.command_send_button = QPushButton( + language_wrapper.language_word_dict.get("ssh_command_widget_button_label_send_command")) + + self._setup_ui() + self._bind_events() + + def _setup_ui(self): + self.terminal.setReadOnly(True) + self.terminal.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap) + self.command_input_edit.setPlaceholderText( + language_wrapper.language_word_dict.get("ssh_command_widget_input_placeholder_command_line") + ) + + terminal_panel = QVBoxLayout() + terminal_panel.addWidget(self.terminal) + + command_input_bar = QHBoxLayout() + command_input_bar.addWidget(self.command_input_edit) + command_input_bar.addWidget(self.command_send_button) + + main_widget = QVBoxLayout() + main_widget.addWidget(self.login_widget) # 插入登入介面 + main_widget.addLayout(terminal_panel) + main_widget.addLayout(command_input_bar) + + self.setLayout(main_widget) + + def _bind_events(self): + # 綁定 LoginWidget 的按鈕 + self.login_widget.connect_btn.clicked.connect(self.connect_ssh) + self.login_widget.disconnect_btn.clicked.connect(self.disconnect_ssh) + + # 綁定其他按鈕 + self.command_send_button.clicked.connect(self.send_command) + self.command_input_edit.returnPressed.connect(self.send_command) + + def append_text(self, text: str): + self.terminal.appendPlainText(text) + + def connect_ssh(self): + host = self.login_widget.host_edit.text().strip() + port = self.login_widget.port_spin.value() + user = self.login_widget.user_edit.text().strip() + use_key = self.login_widget.use_key_check.isChecked() + key_path = self.login_widget.key_edit.text().strip() + password = self.login_widget.pass_edit.text() + + if not host or not user: + QMessageBox.warning( + self, + language_wrapper.language_word_dict.get("ssh_command_widget_dialog_title_input_error"), + language_wrapper.language_word_dict.get( + "ssh_command_widget_dialog_message_input_error_host_user_required")) + return + + try: + self.ssh_client = paramiko.SSHClient() + self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + if use_key: + if not os.path.exists(key_path): + QMessageBox.warning( + self, + language_wrapper.language_word_dict.get("ssh_command_widget_dialog_title_key_error"), + language_wrapper.language_word_dict.get("ssh_command_widget_dialog_message_key_file_not_exist")) + return + try: + pkey = None + for KeyType in (paramiko.RSAKey, paramiko.Ed25519Key, paramiko.ECDSAKey): + try: + pkey = KeyType.from_private_key_file(key_path, password if password else None) + break + except Exception as error: + print(error) + continue + if pkey is None: + raise ValueError( + language_wrapper.language_word_dict.get( + "ssh_command_widget_error_message_unsupported_private_key" + )) + self.ssh_client.connect(hostname=host, port=port, username=user, pkey=pkey, timeout=10) + except Exception as e: + raise RuntimeError(f"{ + language_wrapper.language_word_dict.get('ssh_command_widget_error_message_key_auth_failed')} {e}") + else: + self.ssh_client.connect( + hostname=host, port=port, username=user, password=password, timeout=10 + ) + + self.shell_channel = self.ssh_client.invoke_shell(term='xterm', width=120, height=32) + self.shell_channel.settimeout(0.0) + self.reader_thread = SSHReaderThread(self.shell_channel) + self.reader_thread.data_received.connect(self._on_data) + self.reader_thread.closed.connect(self._on_closed) + self.reader_thread.start() + self.login_widget.status_label.setText( + language_wrapper.language_word_dict.get("ssh_command_widget_dialog_title_not_connected")) + self.append_text(f"{ + language_wrapper.language_word_dict.get('ssh_command_widget_log_message_connected')}" + f" {host}:{port} as {user}\n") + except Exception as e: + self.login_widget.status_label.setText( + language_wrapper.language_word_dict.get('ssh_command_widget_status_label_disconnected')) + self.append_text(f"{ + language_wrapper.language_word_dict.get('ssh_command_widget_log_message_error')} {e}\n") + self._cleanup() + + def _on_data(self, data: bytes): + try: + text = data.decode("utf-8", errors="replace") + clean_text = ANSI_ESCAPE_PATTERN.sub('', text) + self.append_text(clean_text) + except Exception as error: + self.append_text(f"{language_wrapper.language_word_dict.get( + 'ssh_command_widget_error_message_decode_failed' + )} {error}\n") + + def _on_closed(self, msg: str): + self.append_text(f"\n{ + language_wrapper.language_word_dict.get('ssh_command_widget_log_message_channel_closed')} {msg}\n") + self.login_widget.status_label.setText(language_wrapper.language_word_dict.get( + 'ssh_command_widget_status_label_disconnected' + )) + + def send_command(self): + cmd = self.command_input_edit.text() + if not cmd: + return + if self.shell_channel and not self.shell_channel.closed: + try: + self.shell_channel.send(cmd + "\n") + self.command_input_edit.clear() + except Exception as e: + self.append_text(f"{language_wrapper.language_word_dict.get( + 'ssh_command_widget_error_message_send_failed' + )} {e}\n") + else: + QMessageBox.information( + self, + language_wrapper.language_word_dict.get('ssh_command_widget_dialog_title_not_connected'), + language_wrapper.language_word_dict.get('ssh_command_widget_dialog_message_not_connected_shell')) + + def disconnect_ssh(self): + self.append_text(f"{ + language_wrapper.language_word_dict.get('ssh_command_widget_log_message_disconnect_in_progress')} \n") + self._cleanup() + self.login_widget.status_label.setText( + language_wrapper.language_word_dict.get('ssh_command_widget_status_label_disconnected')) + + def _cleanup(self): + try: + if self.reader_thread: + self.reader_thread.stop() + self.reader_thread.wait(1000) + except Exception as error: + print(error) + self.reader_thread = None + + try: + if self.shell_channel and not self.shell_channel.closed: + self.shell_channel.close() + except Exception as error: + print(error) + self.shell_channel = None + + try: + if self.ssh_client: + self.ssh_client.close() + except Exception as error: + print(error) + self.ssh_client = None diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_file_viewer_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_file_viewer_widget.py new file mode 100644 index 0000000..76d21be --- /dev/null +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_file_viewer_widget.py @@ -0,0 +1,507 @@ +import os +from pathlib import Path +from typing import Optional + +import paramiko +from PySide6.QtCore import Qt, QEvent +from PySide6.QtWidgets import ( + QWidget, QVBoxLayout, QLineEdit, QPushButton, QTreeWidget, QTreeWidgetItem, + QMenu, QFileDialog, QMessageBox, QSplitter, QInputDialog, QStyle +) +from je_editor import language_wrapper + +from automation_ide.automation_editor_ui.connect_gui.ssh.ssh_login_widget import LoginWidget + + +class SFTPClientWrapper: + """ + Lightweight wrapper around Paramiko SFTP client. + 輕量級封裝 Paramiko SFTP 客戶端,提供基本操作。 + """ + + def __init__(self): + self._ssh: Optional[paramiko.SSHClient] = None + self._sftp: Optional[paramiko.SFTPClient] = None + self.root_path: str = "/" + + def connect(self, host: str, port: int, username: str, password: str): + """ + Establish SSH + SFTP connection. + 建立 SSH + SFTP 連線。 + """ + self.close() + self._ssh = paramiko.SSHClient() + self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self._ssh.connect(hostname=host, port=port, username=username, password=password) + self._sftp = self._ssh.open_sftp() + + def close(self): + """ + Close SFTP and SSH safely. + 安全關閉 SFTP 與 SSH。 + """ + try: + if self._sftp: + self._sftp.close() + finally: + self._sftp = None + try: + if self._ssh: + self._ssh.close() + finally: + self._ssh = None + + @property + def connected(self) -> bool: + """ + Check connection state. + 檢查連線狀態。 + """ + return self._ssh is not None and self._sftp is not None + + def list_dir(self, path: str): + """ + List directory entries with stat attributes. + 列出目錄項目(含屬性)。 + """ + if not self.connected: + raise RuntimeError( + language_wrapper.language_word_dict.get("ssh_command_widget_dialog_title_not_connected") + ) + return self._sftp.listdir_attr(path) + + def is_dir(self, path: str) -> bool: + """ + Determine if target path is a directory. + 判斷路徑是否為目錄。 + """ + if not self.connected: + raise RuntimeError(language_wrapper.language_word_dict.get( + "ssh_command_widget_dialog_title_not_connected" + )) + try: + st = self._sftp.stat(path) + # S_ISDIR check via stat.S_ISDIR + import stat + return stat.S_ISDIR(st.st_mode) + except IOError: + return False + + def mkdir(self, path: str): + """ + Create directory. + 建立目錄。 + """ + self._sftp.mkdir(path) + + def remove_file(self, path: str): + """ + Remove file. + 刪除檔案。 + """ + self._sftp.remove(path) + + def remove_dir(self, path: str): + """ + Remove empty directory. + 刪除空目錄。 + """ + self._sftp.rmdir(path) + + def rename(self, old_path: str, new_path: str): + """ + Rename file/folder. + 重新命名檔案/資料夾。 + """ + self._sftp.rename(old_path, new_path) + + def download(self, remote_path: str, local_path: str): + """ + Download remote to local. + 下載遠端檔案至本地。 + """ + self._sftp.get(remote_path, local_path) + + def upload(self, local_path: str, remote_path: str): + """ + Upload local to remote. + 上傳本地檔案至遠端。 + """ + self._sftp.put(local_path, remote_path) + + +class SSHFileTreeManager(QWidget): + """ + QWidget: connection form + tree + context menu. + QWidget:連線表單 + 樹狀檔案管理 + 右鍵選單。 + """ + + def __init__(self, external_login_widget: LoginWidget = None, add_login_widget: bool = True): + super().__init__() + self.setWindowTitle( + language_wrapper.language_word_dict.get("ssh_file_viewer_window_title_file_tree_manager") + ) + self.add_login_widget = add_login_widget + + self.client = SFTPClientWrapper() + + if self.add_login_widget: + # 使用獨立的登入介面 + self.login_widget = LoginWidget() + else: + if external_login_widget is None: + external_login_widget = LoginWidget() + self.login_widget = external_login_widget + + # UI: Tree + self.tree = QTreeWidget() + self.tree.setHeaderLabels(["Name", "Type", "Size", "Path"]) + self.tree.itemExpanded.connect(self.on_item_expanded) + self.tree.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + self.tree.customContextMenuRequested.connect(self.on_context_menu) + + # Layouts + splitter = QSplitter(Qt.Orientation.Vertical) + splitter.addWidget(self.login_widget) # 插入登入介面 + splitter.addWidget(self.tree) + splitter.setStretchFactor(1, 1) + + container = QWidget() + root_layout = QVBoxLayout(container) + root_layout.addWidget(splitter) + self.setLayout(root_layout) + + # Signals + self.login_widget.connect_btn.clicked.connect(self._connect) + self.login_widget.disconnect_btn.clicked.connect(self._disconnect) + + def _connect(self): + """ + Connect to SSH and load root. + 連線 SSH 並載入根目錄。 + """ + host = self.login_widget.host_edit.text().strip() + port = int(self.login_widget.port_spin.text().strip() or "22") + user = self.login_widget.user_edit.text().strip() + pwd = self.login_widget.pass_edit.text() + + if not host or not user or not pwd: + QMessageBox.warning( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_missing_input"), + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_message_missing_input")) + return + try: + self.client.connect(host, port, user, pwd) + self.load_root("/") + except Exception as e: + QMessageBox.critical( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_connection_failed"), + f"{language_wrapper.language_word_dict.get( + 'ssh_file_viewer_dialog_message_connection_failed')}: {e}") + + def _disconnect(self): + """ + Disconnect SSH. + 斷線 SSH。 + """ + self.client.close() + self.tree.clear() + + def load_root(self, path: str = "/"): + """ + Clear and load root items. + 清空並載入根項目。 + """ + self.tree.clear() + root_item = self.make_item("/", "dir", 0, "/") + # Add a placeholder child to show expandable icon + self.add_placeholder(root_item) + self.tree.addTopLevelItem(root_item) + root_item.setExpanded(True) + self.populate_children(root_item) + + def make_item(self, name: str, typ: str, size: int, full_path: str) -> QTreeWidgetItem: + """ + Create a tree item with metadata. + 建立帶有中繼資料的樹狀項目。 + """ + item = QTreeWidgetItem([name, typ, str(size), full_path]) + if typ == "dir": + # Use QStyle enum for standard icons + # 使用 QStyle 列舉取得標準資料夾圖示 + item.setIcon(0, self.style().standardIcon(QStyle.StandardPixmap.SP_DirIcon)) + else: + # Use QStyle enum for standard file icon + # 使用 QStyle 列舉取得標準檔案圖示 + item.setIcon(0, self.style().standardIcon(QStyle.StandardPixmap.SP_FileIcon)) + return item + + def add_placeholder(self, item: QTreeWidgetItem): + """ + Add a dummy child to indicate lazy-load. + 加入占位子項以表示可延遲載入。 + """ + placeholder = QTreeWidgetItem(["...", "", "", ""]) + item.addChild(placeholder) + + def is_placeholder_present(self, item: QTreeWidgetItem) -> bool: + """ + Check if the first child is a placeholder. + 檢查第一個子項是否為占位項。 + """ + if item.childCount() == 0: + return False + return item.child(0).text(0) == "..." + + def on_item_expanded(self, item: QTreeWidgetItem): + """ + When a directory is expanded, populate its children. + 展開目錄時載入子項目。 + """ + # Only load once + if self.is_placeholder_present(item): + # Remove placeholder then populate + item.takeChild(0) + self.populate_children(item) + + def populate_children(self, parent_item: QTreeWidgetItem): + """ + List items under parent path and add to tree. + 列出父路徑下的項目並加入樹。 + """ + if not self.client.connected: + return + path = parent_item.text(3) + try: + entries = self.client.list_dir(path) + # Sort: dirs first, then files + import stat + dirs = [] + files = [] + for e in entries: + name = e.filename + full_path = os.path.join(path if path != "/" else "", name) + full_path = full_path if full_path.startswith("/") else f"/{full_path}" + is_dir = stat.S_ISDIR(e.st_mode) + if is_dir: + dirs.append((name, e)) + else: + files.append((name, e)) + for name, e in dirs + files: + import stat as _stat + full_path = os.path.join(path if path != "/" else "", name) + full_path = full_path if full_path.startswith("/") else f"/{full_path}" + typ = "dir" if _stat.S_ISDIR(e.st_mode) else "file" + size = e.st_size if typ == "file" else 0 + child = self.make_item(name, typ, size, full_path) + parent_item.addChild(child) + if typ == "dir": + self.add_placeholder(child) + except Exception as ex: + QMessageBox.critical( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_list_error"), + f"{language_wrapper.language_word_dict.get( + 'ssh_file_viewer_dialog_message_list_failed')} '{path}': {ex}") + + def on_context_menu(self, pos): + """ + Show context menu for file operations. + 顯示右鍵選單以進行檔案操作。 + """ + item = self.tree.itemAt(pos) + menu = QMenu(self) + + refresh_act = menu.addAction("Refresh") + create_act = menu.addAction("Create folder") + rename_act = menu.addAction("Rename") + delete_act = menu.addAction("Delete") + download_act = menu.addAction("Download") + upload_act = menu.addAction("Upload to this folder") + + action = menu.exec_(self.tree.viewport().mapToGlobal(pos)) + if action is None: + return + + try: + if action == refresh_act: + self.action_refresh(item) + elif action == create_act: + self.action_create_folder(item) + elif action == rename_act: + self.action_rename(item) + elif action == delete_act: + self.action_delete(item) + elif action == download_act: + self.action_download(item) + elif action == upload_act: + self.action_upload(item) + except Exception as e: + QMessageBox.critical( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_operation_failed"), + f"{language_wrapper.language_word_dict.get( + 'ssh_file_viewer_dialog_message_operation_failed')}: {e}") + + def action_refresh(self, item: Optional[QTreeWidgetItem]): + """ + Refresh current item children (or root). + 重新整理目前項目的子項(或根)。 + """ + target = item or (self.tree.topLevelItem(0) if self.tree.topLevelItemCount() else None) + if target is None: + return + # Clear and reload + target.takeChildren() + if target.text(1) == "dir": + self.add_placeholder(target) + self.on_item_expanded(target) + + def action_create_folder(self, item: Optional[QTreeWidgetItem]): + """ + Create a subfolder under the selected directory. + 在選定目錄下建立子資料夾。 + """ + if item is None: + QMessageBox.information( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_no_selection"), + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_message_select_folder_to_create")) + return + base_path = item.text(3) + if item.text(1) != "dir": + base_path = os.path.dirname(base_path) + name, ok = self.get_text( + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_create_folder"), + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_label_folder_name")) + if not ok or not name.strip(): + return + new_path = os.path.join(base_path if base_path != "/" else "", name.strip()) + new_path = new_path if new_path.startswith("/") else f"/{new_path}" + self.client.mkdir(new_path) + self.action_refresh(item) + + def action_rename(self, item: Optional[QTreeWidgetItem]): + """ + Rename selected item. + 重新命名選定項目。 + """ + if item is None: + return + old_path = item.text(3) + new_name, ok = self.get_text( + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_rename"), + f"{language_wrapper.language_word_dict.get( + 'ssh_file_viewer_dialog_label_new_name_for_item')}: {item.text(0)}") + if not ok or not new_name.strip(): + return + base = os.path.dirname(old_path) or "/" + new_path = os.path.join(base if base != "/" else "", new_name.strip()) + new_path = new_path if new_path.startswith("/") else f"/{new_path}" + self.client.rename(old_path, new_path) + # Update item display + item.setText(0, new_name.strip()) + item.setText(3, new_path) + + def action_delete(self, item: Optional[QTreeWidgetItem]): + """ + Delete selected file/folder (folder must be empty). + 刪除選定檔案/資料夾(資料夾需為空)。 + """ + if item is None: + return + path = item.text(3) + reply = QMessageBox.question( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_confirm_delete"), + f"{language_wrapper.language_word_dict.get( + 'ssh_file_viewer_dialog_message_confirm_delete')} '{path}'?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + if reply != QMessageBox.StandardButton.Yes: + return + # Try dir first; if fails, try file + if item.text(1) == "dir": + self.client.remove_dir(path) + else: + self.client.remove_file(path) + parent = item.parent() + if parent: + parent.removeChild(item) + else: + self.tree.takeTopLevelItem(self.tree.indexOfTopLevelItem(item)) + + def action_download(self, item: Optional[QTreeWidgetItem]): + """ + Download selected file to local. + 將選定檔案下載至本地。 + """ + if item is None or item.text(1) != "file": + QMessageBox.information( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_invalid_selection"), + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_message_select_file_to_download")) + return + remote_path = item.text(3) + suggested = Path(remote_path).name + local_path, _ = QFileDialog.getSaveFileName( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_save_as"), + suggested) + if not local_path: + return + self.client.download(remote_path, local_path) + QMessageBox.information( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_downloaded"), + f"{language_wrapper.language_word_dict.get( + 'ssh_file_viewer_dialog_message_saved_to')}: {local_path}") + + def action_upload(self, item: Optional[QTreeWidgetItem]): + """ + Upload a local file into the selected folder. + 將本地檔案上傳至所選資料夾。 + """ + if item is None: + return + target_dir = item.text(3) if item.text(1) == "dir" else os.path.dirname(item.text(3)) + local_path, _ = QFileDialog.getOpenFileName( + self, language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_select_local_file"), "") + if not local_path: + return + filename = os.path.basename(local_path) + remote_path = os.path.join(target_dir if target_dir != "/" else "", filename) + remote_path = remote_path if remote_path.startswith("/") else f"/{remote_path}" + self.client.upload(local_path, remote_path) + QMessageBox.information( + self, + language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_uploaded"), + f"{language_wrapper.language_word_dict.get( + 'ssh_file_viewer_dialog_message_uploaded_to')}: {remote_path}") + # Refresh folder contents + self.action_refresh(item) + + def get_text(self, title: str, label: str): + """ + Simple input dialog using QMessageBox alternative. + 簡易文字輸入對話框(基於 QLineEdit)。 + """ + text, ok = QInputDialog.getText(self, title, label) + return text, ok + + def eventFilter(self, obj, event): + """ + Allow Enter to trigger OK in our improvised input dialog. + 允許在自製輸入框中使用 Enter 觸發確定。 + """ + if isinstance(obj, QLineEdit) and event.type() == QEvent.Type.KeyPress: + if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): + w = obj.window() + for b in w.findChildren(QPushButton): + if b.text().lower() in (language_wrapper.language_word_dict.get( + "ssh_file_viewer_dialog_button_ok"),): + b.click() + return True + return super().eventFilter(obj, event) diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_login_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_login_widget.py new file mode 100644 index 0000000..c1ca6a9 --- /dev/null +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_login_widget.py @@ -0,0 +1,67 @@ +from PySide6.QtWidgets import ( + QWidget, QHBoxLayout, QVBoxLayout, QLabel, + QLineEdit, QSpinBox, QCheckBox, QPushButton +) +from je_editor import language_wrapper + + +class LoginWidget(QWidget): + """登入介面獨立 QWidget""" + + def __init__(self, parent=None): + super().__init__(parent) + + # UI 控制元件 + self.host_edit = QLineEdit() + self.port_spin = QSpinBox() + self.user_edit = QLineEdit() + self.pass_edit = QLineEdit() + self.key_edit = QLineEdit() + self.use_key_check = QCheckBox(language_wrapper.language_word_dict.get("ssh_login_widget_button_use_key_auth")) + self.connect_btn = QPushButton(language_wrapper.language_word_dict.get("ssh_login_widget_button_connect")) + self.disconnect_btn = QPushButton(language_wrapper.language_word_dict.get("ssh_login_widget_button_disconnect")) + self.status_label = QLabel(language_wrapper.language_word_dict.get("ssh_login_widget_status_disconnected")) + + # 初始化 UI + self._setup_ui() + + def _setup_ui(self): + """建立登入介面 UI""" + self.host_edit.setPlaceholderText(language_wrapper.language_word_dict.get("ssh_login_widget_placeholder_host")) + self.port_spin.setRange(1, 65535) + self.port_spin.setValue(22) + self.user_edit.setPlaceholderText( + language_wrapper.language_word_dict.get("ssh_login_widget_placeholder_username")) + self.pass_edit.setPlaceholderText( + language_wrapper.language_word_dict.get("ssh_login_widget_placeholder_password")) + self.pass_edit.setEchoMode(QLineEdit.EchoMode.Password) + self.key_edit.setPlaceholderText( + language_wrapper.language_word_dict.get("ssh_login_widget_placeholder_private_key")) + + # 佈局設計 + top = QHBoxLayout() + top.addWidget(QLabel(language_wrapper.language_word_dict.get("ssh_login_widget_label_host"))) + top.addWidget(self.host_edit) + top.addWidget(QLabel(language_wrapper.language_word_dict.get("ssh_login_widget_label_port"))) + top.addWidget(self.port_spin) + top.addWidget(QLabel(language_wrapper.language_word_dict.get("ssh_login_widget_label_user"))) + top.addWidget(self.user_edit) + + auth = QHBoxLayout() + auth.addWidget(self.use_key_check) + auth.addWidget(QLabel(language_wrapper.language_word_dict.get("ssh_login_widget_label_key"))) + auth.addWidget(self.key_edit) + auth.addWidget(QLabel(language_wrapper.language_word_dict.get("ssh_login_widget_label_password"))) + auth.addWidget(self.pass_edit) + + conn = QHBoxLayout() + conn.addWidget(self.connect_btn) + conn.addWidget(self.disconnect_btn) + conn.addWidget(self.status_label) + + root = QVBoxLayout() + root.addLayout(top) + root.addLayout(auth) + root.addLayout(conn) + + self.setLayout(root) diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_main_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_main_widget.py new file mode 100644 index 0000000..2f44190 --- /dev/null +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_main_widget.py @@ -0,0 +1,49 @@ +import sys + +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QWidget, QVBoxLayout, QSplitter, QApplication, QSizePolicy + +from automation_ide.automation_editor_ui.connect_gui.ssh.ssh_command_widget import SSHCommandWidget +from automation_ide.automation_editor_ui.connect_gui.ssh.ssh_file_viewer_widget import SSHFileTreeManager +from automation_ide.automation_editor_ui.connect_gui.ssh.ssh_login_widget import LoginWidget + + +class SSHMainWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + # 已經實作好的元件 + self.login_widget = LoginWidget() + self.login_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + self.file_tree = SSHFileTreeManager(external_login_widget=self.login_widget, add_login_widget=False) + self.command_widget = SSHCommandWidget(external_login_widget=self.login_widget, add_login_widget=False) + + # 整體垂直布局 + main_layout = QVBoxLayout(self) + + # 上方放登入區 + main_layout.addWidget(self.login_widget) + + # Splitter 左右分割 + splitter = QSplitter(Qt.Orientation.Horizontal) + splitter.addWidget(self.file_tree) # 左邊檔案瀏覽器 + splitter.addWidget(self.command_widget) # 右邊命令區 + # 設定初始比例:左邊 30%,右邊 70% + splitter.setSizes([300, 700]) # 數值會依視窗大小縮放 + splitter.setStretchFactor(0, 1) + splitter.setStretchFactor(1, 2) + + # 讓拖曳更平滑:加寬 handle 並避免完全收合 + splitter.setHandleWidth(8) + splitter.setCollapsible(0, False) + splitter.setCollapsible(1, False) + + main_layout.addWidget(splitter) + self.setLayout(main_layout) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + win = SSHMainWidget() + win.showMaximized() + sys.exit(app.exec()) diff --git a/automation_ide/automation_editor_ui/connect_gui/url/__init__.py b/automation_ide/automation_editor_ui/connect_gui/url/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py b/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py new file mode 100644 index 0000000..ed98924 --- /dev/null +++ b/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py @@ -0,0 +1,180 @@ +import sys +import requests +import os +from PySide6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QHBoxLayout, + QPushButton, QLineEdit, QTextEdit, QComboBox, QLabel, QSizePolicy +) +from je_editor import language_wrapper + + +class AICodeReviewClient(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle(language_wrapper.language_word_dict.get( + "ai_code_review_gui_window_title" + )) + + # 記錄接受/拒絕次數 + self.accept_count = 0 + self.reject_count = 0 + self.stats_file = "response_stats.txt" + self.url_file = "urls.txt" + + # 主佈局 (垂直) + main_layout = QVBoxLayout() + + # ------------------------------- + # 上方:URL 與 Method + # ------------------------------- + top_layout = QHBoxLayout() + + # URL + url_layout = QHBoxLayout() + url_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("ai_code_review_gui_label_url"))) + self.url_input = QLineEdit() + url_layout.addWidget(self.url_input) + top_layout.addLayout(url_layout) + + # Method + method_layout = QHBoxLayout() + method_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("ai_code_review_gui_label_method"))) + self.method_box = QComboBox() + self.method_box.addItems(["GET", "POST", "PUT", "DELETE"]) + method_layout.addWidget(self.method_box) + top_layout.addLayout(method_layout) + + main_layout.addLayout(top_layout) + + # ------------------------------- + # 中間:左右顯示框 (同樣高) + # ------------------------------- + middle_layout = QHBoxLayout() + + # 左邊:程式碼輸入 + left_layout = QVBoxLayout() + left_layout.addWidget(QLabel( + language_wrapper.language_word_dict.get("ai_code_review_gui_label_code_to_send"))) + self.code_input = QTextEdit() + self.code_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + left_layout.addWidget(self.code_input) + + # 右邊:回傳顯示 + right_layout = QVBoxLayout() + right_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("ai_code_review_gui_label_response"))) + self.response_panel = QTextEdit() + self.response_panel.setReadOnly(True) + self.response_panel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + right_layout.addWidget(self.response_panel) + + # 放入中間佈局 + middle_layout.addLayout(left_layout, 1) + middle_layout.addLayout(right_layout, 1) + + main_layout.addLayout(middle_layout) + + # ------------------------------- + # 最下面:發送按鈕 + # ------------------------------- + self.send_button = QPushButton( + language_wrapper.language_word_dict.get("ai_code_review_gui_button_send_request")) + self.send_button.clicked.connect(self.send_request) + main_layout.addWidget(self.send_button) + + # ------------------------------- + # 最下面:接受/不接受按鈕 + # ------------------------------- + bottom_layout = QHBoxLayout() + self.accept_button = QPushButton( + language_wrapper.language_word_dict.get("ai_code_review_gui_button_accept_response")) + self.reject_button = QPushButton( + language_wrapper.language_word_dict.get("ai_code_review_gui_button_reject_response")) + + # 綁定事件 + self.accept_button.clicked.connect(self.accept_response) + self.reject_button.clicked.connect(self.reject_response) + + bottom_layout.addWidget(self.accept_button) + bottom_layout.addWidget(self.reject_button) + + main_layout.addLayout(bottom_layout) + + self.setLayout(main_layout) + + def send_request(self): + """Send HTTP request and display result""" + url = self.url_input.text().strip() + method = self.method_box.currentText() + code_content = self.code_input.toPlainText().strip() + + if not url: + self.response_panel.setPlainText( + language_wrapper.language_word_dict.get("ai_code_review_gui_message_enter_valid_url")) + return + + # 檢查 URL 是否已紀錄 + if os.path.exists(self.url_file): + with open(self.url_file, "r", encoding="utf-8") as f: + urls = [line.strip() for line in f.readlines()] + else: + urls = [] + + if url in urls: + self.response_panel.setPlainText( + language_wrapper.language_word_dict.get("ai_code_review_gui_message_url_already_recorded")) + else: + with open(self.url_file, "a", encoding="utf-8") as f: + f.write(url + "\n") + self.response_panel.setPlainText( + language_wrapper.language_word_dict.get("ai_code_review_gui_message_new_url_recorded")) + + try: + if method == "GET": + response = requests.get(url) + elif method == "POST": + response = requests.post(url, data={"code": code_content}) + elif method == "PUT": + response = requests.put(url, data={"code": code_content}) + elif method == "DELETE": + response = requests.delete(url) + else: + self.response_panel.setPlainText( + language_wrapper.language_word_dict.get("ai_code_review_gui_message_unsupported_http_method")) + return + + self.response_panel.append(response.text) + + except Exception as e: + self.response_panel.setPlainText(f"{ + language_wrapper.language_word_dict.get('ai_code_review_gui_message_error')}: {e}") + + def accept_response(self): + """Accept response code and save""" + self.accept_count += 1 + self.response_panel.append(f"\n{ + language_wrapper.language_word_dict.get('ai_code_review_gui_status_accepted')}") + self.save_stats() + + def reject_response(self): + """Reject response code and save""" + self.reject_count += 1 + self.response_panel.append(f"\n{ + language_wrapper.language_word_dict.get('ai_code_review_gui_status_rejected')}") + self.save_stats() + + def save_stats(self): + """Save accept/reject counts""" + try: + with open(self.stats_file, "w", encoding="utf-8") as f: + f.write(f"Accepted: {self.accept_count}\n") + f.write(f"Rejected: {self.reject_count}\n") + except Exception as e: + self.response_panel.append(f"\n[{ + language_wrapper.language_word_dict.get("ai_code_review_gui_status_save_failed")}: {e}]") + + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = AICodeReviewClient() + window.showMaximized() + sys.exit(app.exec()) \ No newline at end of file diff --git a/automation_ide/automation_editor_ui/editor_main/main_ui.py b/automation_ide/automation_editor_ui/editor_main/main_ui.py index 0faddc0..398d183 100644 --- a/automation_ide/automation_editor_ui/editor_main/main_ui.py +++ b/automation_ide/automation_editor_ui/editor_main/main_ui.py @@ -17,14 +17,8 @@ from automation_ide.automation_editor_ui.syntax.syntax_extend import \ syntax_extend_package -from je_api_testka.gui.main_widget import APITestkaWidget -from je_load_density.gui.main_widget import LoadDensityWidget -from je_auto_control.gui.main_widget import AutoControlGUIWidget EDITOR_EXTEND_TAB: Dict[str, Type[QWidget]] = { - "LoadDensity GUI": LoadDensityWidget, - "APITestka GUI": APITestkaWidget, - "AutoControl GUI": AutoControlGUIWidget, } diff --git a/automation_ide/automation_editor_ui/extend_multi_language/extend_english.py b/automation_ide/automation_editor_ui/extend_multi_language/extend_english.py index 6e359ba..acc8053 100644 --- a/automation_ide/automation_editor_ui/extend_multi_language/extend_english.py +++ b/automation_ide/automation_editor_ui/extend_multi_language/extend_english.py @@ -99,5 +99,98 @@ def update_english_word_dict(): "test_pioneer_create_template_label": "Create TestPioneer Yaml template", "test_pioneer_run_yaml": "Execute Test Pioneer Yaml", "test_pioneer_not_choose_yaml": "Please choose a Yaml file", + # SSH command widget + "ssh_command_widget_window_title_ssh_command_widget": "SSH Command Widget", + "ssh_command_widget_button_label_send_command": "Send", + "ssh_command_widget_input_placeholder_command_line": "Type command then Enter...", + "ssh_command_widget_dialog_title_input_error": "Input error", + "ssh_command_widget_dialog_message_input_error_host_user_required": "Host and username are required.", + "ssh_command_widget_dialog_title_key_error": "Key error", + "ssh_command_widget_dialog_message_key_file_not_exist": "Key file does not exist.", + "ssh_command_widget_error_message_unsupported_private_key": "Unsupported or invalid private key.", + "ssh_command_widget_error_message_key_auth_failed": "Key auth failed", + "ssh_command_widget_status_label_connected": "Connected", + "ssh_command_widget_status_label_disconnected": "Disconnected", + "ssh_command_widget_log_message_connected": "Connected to", + "ssh_command_widget_log_message_error": "[Error] ", + "ssh_command_widget_error_message_decode_failed": " ", + "ssh_command_widget_log_message_channel_closed": "[Channel closed]", + "ssh_command_widget_error_message_reader_failed": "Reader error", + "ssh_command_widget_log_message_reader_closed": "Reader closed", + "ssh_command_widget_error_message_send_failed": "[Send error]", + "ssh_command_widget_dialog_title_not_connected": "Not connected", + "ssh_command_widget_dialog_message_not_connected_shell": "SSH shell is not connected.", + "ssh_command_widget_log_message_disconnect_in_progress": "[Disconnecting...]", + # SSH File Viewer GUI + "ssh_file_viewer_dialog_title_list_error": "List error", + "ssh_file_viewer_dialog_message_list_failed": "Failed to list", + "ssh_file_viewer_dialog_title_operation_failed": "Operation failed", + "ssh_file_viewer_dialog_message_operation_failed": "Error", + "ssh_file_viewer_context_menu_action_refresh": "Refresh", + "ssh_file_viewer_context_menu_action_create_folder": "Create folder", + "ssh_file_viewer_context_menu_action_rename": "Rename", + "ssh_file_viewer_context_menu_action_delete": "Delete", + "ssh_file_viewer_context_menu_action_download": "Download", + "ssh_file_viewer_context_menu_action_upload": "Upload to this folder", + "ssh_file_viewer_log_message_refreshing": "Refreshing current item children (or root)", + "ssh_file_viewer_dialog_title_no_selection": "No selection", + "ssh_file_viewer_dialog_message_select_folder_to_create": "Select a folder to create inside.", + "ssh_file_viewer_dialog_title_create_folder": "Create folder", + "ssh_file_viewer_dialog_label_folder_name": "Folder name:", + "ssh_file_viewer_dialog_title_rename": "Rename", + "ssh_file_viewer_dialog_label_new_name_for_item": "New name for", + "ssh_file_viewer_dialog_title_confirm_delete": "Confirm delete", + "ssh_file_viewer_dialog_message_confirm_delete": "Delete", + "ssh_file_viewer_dialog_title_invalid_selection": "Invalid selection", + "ssh_file_viewer_dialog_message_select_file_to_download": "Select a file to download.", + "ssh_file_viewer_dialog_title_save_as": "Save as", + "ssh_file_viewer_dialog_title_downloaded": "Downloaded", + "ssh_file_viewer_dialog_message_saved_to": "Saved to", + "ssh_file_viewer_dialog_title_select_local_file": "Select local file to upload", + "ssh_file_viewer_dialog_title_uploaded": "Uploaded", + "ssh_file_viewer_dialog_message_uploaded_to": "Uploaded to", + "ssh_file_viewer_dialog_label_input_text": "Input text", + "ssh_file_viewer_dialog_button_ok": "OK", + "ssh_file_viewer_window_title_file_tree_manager": "SSH File TreeView Manager", + "ssh_file_viewer_tree_header_name": "Name", + "ssh_file_viewer_tree_header_type": "Type", + "ssh_file_viewer_tree_header_size": "Size", + "ssh_file_viewer_tree_header_path": "Path", + "ssh_file_viewer_dialog_title_missing_input": "Missing input", + "ssh_file_viewer_dialog_message_missing_input": "Host, user, and password are required.", + "ssh_file_viewer_dialog_title_connection_failed": "Connection failed", + "ssh_file_viewer_dialog_message_connection_failed": "Failed to connect", + # SSH Login Widget + "ssh_login_widget_label_host": "Host", + "ssh_login_widget_label_port": "Port", + "ssh_login_widget_label_user": "User", + "ssh_login_widget_label_key": "Key", + "ssh_login_widget_label_password": "Password", + "ssh_login_widget_placeholder_host": "Host (e.g., 192.168.0.10)", + "ssh_login_widget_placeholder_username": "Username", + "ssh_login_widget_placeholder_password": "Password", + "ssh_login_widget_placeholder_private_key": "Private key path (.pem/.ppk)", + "ssh_login_widget_button_use_key_auth": "Use key auth", + "ssh_login_widget_button_connect": "Connect", + "ssh_login_widget_button_disconnect": "Disconnect", + "ssh_login_widget_status_disconnected": "Disconnected", + # AI Code Review GUI + "ai_code_review_gui_window_title": "AI Code-Review Client", + "ai_code_review_gui_label_url": "URL:", + "ai_code_review_gui_label_method": "Method:", + "ai_code_review_gui_label_code_to_send": "Code to Send:", + "ai_code_review_gui_label_response": "Response:", + "ai_code_review_gui_button_send_request": "Send Request", + "ai_code_review_gui_button_accept_response": "Accept Response", + "ai_code_review_gui_button_reject_response": "Reject Response", + "ai_code_review_gui_message_enter_valid_url": "Please enter a valid URL", + "ai_code_review_gui_message_url_already_recorded": "This URL is already recorded, still sending request...", + "ai_code_review_gui_message_new_url_recorded": "New URL recorded, sending request...", + "ai_code_review_gui_message_unsupported_http_method": "Unsupported HTTP method", + "ai_code_review_gui_message_error": "Error", + "ai_code_review_gui_status_accepted": "[Accepted]", + "ai_code_review_gui_status_rejected": "[Rejected]", + "ai_code_review_gui_status_save_failed": "Save failed" + } ) diff --git a/automation_ide/automation_editor_ui/extend_multi_language/extend_traditional_chinese.py b/automation_ide/automation_editor_ui/extend_multi_language/extend_traditional_chinese.py index 763b0f9..9c1bfb9 100644 --- a/automation_ide/automation_editor_ui/extend_multi_language/extend_traditional_chinese.py +++ b/automation_ide/automation_editor_ui/extend_multi_language/extend_traditional_chinese.py @@ -99,5 +99,97 @@ def update_traditional_chinese_word_dict(): "test_pioneer_create_template_label": "建立 TestPioneer Yaml 模板", "test_pioneer_run_yaml": "執行 Test Pioneer Yaml", "test_pioneer_not_choose_yaml": "請選擇 Yaml 檔案", + # SSH command widget + "ssh_command_widget_window_title_ssh_command_widget": "SSH 指令介面", + "ssh_command_widget_button_label_send_command": "送出", + "ssh_command_widget_input_placeholder_command_line": "輸入指令後按 Enter...", + "ssh_command_widget_dialog_title_input_error": "輸入錯誤", + "ssh_command_widget_dialog_message_input_error_host_user_required": "必須輸入主機與使用者名稱。", + "ssh_command_widget_dialog_title_key_error": "金鑰錯誤", + "ssh_command_widget_dialog_message_key_file_not_exist": "金鑰檔案不存在。", + "ssh_command_widget_error_message_unsupported_private_key": "不支援或無效的私鑰。", + "ssh_command_widget_error_message_key_auth_failed": "金鑰驗證失敗", + "ssh_command_widget_status_label_connected": "已連線", + "ssh_command_widget_status_label_disconnected": "已斷線", + "ssh_command_widget_log_message_connected": "已連線至", + "ssh_command_widget_log_message_error": "[錯誤]", + "ssh_command_widget_error_message_decode_failed": "<解碼錯誤> {error}", + "ssh_command_widget_log_message_channel_closed": "[通道已關閉]", + "ssh_command_widget_error_message_reader_failed": "讀取錯誤", + "ssh_command_widget_log_message_reader_closed": "讀取已關閉", + "ssh_command_widget_error_message_send_failed": "[傳送錯誤]", + "ssh_command_widget_dialog_title_not_connected": "尚未連線", + "ssh_command_widget_dialog_message_not_connected_shell": "SSH shell 尚未連線。", + "ssh_command_widget_log_message_disconnect_in_progress": "[正在斷線...]", + # SSH File Viewer GUI + "ssh_file_viewer_dialog_title_list_error": "列出錯誤", + "ssh_file_viewer_dialog_message_list_failed": "無法列出", + "ssh_file_viewer_dialog_title_operation_failed": "操作失敗", + "ssh_file_viewer_dialog_message_operation_failed": "錯誤", + "ssh_file_viewer_context_menu_action_refresh": "重新整理", + "ssh_file_viewer_context_menu_action_create_folder": "建立資料夾", + "ssh_file_viewer_context_menu_action_rename": "重新命名", + "ssh_file_viewer_context_menu_action_delete": "刪除", + "ssh_file_viewer_context_menu_action_download": "下載", + "ssh_file_viewer_context_menu_action_upload": "上傳至此資料夾", + "ssh_file_viewer_log_message_refreshing": "正在重新整理目前項目的子項(或根)", + "ssh_file_viewer_dialog_title_no_selection": "未選取", + "ssh_file_viewer_dialog_message_select_folder_to_create": "請選擇一個資料夾以建立子資料夾。", + "ssh_file_viewer_dialog_title_create_folder": "建立資料夾", + "ssh_file_viewer_dialog_label_folder_name": "資料夾名稱:", + "ssh_file_viewer_dialog_title_rename": "重新命名", + "ssh_file_viewer_dialog_label_new_name_for_item": "新名稱", + "ssh_file_viewer_dialog_title_confirm_delete": "確認刪除", + "ssh_file_viewer_dialog_message_confirm_delete": "是否刪除", + "ssh_file_viewer_dialog_title_invalid_selection": "選取無效", + "ssh_file_viewer_dialog_message_select_file_to_download": "請選擇要下載的檔案。", + "ssh_file_viewer_dialog_title_save_as": "另存新檔", + "ssh_file_viewer_dialog_title_downloaded": "已下載", + "ssh_file_viewer_dialog_message_saved_to": "已儲存至", + "ssh_file_viewer_dialog_title_select_local_file": "選擇要上傳的本地檔案", + "ssh_file_viewer_dialog_title_uploaded": "已上傳", + "ssh_file_viewer_dialog_message_uploaded_to": "已上傳至", + "ssh_file_viewer_dialog_label_input_text": "輸入文字", + "ssh_file_viewer_dialog_button_ok": "確定", + "ssh_file_viewer_window_title_file_tree_manager": "SSH 檔案樹狀管理器", + "ssh_file_viewer_tree_header_name": "名稱", + "ssh_file_viewer_tree_header_type": "類型", + "ssh_file_viewer_tree_header_size": "大小", + "ssh_file_viewer_tree_header_path": "路徑", + "ssh_file_viewer_dialog_title_missing_input": "缺少輸入", + "ssh_file_viewer_dialog_message_missing_input": "必須輸入主機、使用者與密碼。", + "ssh_file_viewer_dialog_title_connection_failed": "連線失敗", + "ssh_file_viewer_dialog_message_connection_failed": "連線失敗", + # SSH Login Widget + "ssh_login_widget_label_host": "主機", + "ssh_login_widget_label_port": "連接埠", + "ssh_login_widget_label_user": "使用者", + "ssh_login_widget_label_key": "金鑰", + "ssh_login_widget_label_password": "密碼", + "ssh_login_widget_placeholder_host": "主機 (例如: 192.168.0.10)", + "ssh_login_widget_placeholder_username": "使用者名稱", + "ssh_login_widget_placeholder_password": "密碼", + "ssh_login_widget_placeholder_private_key": "私鑰路徑 (.pem/.ppk)", + "ssh_login_widget_button_use_key_auth": "使用金鑰驗證", + "ssh_login_widget_button_connect": "連線", + "ssh_login_widget_button_disconnect": "斷線", + "ssh_login_widget_status_disconnected": "已斷線", + # AI Code Review GUI + "ai_code_review_gui_window_title": "AI 程式碼審查客戶端", + "ai_code_review_gui_label_url": "網址:", + "ai_code_review_gui_label_method": "方法:", + "ai_code_review_gui_label_code_to_send": "要送出的程式碼:", + "ai_code_review_gui_label_response": "回應:", + "ai_code_review_gui_button_send_request": "送出請求", + "ai_code_review_gui_button_accept_response": "接受回應", + "ai_code_review_gui_button_reject_response": "拒絕回應", + "ai_code_review_gui_message_enter_valid_url": "請輸入有效的網址", + "ai_code_review_gui_message_url_already_recorded": "此網址已被紀錄,仍然送出請求...", + "ai_code_review_gui_message_new_url_recorded": "新網址已紀錄,正在送出請求...", + "ai_code_review_gui_message_unsupported_http_method": "不支援的 HTTP 方法", + "ai_code_review_gui_message_error": "錯誤", + "ai_code_review_gui_status_accepted": "[已接受]", + "ai_code_review_gui_status_rejected": "[已拒絕]", + "ai_code_review_gui_status_save_failed": "儲存失敗" } ) diff --git a/automation_ide/automation_editor_ui/menu/build_menubar.py b/automation_ide/automation_editor_ui/menu/build_menubar.py index 20e381d..fe80f13 100644 --- a/automation_ide/automation_editor_ui/menu/build_menubar.py +++ b/automation_ide/automation_editor_ui/menu/build_menubar.py @@ -2,6 +2,8 @@ from typing import TYPE_CHECKING +from automation_ide.automation_editor_ui.menu.tools.tools_menu import build_tools_menu, extend_dock_menu + if TYPE_CHECKING: from automation_ide.automation_editor_ui.editor_main.main_ui import AutomationEditor @@ -41,3 +43,5 @@ def add_menu_to_menubar(ui_we_want_to_set: AutomationEditor): set_test_pioneer_menu(ui_we_want_to_set) build_automation_install_menu(ui_we_want_to_set) build_tool_install_menu(ui_we_want_to_set) + build_tools_menu(ui_we_want_to_set) + extend_dock_menu(ui_we_want_to_set) diff --git a/automation_ide/automation_editor_ui/menu/tools/__init__.py b/automation_ide/automation_editor_ui/menu/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/automation_ide/automation_editor_ui/menu/tools/tools_menu.py b/automation_ide/automation_editor_ui/menu/tools/tools_menu.py new file mode 100644 index 0000000..bba0345 --- /dev/null +++ b/automation_ide/automation_editor_ui/menu/tools/tools_menu.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from PySide6.QtGui import QAction, Qt +from je_editor.pyside_ui.main_ui.dock.destroy_dock import DestroyDock +from je_editor.utils.logging.loggin_instance import jeditor_logger + +from automation_ide.automation_editor_ui.connect_gui.ssh.ssh_main_widget import SSHMainWidget +from automation_ide.automation_editor_ui.connect_gui.url.ai_code_review_gui import AICodeReviewClient + +if TYPE_CHECKING: + from automation_ide.automation_editor_ui.editor_main.main_ui import AutomationEditor + + +def build_tools_menu(ui_we_want_to_set: AutomationEditor): + # Menus + ui_we_want_to_set.tools_menu = ui_we_want_to_set.menu.addMenu("Tools") + ui_we_want_to_set.tools_ssh_menu = ui_we_want_to_set.tools_menu.addMenu("SSH") + ui_we_want_to_set.tools_ai_menu = ui_we_want_to_set.tools_menu.addMenu("AI") + + ui_we_want_to_set.tools_ssh_client_tab_action = QAction("SSH Client Tab") + ui_we_want_to_set.tools_ssh_client_tab_action.triggered.connect(lambda: ui_we_want_to_set.tab_widget.addTab( + SSHMainWidget(), "SSH Client" + )) + ui_we_want_to_set.tools_ssh_menu.addAction(ui_we_want_to_set.tools_ssh_client_tab_action) + + ui_we_want_to_set.tools_ai_code_review_action = QAction("AI Code-Review Tab") + ui_we_want_to_set.tools_ai_code_review_action.triggered.connect(lambda: ui_we_want_to_set.tab_widget.addTab( + AICodeReviewClient(), "AI Code-Review" + )) + ui_we_want_to_set.tools_ai_menu.addAction(ui_we_want_to_set.tools_ai_code_review_action) + + +def extend_dock_menu(ui_we_want_to_set: AutomationEditor): + # Sub menu + ui_we_want_to_set.dock_ssh_menu = ui_we_want_to_set.dock_menu.addMenu( + "SSH" + ) + ui_we_want_to_set.dock_ai_menu = ui_we_want_to_set.dock_menu.addMenu( + "AI" + ) + # SSH + ui_we_want_to_set.tools_ssh_client_dock_action = QAction("SSH Client Dock") + ui_we_want_to_set.tools_ssh_client_dock_action.triggered.connect( + lambda: add_dock(ui_we_want_to_set, "SSH") + ) + ui_we_want_to_set.dock_ssh_menu.addAction(ui_we_want_to_set.tools_ssh_client_dock_action) + # AI + ui_we_want_to_set.tools_ai_code_review_dock_action = QAction("AI Code-Review Dock") + ui_we_want_to_set.tools_ai_code_review_dock_action.triggered.connect( + lambda: add_dock(ui_we_want_to_set, "AICodeReview") + ) + ui_we_want_to_set.dock_ai_menu.addAction(ui_we_want_to_set.tools_ai_code_review_dock_action) + +def add_dock(ui_we_want_to_set: AutomationEditor, widget_type: str = None): + jeditor_logger.info("build_dock_menu.py add_dock_widget " + f"ui_we_want_to_set: {ui_we_want_to_set} " + f"widget_type: {widget_type}") + + # 建立一個可銷毀的 Dock 容器 + # Create a destroyable dock container + dock_widget = DestroyDock() + + if widget_type == "SSH": + dock_widget.setWindowTitle("SSH Client") + dock_widget.setWidget(SSHMainWidget()) + elif widget_type == "AICodeReview": + dock_widget.setWindowTitle("AI Code-Review") + dock_widget.setWidget(AICodeReviewClient()) + + # 如果成功建立了 widget,將其加到主視窗右側 Dock 區域 + # If widget is created, add it to the right dock area of the main window + if dock_widget.widget() is not None: + ui_we_want_to_set.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock_widget) diff --git a/automation_ide/automation_editor_ui/prompt_edit_gui/__init__.py b/automation_ide/automation_editor_ui/prompt_edit_gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/automation_ide/automation_editor_ui/prompt_edit_gui/main_prompt_edit_widget.py b/automation_ide/automation_editor_ui/prompt_edit_gui/main_prompt_edit_widget.py new file mode 100644 index 0000000..eaa0492 --- /dev/null +++ b/automation_ide/automation_editor_ui/prompt_edit_gui/main_prompt_edit_widget.py @@ -0,0 +1,167 @@ +import os +import sys + +from PySide6.QtCore import QFileSystemWatcher +from PySide6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QHBoxLayout, + QComboBox, QTextEdit, QLineEdit, QPushButton, QLabel, QGroupBox, QMessageBox +) + +from automation_ide.automation_editor_ui.prompt_edit_gui.prompt_templates.first_code_review import FIRST_CODE_REVIEW +from automation_ide.automation_editor_ui.prompt_edit_gui.prompt_templates.first_summary_prompt import \ + FIRST_SUMMARY_PROMPT +from automation_ide.automation_editor_ui.prompt_edit_gui.prompt_templates.global_rule import GLOBAL_RULE_TEMPLATE +from automation_ide.automation_editor_ui.prompt_edit_gui.prompt_templates.judge import JUDGE_TEMPLATE +from automation_ide.automation_editor_ui.prompt_edit_gui.prompt_templates.total_summary import TOTAL_SUMMARY_TEMPLATE + + +class PromptEditor(QWidget): + def __init__(self, prompt_files=None, parent=None): + super().__init__(parent) + self.prompt_files = prompt_files or [ + "global_rule.md", + "first_summary_prompt.md", + "first_code_review.md", + "judge.md", + "total_summary.md" + ] + + # 對應檔案名稱與模板內容 + self.templates = { + "global_rule.md": GLOBAL_RULE_TEMPLATE, + "first_summary_prompt.md": FIRST_SUMMARY_PROMPT, + "first_code_review.md": FIRST_CODE_REVIEW, + "judge.md": JUDGE_TEMPLATE, + "total_summary.md": TOTAL_SUMMARY_TEMPLATE, + } + + self.setWindowTitle("Prompt Editor") # 視窗標題:Prompt 編輯器 + + # --- Layouts (版面配置) --- + main_layout = QVBoxLayout(self) + top_layout = QHBoxLayout() + editor_layout = QHBoxLayout() + bottom_layout = QHBoxLayout() + + # --- AI URL input box (AI URL 輸入框) --- + self.url_label = QLabel("AI URL:") + self.url_input = QLineEdit() + top_layout.addWidget(self.url_label) + top_layout.addWidget(self.url_input) + + # --- ComboBox for selecting files (下拉選單選擇檔案) --- + self.file_selector = QComboBox() + self.file_selector.addItems(self.prompt_files) + self.file_selector.currentIndexChanged.connect(self.load_file_content) + + # --- Left Editable panel (左邊編輯區塊) --- + self.left_editor = QTextEdit() + left_group = QGroupBox("Edit File Content") # 左邊編輯檔案內容 + left_layout = QVBoxLayout() + left_layout.addWidget(self.left_editor) + left_group.setLayout(left_layout) + + # --- Right Editable panel (右邊可編輯區塊) --- + self.editable_panel = QTextEdit() + editable_group = QGroupBox("Edit Prompt (Editable)") # 編輯 Prompt (可編輯) + editable_layout = QVBoxLayout() + editable_layout.addWidget(self.editable_panel) + editable_group.setLayout(editable_layout) + + editor_layout.addWidget(left_group, 1) + editor_layout.addWidget(editable_group, 1) + + # --- Buttons --- + self.send_button = QPushButton("Send") + self.send_button.clicked.connect(self.send_prompt) + + self.create_button = QPushButton("Create File") + self.create_button.clicked.connect(self.create_file) + + self.save_button = QPushButton("Save") + self.save_button.clicked.connect(self.save_file) + + self.reload_button = QPushButton("Reload") + self.reload_button.clicked.connect(lambda: self.load_file_content(self.file_selector.currentIndex())) + + bottom_layout.addWidget(self.file_selector) + bottom_layout.addStretch() + bottom_layout.addWidget(self.reload_button) + bottom_layout.addWidget(self.save_button) + bottom_layout.addWidget(self.create_button) + bottom_layout.addWidget(self.send_button) + + # --- Combine layouts (組合版面配置) --- + main_layout.addLayout(top_layout) + main_layout.addLayout(editor_layout) + main_layout.addLayout(bottom_layout) + + # --- FileSystemWatcher (檔案監控器) --- + self.watcher = QFileSystemWatcher(self.prompt_files) + self.watcher.fileChanged.connect(self.on_file_changed) + + # 預設載入第一個檔案 + self.load_file_content(0) + + def load_file_content(self, index): + """載入選擇的檔案內容到左邊編輯區""" + filename = self.prompt_files[index] + self.current_file = filename + if os.path.exists(filename): + with open(filename, "r", encoding="utf-8") as f: + content = f.read() + self.left_editor.setPlainText(content) + else: + self.left_editor.setPlainText(f"(File {filename} does not exist)") + + def create_file(self): + """建立目前選擇的檔案,若不存在則用模板內容建立""" + filename = self.current_file + if os.path.exists(filename): + QMessageBox.information(self, "Info", f"File {filename} already exists, no need to create") + return + + template_content = self.templates.get(filename, "") + with open(filename, "w", encoding="utf-8") as f: + f.write(template_content) + + QMessageBox.information(self, "Success", f"File {filename} has been created") + self.load_file_content(self.file_selector.currentIndex()) + + def on_file_changed(self, path): + """當檔案被外部修改時即時更新""" + if path == self.current_file: + self.load_file_content(self.file_selector.currentIndex()) + + def save_file(self): + """將左邊編輯區內容儲存到目前檔案""" + if not hasattr(self, "current_file"): + QMessageBox.warning(self, "Error", "No file selected") + return + + content = self.left_editor.toPlainText() + with open(self.current_file, "w", encoding="utf-8") as f: + f.write(content) + QMessageBox.information(self, "Success", f"File {self.current_file} saved") + + def send_prompt(self): + """模擬發送 prompt 到 AI URL""" + ai_url = self.url_input.text().strip() + prompt_text = self.editable_panel.toPlainText().strip() + + if not ai_url: + QMessageBox.warning(self, "Warning", "Please enter AI URL") + return + if not prompt_text: + QMessageBox.warning(self, "Warning", "Please enter the prompt to send") + return + + # 成功提示 + QMessageBox.information(self, "Sending Prompt", + f"Sending prompt to {ai_url}:\n\n{prompt_text}") + +if __name__ == "__main__": + app = QApplication(sys.argv) + editor = PromptEditor() + editor.showMaximized() + sys.exit(app.exec()) diff --git a/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/__init__.py b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/first_code_review.py b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/first_code_review.py new file mode 100644 index 0000000..eb07aeb --- /dev/null +++ b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/first_code_review.py @@ -0,0 +1 @@ +FIRST_CODE_REVIEW = "" diff --git a/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/first_summary_prompt.py b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/first_summary_prompt.py new file mode 100644 index 0000000..517efcf --- /dev/null +++ b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/first_summary_prompt.py @@ -0,0 +1 @@ +FIRST_SUMMARY_PROMPT = "" diff --git a/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/global_rule.py b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/global_rule.py new file mode 100644 index 0000000..c967395 --- /dev/null +++ b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/global_rule.py @@ -0,0 +1 @@ +GLOBAL_RULE_TEMPLATE = "" \ No newline at end of file diff --git a/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/judge.py b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/judge.py new file mode 100644 index 0000000..f0a1655 --- /dev/null +++ b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/judge.py @@ -0,0 +1 @@ +JUDGE_TEMPLATE = "" \ No newline at end of file diff --git a/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/total_summary.py b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/total_summary.py new file mode 100644 index 0000000..d235c90 --- /dev/null +++ b/automation_ide/automation_editor_ui/prompt_edit_gui/prompt_templates/total_summary.py @@ -0,0 +1 @@ +TOTAL_SUMMARY_TEMPLATE = "" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 9fa5762..da221d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ -# Rename to build stable version -# This is stable version +# Rename to dev version +# This is dev version [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_ide" -version = "0.0.50" +name = "automation_ide_dev" +version = "0.0.58" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] @@ -14,9 +14,9 @@ description = "AutomationEditor for multi automation" requires-python = ">=3.10" license-files = ["LICENSE"] dependencies = [ - "je-editor", "je_auto_control", "je_web_runner", + "je_editor_dev", "je_auto_control", "je_web_runner", "je_load_density", "je_api_testka", "je-mail-thunder", - "automation-file", "PySide6==6.10.0", "test_pioneer" + "automation-file", "PySide6==6.10.1", "test_pioneer", "paramiko" ] classifiers = [ "Programming Language :: Python :: 3.10", @@ -35,5 +35,6 @@ Code = "https://github.com/Intergration-Automation-Testing/AutomationEditor" file = "README.md" content-type = "text/markdown" + [tool.setuptools.packages] find = { namespaces = false } diff --git a/dev.toml b/stable.toml similarity index 80% rename from dev.toml rename to stable.toml index 4db7076..0beb906 100644 --- a/dev.toml +++ b/stable.toml @@ -1,12 +1,12 @@ -# Rename to dev version -# This is dev version +# Rename to build stable version +# This is stable version [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_ide_dev" -version = "0.0.57" +name = "automation_ide" +version = "0.0.50" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] @@ -14,9 +14,9 @@ description = "AutomationEditor for multi automation" requires-python = ">=3.10" license-files = ["LICENSE"] dependencies = [ - "je_editor_dev", "je_auto_control", "je_web_runner", + "je-editor", "je_auto_control", "je_web_runner", "je_load_density", "je_api_testka", "je-mail-thunder", - "automation-file", "PySide6==6.10.0", "test_pioneer" + "automation-file", "PySide6==6.10.0", "test_pioneer", "paramiko" ] classifiers = [ "Programming Language :: Python :: 3.10", @@ -35,6 +35,5 @@ Code = "https://github.com/Intergration-Automation-Testing/AutomationEditor" file = "README.md" content-type = "text/markdown" - [tool.setuptools.packages] find = { namespaces = false } From bd797e7f10e53959b65cbd249a6b500cd0d6b1e6 Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 12:04:25 +0800 Subject: [PATCH 02/12] Update stable version Update stable version --- stable.toml => dev.toml | 13 +++++++------ pyproject.toml | 13 ++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) rename stable.toml => dev.toml (80%) diff --git a/stable.toml b/dev.toml similarity index 80% rename from stable.toml rename to dev.toml index 0beb906..da221d0 100644 --- a/stable.toml +++ b/dev.toml @@ -1,12 +1,12 @@ -# Rename to build stable version -# This is stable version +# Rename to dev version +# This is dev version [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_ide" -version = "0.0.50" +name = "automation_ide_dev" +version = "0.0.58" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] @@ -14,9 +14,9 @@ description = "AutomationEditor for multi automation" requires-python = ">=3.10" license-files = ["LICENSE"] dependencies = [ - "je-editor", "je_auto_control", "je_web_runner", + "je_editor_dev", "je_auto_control", "je_web_runner", "je_load_density", "je_api_testka", "je-mail-thunder", - "automation-file", "PySide6==6.10.0", "test_pioneer", "paramiko" + "automation-file", "PySide6==6.10.1", "test_pioneer", "paramiko" ] classifiers = [ "Programming Language :: Python :: 3.10", @@ -35,5 +35,6 @@ Code = "https://github.com/Intergration-Automation-Testing/AutomationEditor" file = "README.md" content-type = "text/markdown" + [tool.setuptools.packages] find = { namespaces = false } diff --git a/pyproject.toml b/pyproject.toml index da221d0..c386c2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ -# Rename to dev version -# This is dev version +# Rename to build stable version +# This is stable version [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_ide_dev" -version = "0.0.58" +name = "automation_ide" +version = "0.0.51" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] @@ -14,9 +14,9 @@ description = "AutomationEditor for multi automation" requires-python = ">=3.10" license-files = ["LICENSE"] dependencies = [ - "je_editor_dev", "je_auto_control", "je_web_runner", + "je-editor", "je_auto_control", "je_web_runner", "je_load_density", "je_api_testka", "je-mail-thunder", - "automation-file", "PySide6==6.10.1", "test_pioneer", "paramiko" + "automation-file", "PySide6==6.10.0", "test_pioneer", "paramiko" ] classifiers = [ "Programming Language :: Python :: 3.10", @@ -35,6 +35,5 @@ Code = "https://github.com/Intergration-Automation-Testing/AutomationEditor" file = "README.md" content-type = "text/markdown" - [tool.setuptools.packages] find = { namespaces = false } From 3bfad9acb05efc6b2e136001e366ab78801021e1 Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 12:10:28 +0800 Subject: [PATCH 03/12] Update stable version * Fix F-string new line now working on Py3.12 below --- .../connect_gui/ssh/ssh_command_widget.py | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py index 1dc0fdb..d770cf2 100644 --- a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py @@ -159,8 +159,8 @@ def connect_ssh(self): )) self.ssh_client.connect(hostname=host, port=port, username=user, pkey=pkey, timeout=10) except Exception as e: - raise RuntimeError(f"{ - language_wrapper.language_word_dict.get('ssh_command_widget_error_message_key_auth_failed')} {e}") + raise RuntimeError(f"{language_wrapper.language_word_dict.get( + 'ssh_command_widget_error_message_key_auth_failed')} {e}") else: self.ssh_client.connect( hostname=host, port=port, username=user, password=password, timeout=10 diff --git a/pyproject.toml b/pyproject.toml index c386c2d..a02bed8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "automation_ide" -version = "0.0.51" +version = "0.0.52" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] From 7349b888956acfbfb905c07c21f24352990fc114 Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 13:24:25 +0800 Subject: [PATCH 04/12] Update stable version * Refactor ssh_command_widget --- .../connect_gui/ssh/ssh_command_widget.py | 50 ++++++++++--------- pyproject.toml | 2 +- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py index d770cf2..17f8181 100644 --- a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py @@ -23,6 +23,7 @@ def __init__(self, chan: paramiko.Channel, parent=None): super().__init__(parent) self.chan = chan self._running = True + self.word_dict = language_wrapper.language_word_dict def run(self): try: @@ -43,10 +44,10 @@ def run(self): self.msleep(10) except Exception as e: self.closed.emit( - f"{language_wrapper.language_word_dict.get('ssh_command_widget_error_message_reader_failed')} {e}") + f"{self.word_dict.get('ssh_command_widget_error_message_reader_failed')} {e}") finally: self.closed.emit( - language_wrapper.language_word_dict.get("ssh_command_widget_log_message_reader_closed")) + self.word_dict.get("ssh_command_widget_log_message_reader_closed")) def stop(self): self._running = False @@ -55,8 +56,9 @@ def stop(self): class SSHCommandWidget(QWidget): def __init__(self, external_login_widget: LoginWidget = None, add_login_widget: bool = True): super().__init__() + self.word_dict = language_wrapper.language_word_dict self.setWindowTitle( - language_wrapper.language_word_dict.get("ssh_command_widget_window_title_ssh_command_widget")) + self.word_dict.get("ssh_command_widget_window_title_ssh_command_widget")) self.add_login_widget = add_login_widget @@ -78,7 +80,7 @@ def __init__(self, external_login_widget: LoginWidget = None, add_login_widget: self.terminal.setReadOnly(True) self.command_input_edit = QLineEdit() self.command_send_button = QPushButton( - language_wrapper.language_word_dict.get("ssh_command_widget_button_label_send_command")) + self.word_dict.get("ssh_command_widget_button_label_send_command")) self._setup_ui() self._bind_events() @@ -87,7 +89,7 @@ def _setup_ui(self): self.terminal.setReadOnly(True) self.terminal.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap) self.command_input_edit.setPlaceholderText( - language_wrapper.language_word_dict.get("ssh_command_widget_input_placeholder_command_line") + self.word_dict.get("ssh_command_widget_input_placeholder_command_line") ) terminal_panel = QVBoxLayout() @@ -127,8 +129,8 @@ def connect_ssh(self): if not host or not user: QMessageBox.warning( self, - language_wrapper.language_word_dict.get("ssh_command_widget_dialog_title_input_error"), - language_wrapper.language_word_dict.get( + self.word_dict.get("ssh_command_widget_dialog_title_input_error"), + self.word_dict.get( "ssh_command_widget_dialog_message_input_error_host_user_required")) return @@ -140,8 +142,8 @@ def connect_ssh(self): if not os.path.exists(key_path): QMessageBox.warning( self, - language_wrapper.language_word_dict.get("ssh_command_widget_dialog_title_key_error"), - language_wrapper.language_word_dict.get("ssh_command_widget_dialog_message_key_file_not_exist")) + self.word_dict.get("ssh_command_widget_dialog_title_key_error"), + self.word_dict.get("ssh_command_widget_dialog_message_key_file_not_exist")) return try: pkey = None @@ -154,13 +156,13 @@ def connect_ssh(self): continue if pkey is None: raise ValueError( - language_wrapper.language_word_dict.get( + self.word_dict.get( "ssh_command_widget_error_message_unsupported_private_key" )) self.ssh_client.connect(hostname=host, port=port, username=user, pkey=pkey, timeout=10) except Exception as e: - raise RuntimeError(f"{language_wrapper.language_word_dict.get( - 'ssh_command_widget_error_message_key_auth_failed')} {e}") + raise RuntimeError( + f"{self.word_dict.get('ssh_command_widget_error_message_key_auth_failed')} {e}") else: self.ssh_client.connect( hostname=host, port=port, username=user, password=password, timeout=10 @@ -173,15 +175,15 @@ def connect_ssh(self): self.reader_thread.closed.connect(self._on_closed) self.reader_thread.start() self.login_widget.status_label.setText( - language_wrapper.language_word_dict.get("ssh_command_widget_dialog_title_not_connected")) + self.word_dict.get("ssh_command_widget_dialog_title_not_connected")) self.append_text(f"{ - language_wrapper.language_word_dict.get('ssh_command_widget_log_message_connected')}" + self.word_dict.get('ssh_command_widget_log_message_connected')}" f" {host}:{port} as {user}\n") except Exception as e: self.login_widget.status_label.setText( - language_wrapper.language_word_dict.get('ssh_command_widget_status_label_disconnected')) + self.word_dict.get('ssh_command_widget_status_label_disconnected')) self.append_text(f"{ - language_wrapper.language_word_dict.get('ssh_command_widget_log_message_error')} {e}\n") + self.word_dict.get('ssh_command_widget_log_message_error')} {e}\n") self._cleanup() def _on_data(self, data: bytes): @@ -190,14 +192,14 @@ def _on_data(self, data: bytes): clean_text = ANSI_ESCAPE_PATTERN.sub('', text) self.append_text(clean_text) except Exception as error: - self.append_text(f"{language_wrapper.language_word_dict.get( + self.append_text(f"{self.word_dict.get( 'ssh_command_widget_error_message_decode_failed' )} {error}\n") def _on_closed(self, msg: str): self.append_text(f"\n{ - language_wrapper.language_word_dict.get('ssh_command_widget_log_message_channel_closed')} {msg}\n") - self.login_widget.status_label.setText(language_wrapper.language_word_dict.get( + self.word_dict.get('ssh_command_widget_log_message_channel_closed')} {msg}\n") + self.login_widget.status_label.setText(self.word_dict.get( 'ssh_command_widget_status_label_disconnected' )) @@ -210,21 +212,21 @@ def send_command(self): self.shell_channel.send(cmd + "\n") self.command_input_edit.clear() except Exception as e: - self.append_text(f"{language_wrapper.language_word_dict.get( + self.append_text(f"{self.word_dict.get( 'ssh_command_widget_error_message_send_failed' )} {e}\n") else: QMessageBox.information( self, - language_wrapper.language_word_dict.get('ssh_command_widget_dialog_title_not_connected'), - language_wrapper.language_word_dict.get('ssh_command_widget_dialog_message_not_connected_shell')) + self.word_dict.get('ssh_command_widget_dialog_title_not_connected'), + self.word_dict.get('ssh_command_widget_dialog_message_not_connected_shell')) def disconnect_ssh(self): self.append_text(f"{ - language_wrapper.language_word_dict.get('ssh_command_widget_log_message_disconnect_in_progress')} \n") + self.word_dict.get('ssh_command_widget_log_message_disconnect_in_progress')} \n") self._cleanup() self.login_widget.status_label.setText( - language_wrapper.language_word_dict.get('ssh_command_widget_status_label_disconnected')) + self.word_dict.get('ssh_command_widget_status_label_disconnected')) def _cleanup(self): try: diff --git a/pyproject.toml b/pyproject.toml index a02bed8..9da90d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "automation_ide" -version = "0.0.52" +version = "0.0.53" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] From 2990b317020f38f748b556d17f83ef34dcb06ba8 Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 13:32:11 +0800 Subject: [PATCH 05/12] Update dev version --- pyproject.toml | 13 +++++++------ dev.toml => stable.toml | 13 ++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) rename dev.toml => stable.toml (80%) diff --git a/pyproject.toml b/pyproject.toml index 9da90d3..08d7122 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ -# Rename to build stable version -# This is stable version +# Rename to dev version +# This is dev version [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_ide" -version = "0.0.53" +name = "automation_ide_dev" +version = "0.0.59" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] @@ -14,9 +14,9 @@ description = "AutomationEditor for multi automation" requires-python = ">=3.10" license-files = ["LICENSE"] dependencies = [ - "je-editor", "je_auto_control", "je_web_runner", + "je_editor_dev", "je_auto_control", "je_web_runner", "je_load_density", "je_api_testka", "je-mail-thunder", - "automation-file", "PySide6==6.10.0", "test_pioneer", "paramiko" + "automation-file", "PySide6==6.10.1", "test_pioneer", "paramiko" ] classifiers = [ "Programming Language :: Python :: 3.10", @@ -35,5 +35,6 @@ Code = "https://github.com/Intergration-Automation-Testing/AutomationEditor" file = "README.md" content-type = "text/markdown" + [tool.setuptools.packages] find = { namespaces = false } diff --git a/dev.toml b/stable.toml similarity index 80% rename from dev.toml rename to stable.toml index da221d0..9da90d3 100644 --- a/dev.toml +++ b/stable.toml @@ -1,12 +1,12 @@ -# Rename to dev version -# This is dev version +# Rename to build stable version +# This is stable version [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_ide_dev" -version = "0.0.58" +name = "automation_ide" +version = "0.0.53" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] @@ -14,9 +14,9 @@ description = "AutomationEditor for multi automation" requires-python = ">=3.10" license-files = ["LICENSE"] dependencies = [ - "je_editor_dev", "je_auto_control", "je_web_runner", + "je-editor", "je_auto_control", "je_web_runner", "je_load_density", "je_api_testka", "je-mail-thunder", - "automation-file", "PySide6==6.10.1", "test_pioneer", "paramiko" + "automation-file", "PySide6==6.10.0", "test_pioneer", "paramiko" ] classifiers = [ "Programming Language :: Python :: 3.10", @@ -35,6 +35,5 @@ Code = "https://github.com/Intergration-Automation-Testing/AutomationEditor" file = "README.md" content-type = "text/markdown" - [tool.setuptools.packages] find = { namespaces = false } From 2f701f3d0a2c8bd6fbd8add219f35ea373ecd889 Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 17:15:16 +0800 Subject: [PATCH 06/12] Update dev version * Fix f-string on different file --- .../connect_gui/ssh/ssh_command_widget.py | 6 +- .../connect_gui/ssh/ssh_file_viewer_widget.py | 66 +++++++++---------- .../connect_gui/url/ai_code_review_gui.py | 34 +++++----- pyproject.toml | 2 +- 4 files changed, 50 insertions(+), 58 deletions(-) diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py index 17f8181..17630a1 100644 --- a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py @@ -176,14 +176,12 @@ def connect_ssh(self): self.reader_thread.start() self.login_widget.status_label.setText( self.word_dict.get("ssh_command_widget_dialog_title_not_connected")) - self.append_text(f"{ - self.word_dict.get('ssh_command_widget_log_message_connected')}" + self.append_text(f"{self.word_dict.get('ssh_command_widget_log_message_connected')}" f" {host}:{port} as {user}\n") except Exception as e: self.login_widget.status_label.setText( self.word_dict.get('ssh_command_widget_status_label_disconnected')) - self.append_text(f"{ - self.word_dict.get('ssh_command_widget_log_message_error')} {e}\n") + self.append_text(f"{self.word_dict.get('ssh_command_widget_log_message_error')} {e}\n") self._cleanup() def _on_data(self, data: bytes): diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_file_viewer_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_file_viewer_widget.py index 76d21be..59f64c3 100644 --- a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_file_viewer_widget.py +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_file_viewer_widget.py @@ -20,6 +20,7 @@ class SFTPClientWrapper: """ def __init__(self): + self.word_dict = language_wrapper.language_word_dict self._ssh: Optional[paramiko.SSHClient] = None self._sftp: Optional[paramiko.SFTPClient] = None self.root_path: str = "/" @@ -66,7 +67,7 @@ def list_dir(self, path: str): """ if not self.connected: raise RuntimeError( - language_wrapper.language_word_dict.get("ssh_command_widget_dialog_title_not_connected") + self.word_dict.get("ssh_command_widget_dialog_title_not_connected") ) return self._sftp.listdir_attr(path) @@ -76,7 +77,7 @@ def is_dir(self, path: str) -> bool: 判斷路徑是否為目錄。 """ if not self.connected: - raise RuntimeError(language_wrapper.language_word_dict.get( + raise RuntimeError(self.word_dict.get( "ssh_command_widget_dialog_title_not_connected" )) try: @@ -138,8 +139,9 @@ class SSHFileTreeManager(QWidget): def __init__(self, external_login_widget: LoginWidget = None, add_login_widget: bool = True): super().__init__() + self.word_dict = language_wrapper.language_word_dict self.setWindowTitle( - language_wrapper.language_word_dict.get("ssh_file_viewer_window_title_file_tree_manager") + self.word_dict.get("ssh_file_viewer_window_title_file_tree_manager") ) self.add_login_widget = add_login_widget @@ -188,8 +190,8 @@ def _connect(self): if not host or not user or not pwd: QMessageBox.warning( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_missing_input"), - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_message_missing_input")) + self.word_dict.get("ssh_file_viewer_dialog_title_missing_input"), + self.word_dict.get("ssh_file_viewer_dialog_message_missing_input")) return try: self.client.connect(host, port, user, pwd) @@ -197,9 +199,8 @@ def _connect(self): except Exception as e: QMessageBox.critical( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_connection_failed"), - f"{language_wrapper.language_word_dict.get( - 'ssh_file_viewer_dialog_message_connection_failed')}: {e}") + self.word_dict.get("ssh_file_viewer_dialog_title_connection_failed"), + f"{self.word_dict.get('ssh_file_viewer_dialog_message_connection_failed')}: {e}") def _disconnect(self): """ @@ -302,9 +303,8 @@ def populate_children(self, parent_item: QTreeWidgetItem): except Exception as ex: QMessageBox.critical( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_list_error"), - f"{language_wrapper.language_word_dict.get( - 'ssh_file_viewer_dialog_message_list_failed')} '{path}': {ex}") + self.word_dict.get("ssh_file_viewer_dialog_title_list_error"), + f"{self.word_dict.get('ssh_file_viewer_dialog_message_list_failed')} '{path}': {ex}") def on_context_menu(self, pos): """ @@ -341,9 +341,8 @@ def on_context_menu(self, pos): except Exception as e: QMessageBox.critical( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_operation_failed"), - f"{language_wrapper.language_word_dict.get( - 'ssh_file_viewer_dialog_message_operation_failed')}: {e}") + self.word_dict.get("ssh_file_viewer_dialog_title_operation_failed"), + f"{self.word_dict.get('ssh_file_viewer_dialog_message_operation_failed')}: {e}") def action_refresh(self, item: Optional[QTreeWidgetItem]): """ @@ -367,15 +366,15 @@ def action_create_folder(self, item: Optional[QTreeWidgetItem]): if item is None: QMessageBox.information( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_no_selection"), - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_message_select_folder_to_create")) + self.word_dict.get("ssh_file_viewer_dialog_title_no_selection"), + self.word_dict.get("ssh_file_viewer_dialog_message_select_folder_to_create")) return base_path = item.text(3) if item.text(1) != "dir": base_path = os.path.dirname(base_path) name, ok = self.get_text( - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_create_folder"), - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_label_folder_name")) + self.word_dict.get("ssh_file_viewer_dialog_title_create_folder"), + self.word_dict.get("ssh_file_viewer_dialog_label_folder_name")) if not ok or not name.strip(): return new_path = os.path.join(base_path if base_path != "/" else "", name.strip()) @@ -392,9 +391,8 @@ def action_rename(self, item: Optional[QTreeWidgetItem]): return old_path = item.text(3) new_name, ok = self.get_text( - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_rename"), - f"{language_wrapper.language_word_dict.get( - 'ssh_file_viewer_dialog_label_new_name_for_item')}: {item.text(0)}") + self.word_dict.get("ssh_file_viewer_dialog_title_rename"), + f"{self.word_dict.get('ssh_file_viewer_dialog_label_new_name_for_item')}: {item.text(0)}") if not ok or not new_name.strip(): return base = os.path.dirname(old_path) or "/" @@ -415,9 +413,8 @@ def action_delete(self, item: Optional[QTreeWidgetItem]): path = item.text(3) reply = QMessageBox.question( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_confirm_delete"), - f"{language_wrapper.language_word_dict.get( - 'ssh_file_viewer_dialog_message_confirm_delete')} '{path}'?", + self.word_dict.get("ssh_file_viewer_dialog_title_confirm_delete"), + f"{self.word_dict.get('ssh_file_viewer_dialog_message_confirm_delete')} '{path}'?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply != QMessageBox.StandardButton.Yes: @@ -441,23 +438,22 @@ def action_download(self, item: Optional[QTreeWidgetItem]): if item is None or item.text(1) != "file": QMessageBox.information( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_invalid_selection"), - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_message_select_file_to_download")) + self.word_dict.get("ssh_file_viewer_dialog_title_invalid_selection"), + self.word_dict.get("ssh_file_viewer_dialog_message_select_file_to_download")) return remote_path = item.text(3) suggested = Path(remote_path).name local_path, _ = QFileDialog.getSaveFileName( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_save_as"), + self.word_dict.get("ssh_file_viewer_dialog_title_save_as"), suggested) if not local_path: return self.client.download(remote_path, local_path) QMessageBox.information( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_downloaded"), - f"{language_wrapper.language_word_dict.get( - 'ssh_file_viewer_dialog_message_saved_to')}: {local_path}") + self.word_dict.get("ssh_file_viewer_dialog_title_downloaded"), + f"{self.word_dict.get('ssh_file_viewer_dialog_message_saved_to')}: {local_path}") def action_upload(self, item: Optional[QTreeWidgetItem]): """ @@ -468,7 +464,7 @@ def action_upload(self, item: Optional[QTreeWidgetItem]): return target_dir = item.text(3) if item.text(1) == "dir" else os.path.dirname(item.text(3)) local_path, _ = QFileDialog.getOpenFileName( - self, language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_select_local_file"), "") + self, self.word_dict.get("ssh_file_viewer_dialog_title_select_local_file"), "") if not local_path: return filename = os.path.basename(local_path) @@ -477,9 +473,8 @@ def action_upload(self, item: Optional[QTreeWidgetItem]): self.client.upload(local_path, remote_path) QMessageBox.information( self, - language_wrapper.language_word_dict.get("ssh_file_viewer_dialog_title_uploaded"), - f"{language_wrapper.language_word_dict.get( - 'ssh_file_viewer_dialog_message_uploaded_to')}: {remote_path}") + self.word_dict.get("ssh_file_viewer_dialog_title_uploaded"), + f"{self.word_dict.get('ssh_file_viewer_dialog_message_uploaded_to')}: {remote_path}") # Refresh folder contents self.action_refresh(item) @@ -500,8 +495,7 @@ def eventFilter(self, obj, event): if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): w = obj.window() for b in w.findChildren(QPushButton): - if b.text().lower() in (language_wrapper.language_word_dict.get( - "ssh_file_viewer_dialog_button_ok"),): + if b.text().lower() in (self.word_dict.get("ssh_file_viewer_dialog_button_ok"),): b.click() return True return super().eventFilter(obj, event) diff --git a/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py b/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py index ed98924..e706891 100644 --- a/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py +++ b/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py @@ -11,7 +11,8 @@ class AICodeReviewClient(QWidget): def __init__(self): super().__init__() - self.setWindowTitle(language_wrapper.language_word_dict.get( + self.word_dict = language_wrapper.language_word_dict + self.setWindowTitle(self.word_dict.get( "ai_code_review_gui_window_title" )) @@ -31,14 +32,14 @@ def __init__(self): # URL url_layout = QHBoxLayout() - url_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("ai_code_review_gui_label_url"))) + url_layout.addWidget(QLabel(self.word_dict.get("ai_code_review_gui_label_url"))) self.url_input = QLineEdit() url_layout.addWidget(self.url_input) top_layout.addLayout(url_layout) # Method method_layout = QHBoxLayout() - method_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("ai_code_review_gui_label_method"))) + method_layout.addWidget(QLabel(self.word_dict.get("ai_code_review_gui_label_method"))) self.method_box = QComboBox() self.method_box.addItems(["GET", "POST", "PUT", "DELETE"]) method_layout.addWidget(self.method_box) @@ -54,14 +55,14 @@ def __init__(self): # 左邊:程式碼輸入 left_layout = QVBoxLayout() left_layout.addWidget(QLabel( - language_wrapper.language_word_dict.get("ai_code_review_gui_label_code_to_send"))) + self.word_dict.get("ai_code_review_gui_label_code_to_send"))) self.code_input = QTextEdit() self.code_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) left_layout.addWidget(self.code_input) # 右邊:回傳顯示 right_layout = QVBoxLayout() - right_layout.addWidget(QLabel(language_wrapper.language_word_dict.get("ai_code_review_gui_label_response"))) + right_layout.addWidget(QLabel(self.word_dict.get("ai_code_review_gui_label_response"))) self.response_panel = QTextEdit() self.response_panel.setReadOnly(True) self.response_panel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) @@ -77,7 +78,7 @@ def __init__(self): # 最下面:發送按鈕 # ------------------------------- self.send_button = QPushButton( - language_wrapper.language_word_dict.get("ai_code_review_gui_button_send_request")) + self.word_dict.get("ai_code_review_gui_button_send_request")) self.send_button.clicked.connect(self.send_request) main_layout.addWidget(self.send_button) @@ -86,9 +87,9 @@ def __init__(self): # ------------------------------- bottom_layout = QHBoxLayout() self.accept_button = QPushButton( - language_wrapper.language_word_dict.get("ai_code_review_gui_button_accept_response")) + self.word_dict.get("ai_code_review_gui_button_accept_response")) self.reject_button = QPushButton( - language_wrapper.language_word_dict.get("ai_code_review_gui_button_reject_response")) + self.word_dict.get("ai_code_review_gui_button_reject_response")) # 綁定事件 self.accept_button.clicked.connect(self.accept_response) @@ -109,7 +110,7 @@ def send_request(self): if not url: self.response_panel.setPlainText( - language_wrapper.language_word_dict.get("ai_code_review_gui_message_enter_valid_url")) + self.word_dict.get("ai_code_review_gui_message_enter_valid_url")) return # 檢查 URL 是否已紀錄 @@ -121,12 +122,12 @@ def send_request(self): if url in urls: self.response_panel.setPlainText( - language_wrapper.language_word_dict.get("ai_code_review_gui_message_url_already_recorded")) + self.word_dict.get("ai_code_review_gui_message_url_already_recorded")) else: with open(self.url_file, "a", encoding="utf-8") as f: f.write(url + "\n") self.response_panel.setPlainText( - language_wrapper.language_word_dict.get("ai_code_review_gui_message_new_url_recorded")) + self.word_dict.get("ai_code_review_gui_message_new_url_recorded")) try: if method == "GET": @@ -139,27 +140,27 @@ def send_request(self): response = requests.delete(url) else: self.response_panel.setPlainText( - language_wrapper.language_word_dict.get("ai_code_review_gui_message_unsupported_http_method")) + self.word_dict.get("ai_code_review_gui_message_unsupported_http_method")) return self.response_panel.append(response.text) except Exception as e: self.response_panel.setPlainText(f"{ - language_wrapper.language_word_dict.get('ai_code_review_gui_message_error')}: {e}") + self.word_dict.get('ai_code_review_gui_message_error')}: {e}") def accept_response(self): """Accept response code and save""" self.accept_count += 1 self.response_panel.append(f"\n{ - language_wrapper.language_word_dict.get('ai_code_review_gui_status_accepted')}") + self.word_dict.get('ai_code_review_gui_status_accepted')}") self.save_stats() def reject_response(self): """Reject response code and save""" self.reject_count += 1 self.response_panel.append(f"\n{ - language_wrapper.language_word_dict.get('ai_code_review_gui_status_rejected')}") + self.word_dict.get('ai_code_review_gui_status_rejected')}") self.save_stats() def save_stats(self): @@ -169,8 +170,7 @@ def save_stats(self): f.write(f"Accepted: {self.accept_count}\n") f.write(f"Rejected: {self.reject_count}\n") except Exception as e: - self.response_panel.append(f"\n[{ - language_wrapper.language_word_dict.get("ai_code_review_gui_status_save_failed")}: {e}]") + self.response_panel.append(f"\n[{self.word_dict.get("ai_code_review_gui_status_save_failed")}: {e}]") if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 08d7122..5318549 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "automation_ide_dev" -version = "0.0.59" +version = "0.0.60" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] From d08ff2a23c61ff9ee0d4e356563e051f8f265d0c Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 17:28:55 +0800 Subject: [PATCH 07/12] Update dev version --- .../automation_editor_ui/connect_gui/ssh/ssh_command_widget.py | 3 +-- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py index 17630a1..61a7cac 100644 --- a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py @@ -190,8 +190,7 @@ def _on_data(self, data: bytes): clean_text = ANSI_ESCAPE_PATTERN.sub('', text) self.append_text(clean_text) except Exception as error: - self.append_text(f"{self.word_dict.get( - 'ssh_command_widget_error_message_decode_failed' + self.append_text(f"{self.word_dict.get('ssh_command_widget_error_message_decode_failed' )} {error}\n") def _on_closed(self, msg: str): diff --git a/pyproject.toml b/pyproject.toml index 5318549..b8b9efc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "automation_ide_dev" -version = "0.0.60" +version = "0.0.61" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] From ed2d207d94bd10234c755c4677f8ac4bc9d7d43b Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 17:58:42 +0800 Subject: [PATCH 08/12] Update dev version --- .../connect_gui/ssh/ssh_command_widget.py | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py index 61a7cac..689f4d5 100644 --- a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py @@ -190,8 +190,8 @@ def _on_data(self, data: bytes): clean_text = ANSI_ESCAPE_PATTERN.sub('', text) self.append_text(clean_text) except Exception as error: - self.append_text(f"{self.word_dict.get('ssh_command_widget_error_message_decode_failed' - )} {error}\n") + self.append_text(f"{self.word_dict.get('ssh_command_widget_error_message_decode_failed')}" + f" {error}\n") def _on_closed(self, msg: str): self.append_text(f"\n{ diff --git a/pyproject.toml b/pyproject.toml index b8b9efc..8f544c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "automation_ide_dev" -version = "0.0.61" +version = "0.0.63" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] From c28f9eb94ddcbdceb73c5afdc606f34f4f451d81 Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 18:07:25 +0800 Subject: [PATCH 09/12] Update dev version --- .../connect_gui/ssh/ssh_command_widget.py | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py index 689f4d5..8dd96ca 100644 --- a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py @@ -194,8 +194,8 @@ def _on_data(self, data: bytes): f" {error}\n") def _on_closed(self, msg: str): - self.append_text(f"\n{ - self.word_dict.get('ssh_command_widget_log_message_channel_closed')} {msg}\n") + self.append_text(f"\n{self.word_dict.get('ssh_command_widget_log_message_channel_closed')}" + f" {msg}\n") self.login_widget.status_label.setText(self.word_dict.get( 'ssh_command_widget_status_label_disconnected' )) diff --git a/pyproject.toml b/pyproject.toml index 8f544c3..d151b59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "automation_ide_dev" -version = "0.0.63" +version = "0.0.64" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] From eb9ac0f3098a0ae10271e38fa810f909e3def5d5 Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 19:37:49 +0800 Subject: [PATCH 10/12] Update dev version * Fix f-string --- .../connect_gui/ssh/ssh_command_widget.py | 7 ++----- .../connect_gui/url/ai_code_review_gui.py | 9 +++------ pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py index 8dd96ca..5cc1698 100644 --- a/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py +++ b/automation_ide/automation_editor_ui/connect_gui/ssh/ssh_command_widget.py @@ -209,9 +209,7 @@ def send_command(self): self.shell_channel.send(cmd + "\n") self.command_input_edit.clear() except Exception as e: - self.append_text(f"{self.word_dict.get( - 'ssh_command_widget_error_message_send_failed' - )} {e}\n") + self.append_text(f"{self.word_dict.get('ssh_command_widget_error_message_send_failed')} {e}\n") else: QMessageBox.information( self, @@ -219,8 +217,7 @@ def send_command(self): self.word_dict.get('ssh_command_widget_dialog_message_not_connected_shell')) def disconnect_ssh(self): - self.append_text(f"{ - self.word_dict.get('ssh_command_widget_log_message_disconnect_in_progress')} \n") + self.append_text(f"{self.word_dict.get('ssh_command_widget_log_message_disconnect_in_progress')} \n") self._cleanup() self.login_widget.status_label.setText( self.word_dict.get('ssh_command_widget_status_label_disconnected')) diff --git a/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py b/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py index e706891..8c969e2 100644 --- a/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py +++ b/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py @@ -146,21 +146,18 @@ def send_request(self): self.response_panel.append(response.text) except Exception as e: - self.response_panel.setPlainText(f"{ - self.word_dict.get('ai_code_review_gui_message_error')}: {e}") + self.response_panel.setPlainText(f"{self.word_dict.get('ai_code_review_gui_message_error')}: {e}") def accept_response(self): """Accept response code and save""" self.accept_count += 1 - self.response_panel.append(f"\n{ - self.word_dict.get('ai_code_review_gui_status_accepted')}") + self.response_panel.append(f"\n{self.word_dict.get('ai_code_review_gui_status_accepted')}") self.save_stats() def reject_response(self): """Reject response code and save""" self.reject_count += 1 - self.response_panel.append(f"\n{ - self.word_dict.get('ai_code_review_gui_status_rejected')}") + self.response_panel.append(f"\n{self.word_dict.get('ai_code_review_gui_status_rejected')}") self.save_stats() def save_stats(self): diff --git a/pyproject.toml b/pyproject.toml index d151b59..6ec0fd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "automation_ide_dev" -version = "0.0.64" +version = "0.0.65" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] From 472e66622be67c04c3cbd6b9adb24ee6863bc926 Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 20:03:46 +0800 Subject: [PATCH 11/12] Update dev version --- .../automation_editor_ui/connect_gui/url/ai_code_review_gui.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py b/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py index 8c969e2..19aa622 100644 --- a/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py +++ b/automation_ide/automation_editor_ui/connect_gui/url/ai_code_review_gui.py @@ -167,7 +167,7 @@ def save_stats(self): f.write(f"Accepted: {self.accept_count}\n") f.write(f"Rejected: {self.reject_count}\n") except Exception as e: - self.response_panel.append(f"\n[{self.word_dict.get("ai_code_review_gui_status_save_failed")}: {e}]") + self.response_panel.append(f"\n[{self.word_dict.get('ai_code_review_gui_status_save_failed')}: {e}]") if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 6ec0fd3..d33f3f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "automation_ide_dev" -version = "0.0.65" +version = "0.0.66" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] From 42d53e6f2d9059b36ef6fd35ac65192fe4574f4b Mon Sep 17 00:00:00 2001 From: JE-Chen Date: Sun, 7 Dec 2025 20:19:23 +0800 Subject: [PATCH 12/12] Update stable version --- stable.toml => dev.toml | 13 +++++++------ pyproject.toml | 13 ++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) rename stable.toml => dev.toml (80%) diff --git a/stable.toml b/dev.toml similarity index 80% rename from stable.toml rename to dev.toml index 9da90d3..d33f3f8 100644 --- a/stable.toml +++ b/dev.toml @@ -1,12 +1,12 @@ -# Rename to build stable version -# This is stable version +# Rename to dev version +# This is dev version [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_ide" -version = "0.0.53" +name = "automation_ide_dev" +version = "0.0.66" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] @@ -14,9 +14,9 @@ description = "AutomationEditor for multi automation" requires-python = ">=3.10" license-files = ["LICENSE"] dependencies = [ - "je-editor", "je_auto_control", "je_web_runner", + "je_editor_dev", "je_auto_control", "je_web_runner", "je_load_density", "je_api_testka", "je-mail-thunder", - "automation-file", "PySide6==6.10.0", "test_pioneer", "paramiko" + "automation-file", "PySide6==6.10.1", "test_pioneer", "paramiko" ] classifiers = [ "Programming Language :: Python :: 3.10", @@ -35,5 +35,6 @@ Code = "https://github.com/Intergration-Automation-Testing/AutomationEditor" file = "README.md" content-type = "text/markdown" + [tool.setuptools.packages] find = { namespaces = false } diff --git a/pyproject.toml b/pyproject.toml index d33f3f8..f99ea83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ -# Rename to dev version -# This is dev version +# Rename to build stable version +# This is stable version [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_ide_dev" -version = "0.0.66" +name = "automation_ide" +version = "0.0.54" authors = [ { name = "JE-Chen", email = "jechenmailman@gmail.com" }, ] @@ -14,9 +14,9 @@ description = "AutomationEditor for multi automation" requires-python = ">=3.10" license-files = ["LICENSE"] dependencies = [ - "je_editor_dev", "je_auto_control", "je_web_runner", + "je-editor", "je_auto_control", "je_web_runner", "je_load_density", "je_api_testka", "je-mail-thunder", - "automation-file", "PySide6==6.10.1", "test_pioneer", "paramiko" + "automation-file", "PySide6==6.10.0", "test_pioneer", "paramiko" ] classifiers = [ "Programming Language :: Python :: 3.10", @@ -35,6 +35,5 @@ Code = "https://github.com/Intergration-Automation-Testing/AutomationEditor" file = "README.md" content-type = "text/markdown" - [tool.setuptools.packages] find = { namespaces = false }