From 4e38d7785b4292d5ddabb71d6e9e8cb144b0ef3a Mon Sep 17 00:00:00 2001 From: bobo Date: Thu, 18 Jul 2024 16:02:25 +0800 Subject: [PATCH] feat: Add aider integration workflow command --- merico/aider/README.md | 19 +++ merico/aider/command.py | 216 ++++++++++++++++++++++++++ merico/aider/command.yml | 8 + merico/aider/files/add/command.py | 59 +++++++ merico/aider/files/add/command.yml | 4 + merico/aider/files/list/command.py | 28 ++++ merico/aider/files/list/command.yml | 3 + merico/aider/files/remove/command.py | 65 ++++++++ merico/aider/files/remove/command.yml | 4 + merico/aider/requirements.txt | 2 + 10 files changed, 408 insertions(+) create mode 100644 merico/aider/README.md create mode 100644 merico/aider/command.py create mode 100644 merico/aider/command.yml create mode 100644 merico/aider/files/add/command.py create mode 100644 merico/aider/files/add/command.yml create mode 100644 merico/aider/files/list/command.py create mode 100644 merico/aider/files/list/command.yml create mode 100644 merico/aider/files/remove/command.py create mode 100644 merico/aider/files/remove/command.yml create mode 100644 merico/aider/requirements.txt diff --git a/merico/aider/README.md b/merico/aider/README.md new file mode 100644 index 0000000..08a4c56 --- /dev/null +++ b/merico/aider/README.md @@ -0,0 +1,19 @@ +### 操作指南 + +aider工作流命令使用步骤如下: + +1. 确保已经使用 `/aider.files.add` 命令添加了需要处理的文件。 +2. 输入 `/aider ` 命令,其中 `` 是你想要aider执行的任务描述。 +3. 等待aider生成建议的更改。 +4. 系统会自动显示每个文件的Diff View,你可以选择是否接受修改。 +5. 对于多个文件的更改,系统会在每个文件之后询问是否继续查看下一个文件的更改。 + +注意事项: +- 如果没有添加任何文件到aider,命令将会提示你先使用 'aider.files.add' 命令添加文件。 +- 你可以使用 `aider.files.remove` 命令从aider中移除文件。 +- 所有的更改都会在IDE中以Diff View的形式展示,你可以在查看后决定是否应用这些更改。 + +使用示例: +/aider 重构这段代码以提高性能 + +这个命令会让aider分析当前添加的文件,并提供重构建议以提高代码性能。 \ No newline at end of file diff --git a/merico/aider/command.py b/merico/aider/command.py new file mode 100644 index 0000000..fa31f65 --- /dev/null +++ b/merico/aider/command.py @@ -0,0 +1,216 @@ +import os +import sys +import json +import subprocess + +from devchat.ide import IDEService +from lib.chatmark import Button + +GLOBAL_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".chat", ".workflow_config.json") + +def save_config(config_path, item, value): + if os.path.exists(config_path): + with open(config_path, "r", encoding="utf-8") as f: + config = json.load(f) + else: + config = {} + + config[item] = value + with open(config_path, "w", encoding="utf-8") as f: + json.dump(config, f, indent=4) + +def write_python_path_to_config(): + """ + Write the current system Python path to the configuration. + """ + python_path = sys.executable + save_config(GLOBAL_CONFIG_PATH, 'aider_python', python_path) + print(f"Python path '{python_path}' has been written to the configuration.") + +def get_aider_files(): + """ + 从.chat/.aider_files文件中读取aider文件列表 + """ + aider_files_path = os.path.join('.chat', '.aider_files') + if not os.path.exists(aider_files_path): + return [] + + with open(aider_files_path, 'r') as f: + return [line.strip() for line in f if line.strip()] + +def run_aider(message, files): + """ + 运行aider命令 + """ + python = sys.executable + model = os.environ.get('LLM_MODEL', 'gpt-3.5-turbo-1106') + + cmd = [ + python, + "-m", + "aider", + "--model", + f"openai/{model}", + "--yes", + "--no-auto-commits", + "--dry-run", + "--no-pretty", + "--message", + message, + ] + files + + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + has_started = False + aider_output = "" + for line in process.stdout: + if "run with --help" in line or 'run "aider --help"' in line: + has_started = True + continue + if has_started: + aider_output += line + print(line, end="", flush=True) + + return_code = process.wait() + + if return_code != 0: + for line in process.stderr: + print(f"Error: {line.strip()}", file=sys.stderr) + sys.exit(return_code) + + return aider_output + +def apply_changes(changes, files): + """ + 应用aider生成的更改 + """ + changes_file = '.chat/changes.txt' + os.makedirs(os.path.dirname(changes_file), exist_ok=True) + with open(changes_file, 'w') as f: + f.write(changes) + + python = sys.executable + model = os.environ.get('LLM_MODEL', 'gpt-3.5-turbo-1106') + + cmd = [ + python, + "-m", + "aider", + "--model", + f"openai/{model}", + "--yes", + "--no-auto-commits", + "--apply", + changes_file, + ] + files + + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + has_started = False + for line in process.stdout: + if "Model:" in line: + has_started = True + continue + if has_started: + print(line, end="", flush=True) + + return_code = process.wait() + + if return_code != 0: + for line in process.stderr: + print(f"Error: {line.strip()}", file=sys.stderr) + sys.exit(return_code) + + os.remove(changes_file) + +def main(): + """ + Main function to run the aider command. + + This function performs the following tasks: + 1. Checks for correct command-line usage + 2. Writes the current Python path to the configuration + 3. Retrieves the list of files to be processed + 4. Runs the aider command with the given message + 5. Applies the suggested changes + 6. Displays the differences in the IDE + + Usage: python command.py + """ + if len(sys.argv) < 2: + print("Usage: python command.py ", file=sys.stderr) + sys.exit(1) + + write_python_path_to_config() + + message = sys.argv[1] + files = get_aider_files() + + if not files: + print("No files added to aider. Please add files using 'aider.files.add' command.", file=sys.stderr) + sys.exit(1) + + print("Running aider...\n", flush=True) + changes = run_aider(message, files) + + if not changes: + print("No changes suggested by aider.") + sys.exit(0) + + print("\nApplying changes...\n", flush=True) + + # 保存原始文件内容 + original_contents = {} + for file in files: + with open(file, 'r') as f: + original_contents[file] = f.read() + + # 应用更改 + apply_changes(changes, files) + + # 读取更新后的文件内容 + updated_contents = {} + for file in files: + with open(file, 'r') as f: + updated_contents[file] = f.read() + + # 还原原始文件内容 + for file in files: + with open(file, 'w') as f: + f.write(original_contents[file]) + + # 使用 IDEService 展示差异 + ide_service = IDEService() + for index,file in enumerate(files): + ide_service.diff_apply(file, updated_contents[file]) + if index < len(files) - 1: + # 等待用户确认 + button = Button( + [ + "Show Next Changes", + "Cancel" + ], + ) + button.render() + + idx = button.clicked + print("click button:", idx) + if idx == 0: + continue + else: + break + + print("Changes have been displayed in the IDE.") + +if __name__ == "__main__": + main() diff --git a/merico/aider/command.yml b/merico/aider/command.yml new file mode 100644 index 0000000..af1c2b2 --- /dev/null +++ b/merico/aider/command.yml @@ -0,0 +1,8 @@ +description: "aider command" +workflow_python: + env_name: devchat-aider-env + version: 3.11.0 + dependencies: requirements.txt +input: required +steps: + - run: $workflow_python $command_path/command.py "$input" \ No newline at end of file diff --git a/merico/aider/files/add/command.py b/merico/aider/files/add/command.py new file mode 100644 index 0000000..7bc4986 --- /dev/null +++ b/merico/aider/files/add/command.py @@ -0,0 +1,59 @@ +import os +import sys + +def is_valid_path(path): + """ + 检查路径是否为有效的文件路径形式 + """ + try: + # 尝试规范化路径 + normalized_path = os.path.normpath(path) + # 检查路径是否是绝对路径或相对路径 + return os.path.isabs(normalized_path) or not os.path.dirname(normalized_path) == normalized_path + except Exception: + return False + +def add_file(file_path): + # 1. 检查是否为有效的文件路径形式 + if not is_valid_path(file_path): + print(f"Error: '{file_path}' is not a valid file path format.", file=sys.stderr) + sys.exit(1) + + # 获取绝对路径 + abs_file_path = file_path.strip() + + # 2. 将新增文件路径存储到.chat/.aider_files文件中 + aider_files_path = os.path.join('.chat', '.aider_files') + + # 确保.chat目录存在 + os.makedirs(os.path.dirname(aider_files_path), exist_ok=True) + + # 读取现有文件列表 + existing_files = set() + if os.path.exists(aider_files_path): + with open(aider_files_path, 'r') as f: + existing_files = set(line.strip() for line in f) + + # 添加新文件 + existing_files.add(abs_file_path) + + # 写入更新后的文件列表 + with open(aider_files_path, 'w') as f: + for file in sorted(existing_files): + f.write(f"{file}\n") + + print(f"Added '{abs_file_path}' to aider files.") + print("\nCurrent aider files:") + for file in sorted(existing_files): + print(f"- {file}") + +def main(): + if len(sys.argv) != 2 or sys.argv[1].strip() == "": + print("Usage: /aider.files.add ", file=sys.stderr) + sys.exit(1) + + file_path = sys.argv[1] + add_file(file_path) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/merico/aider/files/add/command.yml b/merico/aider/files/add/command.yml new file mode 100644 index 0000000..dc208ef --- /dev/null +++ b/merico/aider/files/add/command.yml @@ -0,0 +1,4 @@ +description: "add files to aider" +input: required +steps: + - run: $devchat_python $command_path/command.py "$input" \ No newline at end of file diff --git a/merico/aider/files/list/command.py b/merico/aider/files/list/command.py new file mode 100644 index 0000000..25f77b0 --- /dev/null +++ b/merico/aider/files/list/command.py @@ -0,0 +1,28 @@ +import os +import sys + +def list_files(): + aider_files_path = os.path.join('.chat', '.aider_files') + + # 确保.chat/.aider_files文件存在 + if not os.path.exists(aider_files_path): + print("No files have been added to aider yet.") + sys.exit(0) + + # 读取文件列表 + with open(aider_files_path, 'r') as f: + files = [line.strip() for line in f] + + # 打印文件列表 + if files: + print("Aider files:") + for file in sorted(files): + print(f"- {file}") + else: + print("No files found in aider.") + +def main(): + list_files() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/merico/aider/files/list/command.yml b/merico/aider/files/list/command.yml new file mode 100644 index 0000000..9eafae1 --- /dev/null +++ b/merico/aider/files/list/command.yml @@ -0,0 +1,3 @@ +description: "list files in aider" +steps: + - run: $devchat_python $command_path/command.py \ No newline at end of file diff --git a/merico/aider/files/remove/command.py b/merico/aider/files/remove/command.py new file mode 100644 index 0000000..d999e60 --- /dev/null +++ b/merico/aider/files/remove/command.py @@ -0,0 +1,65 @@ +import os +import sys + +def is_valid_path(path): + """ + 检查路径是否为有效的文件路径形式 + """ + try: + # 尝试规范化路径 + normalized_path = os.path.normpath(path) + # 检查路径是否是绝对路径或相对路径 + return os.path.isabs(normalized_path) or not os.path.dirname(normalized_path) == normalized_path + except Exception: + return False + +def remove_file(file_path): + # 1. 检查是否为有效的文件路径形式 + if not is_valid_path(file_path): + print(f"Error: '{file_path}' is not a valid file path format.", file=sys.stderr) + sys.exit(1) + + # 获取绝对路径 + abs_file_path = file_path.strip() + + # 2. 从.chat/.aider_files文件中移除指定文件路径 + aider_files_path = os.path.join('.chat', '.aider_files') + + # 确保.chat目录存在 + if not os.path.exists(aider_files_path): + print(f"Error: '{aider_files_path}' does not exist.", file=sys.stderr) + sys.exit(1) + + # 读取现有文件列表 + existing_files = set() + with open(aider_files_path, 'r') as f: + existing_files = set(line.strip() for line in f) + + # 检查文件是否在列表中 + if abs_file_path not in existing_files: + print(f"'{abs_file_path}' is not in aider files.") + sys.exit(0) + + # 移除文件 + existing_files.remove(abs_file_path) + + # 写入更新后的文件列表 + with open(aider_files_path, 'w') as f: + for file in sorted(existing_files): + f.write(f"{file}\n") + + print(f"Removed '{abs_file_path}' from aider files.") + print("\nCurrent aider files:") + for file in sorted(existing_files): + print(f"- {file}") + +def main(): + if len(sys.argv) != 2 or sys.argv[1].strip() == "": + print("Usage: /aider.files.remove ", file=sys.stderr) + sys.exit(1) + + file_path = sys.argv[1] + remove_file(file_path) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/merico/aider/files/remove/command.yml b/merico/aider/files/remove/command.yml new file mode 100644 index 0000000..f89f3e7 --- /dev/null +++ b/merico/aider/files/remove/command.yml @@ -0,0 +1,4 @@ +description: "remove files from aider" +input: required +steps: + - run: $devchat_python $command_path/command.py "$input" \ No newline at end of file diff --git a/merico/aider/requirements.txt b/merico/aider/requirements.txt new file mode 100644 index 0000000..02f9e9f --- /dev/null +++ b/merico/aider/requirements.txt @@ -0,0 +1,2 @@ +git+https://gitee.com/imlaji/aider.git@main +git+https://gitee.com/devchat-ai/devchat.git@aider