feat: Add aider integration workflow command

This commit is contained in:
bobo 2024-07-18 16:02:25 +08:00
parent c4352d8486
commit 4e38d7785b
10 changed files with 408 additions and 0 deletions

19
merico/aider/README.md Normal file
View File

@ -0,0 +1,19 @@
### 操作指南
aider工作流命令使用步骤如下
1. 确保已经使用 `/aider.files.add` 命令添加了需要处理的文件。
2. 输入 `/aider <message>` 命令,其中 `<message>` 是你想要aider执行的任务描述。
3. 等待aider生成建议的更改。
4. 系统会自动显示每个文件的Diff View你可以选择是否接受修改。
5. 对于多个文件的更改,系统会在每个文件之后询问是否继续查看下一个文件的更改。
注意事项:
- 如果没有添加任何文件到aider命令将会提示你先使用 'aider.files.add' 命令添加文件。
- 你可以使用 `aider.files.remove` 命令从aider中移除文件。
- 所有的更改都会在IDE中以Diff View的形式展示你可以在查看后决定是否应用这些更改。
使用示例:
/aider 重构这段代码以提高性能
这个命令会让aider分析当前添加的文件并提供重构建议以提高代码性能。

216
merico/aider/command.py Normal file
View File

@ -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 <message>
"""
if len(sys.argv) < 2:
print("Usage: python command.py <message>", 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()

8
merico/aider/command.yml Normal file
View File

@ -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"

View File

@ -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_path>", file=sys.stderr)
sys.exit(1)
file_path = sys.argv[1]
add_file(file_path)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,4 @@
description: "add files to aider"
input: required
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -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()

View File

@ -0,0 +1,3 @@
description: "list files in aider"
steps:
- run: $devchat_python $command_path/command.py

View File

@ -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_path>", file=sys.stderr)
sys.exit(1)
file_path = sys.argv[1]
remove_file(file_path)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,4 @@
description: "remove files from aider"
input: required
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -0,0 +1,2 @@
git+https://gitee.com/imlaji/aider.git@main
git+https://gitee.com/devchat-ai/devchat.git@aider