jarvis-ai-assistant 0.1.97__py3-none-any.whl → 0.1.99__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of jarvis-ai-assistant might be problematic. Click here for more details.

Files changed (41) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +199 -157
  3. jarvis/jarvis_code_agent/__init__.py +0 -0
  4. jarvis/jarvis_code_agent/main.py +203 -0
  5. jarvis/jarvis_codebase/main.py +412 -284
  6. jarvis/jarvis_coder/file_select.py +209 -0
  7. jarvis/jarvis_coder/git_utils.py +81 -19
  8. jarvis/jarvis_coder/main.py +68 -446
  9. jarvis/jarvis_coder/patch_handler.py +117 -47
  10. jarvis/jarvis_coder/plan_generator.py +69 -27
  11. jarvis/jarvis_platform/main.py +38 -38
  12. jarvis/jarvis_rag/main.py +189 -189
  13. jarvis/jarvis_smart_shell/main.py +22 -24
  14. jarvis/models/base.py +6 -1
  15. jarvis/models/ollama.py +2 -2
  16. jarvis/models/registry.py +3 -6
  17. jarvis/tools/ask_user.py +6 -6
  18. jarvis/tools/codebase_qa.py +5 -7
  19. jarvis/tools/create_code_sub_agent.py +55 -0
  20. jarvis/tools/{sub_agent.py → create_sub_agent.py} +4 -1
  21. jarvis/tools/execute_code_modification.py +72 -0
  22. jarvis/tools/{file_ops.py → file_operation.py} +13 -14
  23. jarvis/tools/find_related_files.py +86 -0
  24. jarvis/tools/methodology.py +25 -25
  25. jarvis/tools/rag.py +32 -32
  26. jarvis/tools/registry.py +72 -36
  27. jarvis/tools/search.py +1 -1
  28. jarvis/tools/select_code_files.py +64 -0
  29. jarvis/utils.py +153 -49
  30. {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/METADATA +1 -1
  31. jarvis_ai_assistant-0.1.99.dist-info/RECORD +52 -0
  32. {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/entry_points.txt +2 -1
  33. jarvis/main.py +0 -155
  34. jarvis/tools/coder.py +0 -69
  35. jarvis_ai_assistant-0.1.97.dist-info/RECORD +0 -47
  36. /jarvis/tools/{shell.py → execute_shell.py} +0 -0
  37. /jarvis/tools/{generator.py → generate_tool.py} +0 -0
  38. /jarvis/tools/{webpage.py → read_webpage.py} +0 -0
  39. {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/LICENSE +0 -0
  40. {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/WHEEL +0 -0
  41. {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@ import threading
3
3
  from typing import Dict, Any, List, Optional
4
4
  import re
5
5
 
6
+ from jarvis.jarvis_coder.file_select import select_files
6
7
  from jarvis.utils import OutputType, PrettyOutput, find_git_root, get_max_context_length, is_long_context, load_env_from_file, while_success
7
8
  from jarvis.models.registry import PlatformRegistry
8
9
  from jarvis.jarvis_codebase.main import CodeBase
@@ -12,7 +13,7 @@ from prompt_toolkit.formatted_text import FormattedText
12
13
  from prompt_toolkit.styles import Style
13
14
  import fnmatch
14
15
  from .patch_handler import PatchHandler
15
- from .git_utils import generate_commit_message, save_edit_record
16
+ from .git_utils import generate_commit_message, init_git_repo, save_edit_record
16
17
  from .plan_generator import PlanGenerator
17
18
 
18
19
  # 全局锁对象
@@ -20,484 +21,120 @@ index_lock = threading.Lock()
20
21
 
21
22
  class JarvisCoder:
22
23
  def __init__(self, root_dir: str, language: Optional[str] = "python"):
23
- """初始化代码修改工具"""
24
+ """Initialize code modification tool"""
24
25
  self.root_dir = root_dir
25
26
  self.language = language
26
27
  self._init_directories()
27
28
  self._init_codebase()
28
29
 
29
30
  def _init_directories(self):
30
- """初始化目录"""
31
+ """Initialize directories"""
31
32
  self.max_context_length = get_max_context_length()
32
-
33
- root_dir = find_git_root(self.root_dir)
34
- if not root_dir:
35
- root_dir = self.root_dir
36
-
37
- self.root_dir = root_dir
38
-
39
- PrettyOutput.print(f"Git根目录: {self.root_dir}", OutputType.INFO)
40
-
41
- # 1. 判断代码库路径是否存在,如果不存在,创建
42
- if not os.path.exists(self.root_dir):
43
- PrettyOutput.print(
44
- "Root directory does not exist, creating...", OutputType.INFO)
45
- os.makedirs(self.root_dir)
46
-
47
- os.chdir(self.root_dir)
48
-
49
- # 2. 创建 .jarvis-coder 目录
50
- self.jarvis_dir = os.path.join(self.root_dir, ".jarvis-coder")
51
- if not os.path.exists(self.jarvis_dir):
52
- os.makedirs(self.jarvis_dir)
53
-
54
- self.record_dir = os.path.join(self.jarvis_dir, "record")
55
- if not os.path.exists(self.record_dir):
56
- os.makedirs(self.record_dir)
57
-
58
- # 3. 处理 .gitignore 文件
59
- gitignore_path = os.path.join(self.root_dir, ".gitignore")
60
- gitignore_modified = False
61
- jarvis_ignore_pattern = ".jarvis-*"
62
-
63
- # 3.1 如果 .gitignore 不存在,创建它
64
- if not os.path.exists(gitignore_path):
65
- PrettyOutput.print("创建 .gitignore 文件", OutputType.INFO)
66
- with open(gitignore_path, "w", encoding="utf-8") as f:
67
- f.write(f"{jarvis_ignore_pattern}\n")
68
- gitignore_modified = True
69
- else:
70
- # 3.2 检查是否已经包含 .jarvis-* 模式
71
- with open(gitignore_path, "r", encoding="utf-8") as f:
72
- content = f.read()
73
-
74
- # 检查是否需要添加 .jarvis-* 模式
75
- if jarvis_ignore_pattern not in content.split("\n"):
76
- PrettyOutput.print("将 .jarvis-* 添加到 .gitignore", OutputType.INFO)
77
- with open(gitignore_path, "a", encoding="utf-8") as f:
78
- # 确保文件以换行符结尾
79
- if not content.endswith("\n"):
80
- f.write("\n")
81
- f.write(f"{jarvis_ignore_pattern}\n")
82
- gitignore_modified = True
83
-
84
- # 4. 判断代码库是否是git仓库,如果不是,初始化git仓库
85
- if not os.path.exists(os.path.join(self.root_dir, ".git")):
86
- PrettyOutput.print("初始化 Git 仓库", OutputType.INFO)
87
- os.system("git init")
88
- os.system("git add .")
89
- os.system("git commit -m 'Initial commit'")
90
- # 5. 如果修改了 .gitignore,提交更改
91
- elif gitignore_modified:
92
- PrettyOutput.print("提交 .gitignore 更改", OutputType.INFO)
93
- os.system("git add .gitignore")
94
- os.system("git commit -m 'chore: update .gitignore to exclude .jarvis-* files'")
95
- # 6. 查看代码库是否有未提交的文件,如果有,提交一次
96
- elif self._has_uncommitted_files():
97
- PrettyOutput.print("提交未保存的更改", OutputType.INFO)
98
- os.system("git add .")
99
- git_diff = os.popen("git diff --cached").read()
100
- commit_message = generate_commit_message(git_diff)
101
- os.system(f"git commit -m '{commit_message}'")
33
+ self.root_dir = init_git_repo(self.root_dir)
102
34
 
103
35
  def _init_codebase(self):
104
- """初始化代码库"""
36
+ """Initialize codebase"""
105
37
  self._codebase = CodeBase(self.root_dir)
106
38
 
107
- def _has_uncommitted_files(self) -> bool:
108
- """判断代码库是否有未提交的文件"""
109
- # 获取未暂存的修改
110
- unstaged = os.popen("git diff --name-only").read()
111
- # 获取已暂存但未提交的修改
112
- staged = os.popen("git diff --cached --name-only").read()
113
- # 获取未跟踪的文件
114
- untracked = os.popen("git ls-files --others --exclude-standard").read()
115
-
116
- return bool(unstaged or staged or untracked)
117
-
118
- def _prepare_execution(self) -> None:
119
- """准备执行环境"""
120
- self._codebase.generate_codebase()
121
-
122
39
 
123
- def _load_related_files(self, feature: str) -> List[Dict]:
124
- """加载相关文件内容"""
40
+ def _load_related_files(self, feature: str) -> List[str]:
41
+ """Load related file content"""
125
42
  ret = []
126
- # 确保索引数据库已生成
43
+ # Ensure the index database is generated
127
44
  if not self._codebase.is_index_generated():
128
- PrettyOutput.print("检测到索引数据库未生成,正在生成...", OutputType.WARNING)
45
+ PrettyOutput.print("Index database not generated, generating...", OutputType.WARNING)
129
46
  self._codebase.generate_codebase()
130
47
 
131
48
  related_files = self._codebase.search_similar(feature)
132
- for file, score in related_files:
133
- PrettyOutput.print(f"相关文件: {file} 相关度: {score:.3f}", OutputType.SUCCESS)
134
- content = open(file, "r", encoding="utf-8").read()
135
- ret.append({"file_path": file, "file_content": content})
49
+ for file in related_files:
50
+ PrettyOutput.print(f"Related file: {file}", OutputType.SUCCESS)
51
+ ret.append(file)
136
52
  return ret
137
53
 
138
- def _parse_file_selection(self, input_str: str, max_index: int) -> List[int]:
139
- """解析文件选择表达式
140
-
141
- 支持的格式:
142
- - 单个数字: "1"
143
- - 逗号分隔: "1,3,5"
144
- - 范围: "1-5"
145
- - 组合: "1,3-5,7"
146
-
147
- Args:
148
- input_str: 用户输入的选择表达式
149
- max_index: 最大可选择的索引
150
-
151
- Returns:
152
- List[int]: 选中的索引列表(从0开始)
153
- """
154
- selected = set()
155
-
156
- # 移除所有空白字符
157
- input_str = "".join(input_str.split())
158
-
159
- # 处理逗号分隔的部分
160
- for part in input_str.split(","):
161
- if not part:
162
- continue
163
-
164
- # 处理范围(例如:3-6)
165
- if "-" in part:
166
- try:
167
- start, end = map(int, part.split("-"))
168
- # 转换为从0开始的索引
169
- start = max(0, start - 1)
170
- end = min(max_index, end - 1)
171
- if start <= end:
172
- selected.update(range(start, end + 1))
173
- except ValueError:
174
- PrettyOutput.print(f"忽略无效的范围表达式: {part}", OutputType.WARNING)
175
- # 处理单个数字
176
- else:
177
- try:
178
- index = int(part) - 1 # 转换为从0开始的索引
179
- if 0 <= index < max_index:
180
- selected.add(index)
181
- else:
182
- PrettyOutput.print(f"忽略超出范围的索引: {part}", OutputType.WARNING)
183
- except ValueError:
184
- PrettyOutput.print(f"忽略无效的数字: {part}", OutputType.WARNING)
185
-
186
- return sorted(list(selected))
187
-
188
- def _get_file_completer(self) -> Completer:
189
- """创建文件路径补全器"""
190
- class FileCompleter(Completer):
191
- def __init__(self, root_dir: str):
192
- self.root_dir = root_dir
193
-
194
- def get_completions(self, document, complete_event):
195
- # 获取当前输入的文本
196
- text = document.text_before_cursor
197
-
198
- # 如果输入为空,返回根目录下的所有文件
199
- if not text:
200
- for path in self._list_files(""):
201
- yield Completion(path, start_position=0)
202
- return
203
-
204
- # 获取当前目录和部分文件名
205
- current_dir = os.path.dirname(text)
206
- file_prefix = os.path.basename(text)
207
-
208
- # 列出匹配的文件
209
- search_dir = os.path.join(self.root_dir, current_dir) if current_dir else self.root_dir
210
- if os.path.isdir(search_dir):
211
- for path in self._list_files(current_dir):
212
- if path.startswith(text):
213
- yield Completion(path, start_position=-len(text))
214
-
215
- def _list_files(self, current_dir: str) -> List[str]:
216
- """列出指定目录下的所有文件(递归)"""
217
- files = []
218
- search_dir = os.path.join(self.root_dir, current_dir)
219
-
220
- for root, _, filenames in os.walk(search_dir):
221
- for filename in filenames:
222
- full_path = os.path.join(root, filename)
223
- rel_path = os.path.relpath(full_path, self.root_dir)
224
- # 忽略 .git 目录和其他隐藏文件
225
- if not any(part.startswith('.') for part in rel_path.split(os.sep)):
226
- files.append(rel_path)
227
-
228
- return sorted(files)
229
54
 
230
- return FileCompleter(self.root_dir)
231
-
232
- def _fuzzy_match_files(self, pattern: str) -> List[str]:
233
- """模糊匹配文件路径
234
-
235
- Args:
236
- pattern: 匹配模式
237
-
238
- Returns:
239
- List[str]: 匹配的文件路径列表
240
- """
241
- matches = []
242
-
243
- # 将模式转换为正则表达式
244
- pattern = pattern.replace('.', r'\.').replace('*', '.*').replace('?', '.')
245
- pattern = f".*{pattern}.*" # 允许部分匹配
246
- regex = re.compile(pattern, re.IGNORECASE)
247
-
248
- # 遍历所有文件
249
- for root, _, files in os.walk(self.root_dir):
250
- for file in files:
251
- full_path = os.path.join(root, file)
252
- rel_path = os.path.relpath(full_path, self.root_dir)
253
- # 忽略 .git 目录和其他隐藏文件
254
- if not any(part.startswith('.') for part in rel_path.split(os.sep)):
255
- if regex.match(rel_path):
256
- matches.append(rel_path)
257
-
258
- return sorted(matches)
259
-
260
- def _select_files(self, related_files: List[Dict]) -> List[Dict]:
261
- """让用户选择和补充相关文件"""
262
- PrettyOutput.section("相关文件", OutputType.INFO)
263
-
264
- # 显示找到的文件
265
- selected_files = list(related_files) # 默认全选
266
- for i, file in enumerate(related_files, 1):
267
- PrettyOutput.print(f"[{i}] {file['file_path']}", OutputType.INFO)
268
-
269
- # 询问用户是否需要调整
270
- user_input = input("\n是否需要调整文件列表?(y/n) [n]: ").strip().lower() or 'n'
271
- if user_input == 'y':
272
- # 让用户选择文件
273
- PrettyOutput.print("\n请输入要包含的文件编号(支持: 1,3-6 格式,直接回车保持当前选择):", OutputType.INFO)
274
- numbers = input(">>> ").strip()
275
- if numbers:
276
- selected_indices = self._parse_file_selection(numbers, len(related_files))
277
- if selected_indices:
278
- selected_files = [related_files[i] for i in selected_indices]
279
- else:
280
- PrettyOutput.print("未选择任何有效文件,保持原有选择", OutputType.WARNING)
281
-
282
- # 询问是否需要补充文件
283
- user_input = input("\n是否需要补充其他文件?(y/n) [n]: ").strip().lower() or 'n'
284
- if user_input == 'y':
285
- # 创建文件补全会话
286
- session = PromptSession(
287
- completer=self._get_file_completer(),
288
- complete_while_typing=True
289
- )
290
-
291
- while True:
292
- PrettyOutput.print("\n请输入要补充的文件路径(支持Tab补全和*?通配符,输入空行结束):", OutputType.INFO)
293
- try:
294
- file_path = session.prompt(">>> ").strip()
295
- except KeyboardInterrupt:
296
- break
297
-
298
- if not file_path:
299
- break
300
-
301
- # 处理通配符匹配
302
- if '*' in file_path or '?' in file_path:
303
- matches = self._fuzzy_match_files(file_path)
304
- if not matches:
305
- PrettyOutput.print("未找到匹配的文件", OutputType.WARNING)
306
- continue
307
-
308
- # 显示匹配的文件
309
- PrettyOutput.print("\n找到以下匹配的文件:", OutputType.INFO)
310
- for i, path in enumerate(matches, 1):
311
- PrettyOutput.print(f"[{i}] {path}", OutputType.INFO)
312
-
313
- # 让用户选择
314
- numbers = input("\n请选择要添加的文件编号(支持: 1,3-6 格式,直接回车全选): ").strip()
315
- if numbers:
316
- indices = self._parse_file_selection(numbers, len(matches))
317
- if not indices:
318
- continue
319
- paths_to_add = [matches[i] for i in indices]
320
- else:
321
- paths_to_add = matches
322
- else:
323
- paths_to_add = [file_path]
324
-
325
- # 添加选中的文件
326
- for path in paths_to_add:
327
- full_path = os.path.join(self.root_dir, path)
328
- if not os.path.isfile(full_path):
329
- PrettyOutput.print(f"文件不存在: {path}", OutputType.ERROR)
330
- continue
331
-
332
- try:
333
- with open(full_path, "r", encoding="utf-8") as f:
334
- content = f.read()
335
- selected_files.append({
336
- "file_path": path,
337
- "file_content": content
338
- })
339
- PrettyOutput.print(f"已添加文件: {path}", OutputType.SUCCESS)
340
- except Exception as e:
341
- PrettyOutput.print(f"读取文件失败: {str(e)}", OutputType.ERROR)
342
-
343
- return selected_files
344
-
345
- def _finalize_changes(self, feature: str) -> None:
346
- """完成修改并提交"""
347
- PrettyOutput.print("修改确认成功,提交修改", OutputType.INFO)
348
-
349
- # 只添加已经在 git 控制下的修改文件
350
- os.system("git add -u")
351
-
352
- # 然后获取 git diff
353
- git_diff = os.popen("git diff --cached").read()
354
-
355
- # 自动生成commit信息,传入feature
356
- commit_message = generate_commit_message(git_diff)
357
-
358
- # 显示并确认commit信息
359
- PrettyOutput.print(f"自动生成的commit信息: {commit_message}", OutputType.INFO)
360
- user_confirm = input("是否使用该commit信息?(y/n) [y]: ") or "y"
361
-
362
- if user_confirm.lower() != "y":
363
- commit_message = input("请输入新的commit信息: ")
364
-
365
- # 不需要再次 git add,因为已经添加过了
366
- os.system(f"git commit -m '{commit_message}'")
367
- save_edit_record(self.record_dir, commit_message, git_diff)
368
-
369
- def _revert_changes(self) -> None:
370
- """回退所有修改"""
371
- PrettyOutput.print("修改已取消,回退更改", OutputType.INFO)
372
- os.system(f"git reset --hard")
373
- os.system(f"git clean -df")
374
-
375
- def get_key_code(self, files: List[Dict], feature: str):
376
- """提取文件中与需求相关的关键代码片段"""
377
- for file_info in files:
378
- PrettyOutput.print(f"分析文件: {file_info['file_path']}", OutputType.INFO)
379
- model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
380
- model.set_suppress_output(True)
381
- file_path = file_info["file_path"]
382
- content = file_info["file_content"]
383
-
384
- try:
385
- prompt = f"""You are a code analysis expert who can extract relevant snippets from code.
386
- Please return in the following format:
387
- <PART>
388
- content
389
- </PART>
390
-
391
- Multiple snippets can be returned. If the file content is not relevant to the requirement, return empty.
392
-
393
- Requirement: {feature}
394
- File path: {file_path}
395
- Code content:
396
- {content}
397
- """
398
-
399
- # 调用大模型进行分析
400
- response = model.chat_until_success(prompt)
401
-
402
- parts = re.findall(r'<PART>\n(.*?)\n</PART>', response, re.DOTALL)
403
- file_info["parts"] = parts
404
- except Exception as e:
405
- PrettyOutput.print(f"分析文件失败: {str(e)}", OutputType.ERROR)
406
55
 
407
56
  def execute(self, feature: str) -> Dict[str, Any]:
408
- """执行代码修改"""
57
+ """Execute code modification"""
409
58
  try:
410
- self._prepare_execution()
411
-
412
- # 获取并选择相关文件
59
+ # Get and select related files
413
60
  initial_files = self._load_related_files(feature)
414
- selected_files = self._select_files(initial_files)
61
+ selected_files = select_files(initial_files, self.root_dir)
415
62
 
416
- # 是否是长上下文
417
- if is_long_context([file['file_path'] for file in selected_files]):
418
- self.get_key_code(selected_files, feature)
419
- else:
420
- for file in selected_files:
421
- file["parts"] = [file["file_content"]]
422
-
423
- # 获取修改方案
424
- raw_plan, structed_plan = PlanGenerator().generate_plan(feature, selected_files)
425
- if not raw_plan or not structed_plan:
63
+ # Get modification plan
64
+ structed_plan = PlanGenerator().generate_plan(feature, selected_files)
65
+ if not structed_plan:
426
66
  return {
427
67
  "success": False,
428
68
  "stdout": "",
429
- "stderr": "修改计划生成失败,请修改需求后重试",
69
+ "stderr": "Failed to generate modification plan, please modify the requirement and try again",
430
70
  }
431
71
 
432
- # 执行修改
433
- if PatchHandler().handle_patch_application(feature ,raw_plan, structed_plan):
434
- self._finalize_changes(feature)
72
+ # Execute modification
73
+ if PatchHandler().handle_patch_application(feature, structed_plan):
435
74
  return {
436
75
  "success": True,
437
- "stdout": "代码修改成功",
76
+ "stdout": "Code modification successful",
438
77
  "stderr": "",
439
78
  }
440
79
  else:
441
- self._revert_changes()
442
80
  return {
443
81
  "success": False,
444
82
  "stdout": "",
445
- "stderr": "代码修改失败,请修改需求后重试",
83
+ "stderr": "Code modification failed, please modify the requirement and try again",
446
84
  }
447
85
 
448
86
  except Exception as e:
449
- self._revert_changes()
450
87
  return {
451
88
  "success": False,
452
89
  "stdout": "",
453
- "stderr": f"执行失败: {str(e)},请修改需求后重试",
90
+ "stderr": f"Execution failed: {str(e)}, please modify the requirement and try again",
454
91
  "error": e
455
92
  }
456
93
 
457
94
  def main():
458
- """命令行入口"""
95
+ """Command line entry"""
459
96
  import argparse
460
97
 
461
98
  load_env_from_file()
462
99
 
463
- parser = argparse.ArgumentParser(description='代码修改工具')
464
- parser.add_argument('-d', '--dir', help='项目根目录', default=os.getcwd())
465
- parser.add_argument('-l', '--language', help='编程语言', default="python")
100
+ parser = argparse.ArgumentParser(description='Code modification tool')
101
+ parser.add_argument('-d', '--dir', help='Project root directory', default=os.getcwd())
102
+ parser.add_argument('-l', '--language', help='Programming language', default="python")
466
103
  args = parser.parse_args()
467
104
 
468
105
  tool = JarvisCoder(args.dir, args.language)
469
106
 
470
- # 循环处理需求
107
+ # Loop through requirements
471
108
  while True:
472
109
  try:
473
- # 获取需求,传入项目根目录
474
- feature = get_multiline_input("请输入开发需求 (输入空行退出):", tool.root_dir)
110
+ # Get requirements, pass in project root directory
111
+ feature = get_multiline_input("Please enter the development requirements (input empty line to exit):", tool.root_dir)
475
112
 
476
113
  if not feature or feature == "__interrupt__":
477
114
  break
478
115
 
479
- # 执行修改
116
+ # Execute modification
480
117
  result = tool.execute(feature)
481
118
 
482
- # 显示结果
119
+ # Display results
483
120
  if result["success"]:
484
121
  PrettyOutput.print(result["stdout"], OutputType.SUCCESS)
485
122
  else:
486
123
  if result.get("stderr"):
487
124
  PrettyOutput.print(result["stderr"], OutputType.WARNING)
488
- if result.get("error"): # 使用 get() 方法避免 KeyError
125
+ if result.get("error"): # Use get() method to avoid KeyError
489
126
  error = result["error"]
490
- PrettyOutput.print(f"错误类型: {type(error).__name__}", OutputType.WARNING)
491
- PrettyOutput.print(f"错误信息: {str(error)}", OutputType.WARNING)
492
- # 提示用户可以继续输入
493
- PrettyOutput.print("\n您可以修改需求后重试", OutputType.INFO)
127
+ PrettyOutput.print(f"Error type: {type(error).__name__}", OutputType.WARNING)
128
+ PrettyOutput.print(f"Error information: {str(error)}", OutputType.WARNING)
129
+ # Prompt user to continue input
130
+ PrettyOutput.print("\nYou can modify the requirements and try again", OutputType.INFO)
494
131
 
495
132
  except KeyboardInterrupt:
496
- print("\n用户中断执行")
133
+ print("\nUser interrupted execution")
497
134
  break
498
135
  except Exception as e:
499
- PrettyOutput.print(f"执行出错: {str(e)}", OutputType.ERROR)
500
- PrettyOutput.print("\n您可以修改需求后重试", OutputType.INFO)
136
+ PrettyOutput.print(f"Execution failed: {str(e)}", OutputType.ERROR)
137
+ PrettyOutput.print("\nYou can modify the requirements and try again", OutputType.INFO)
501
138
  continue
502
139
 
503
140
  return 0
@@ -506,93 +143,78 @@ if __name__ == "__main__":
506
143
  exit(main())
507
144
 
508
145
  class FilePathCompleter(Completer):
509
- """文件路径自动完成器"""
146
+ """File path auto-completer"""
510
147
 
511
148
  def __init__(self, root_dir: str):
512
149
  self.root_dir = root_dir
513
150
  self._file_list = None
514
151
 
515
152
  def _get_files(self) -> List[str]:
516
- """获取git管理的文件列表"""
153
+ """Get the list of files managed by git"""
517
154
  if self._file_list is None:
518
155
  try:
519
- # 切换到项目根目录
156
+ # Switch to project root directory
520
157
  old_cwd = os.getcwd()
521
158
  os.chdir(self.root_dir)
522
159
 
523
- # 获取git管理的文件列表
160
+ # Get the list of files managed by git
524
161
  self._file_list = os.popen("git ls-files").read().splitlines()
525
162
 
526
- # 恢复工作目录
163
+ # Restore working directory
527
164
  os.chdir(old_cwd)
528
165
  except Exception as e:
529
- PrettyOutput.print(f"获取文件列表失败: {str(e)}", OutputType.WARNING)
166
+ PrettyOutput.print(f"Failed to get file list: {str(e)}", OutputType.WARNING)
530
167
  self._file_list = []
531
168
  return self._file_list
532
169
 
533
170
  def get_completions(self, document, complete_event):
534
- """获取补全建议"""
171
+ """Get completion suggestions"""
535
172
  text_before_cursor = document.text_before_cursor
536
173
 
537
- # 检查是否刚输入了@
174
+ # Check if @ was just entered
538
175
  if text_before_cursor.endswith('@'):
539
- # 显示所有文件
176
+ # Display all files
540
177
  for path in self._get_files():
541
178
  yield Completion(path, start_position=0)
542
179
  return
543
180
 
544
- # 检查之前是否有@,并获取@后的搜索词
181
+ # Check if there was an @ before, and get the search word after @
545
182
  at_pos = text_before_cursor.rfind('@')
546
183
  if at_pos == -1:
547
184
  return
548
185
 
549
186
  search = text_before_cursor[at_pos + 1:].lower().strip()
550
187
 
551
- # 提供匹配的文件建议
188
+ # Provide matching file suggestions
552
189
  for path in self._get_files():
553
190
  path_lower = path.lower()
554
- if (search in path_lower or # 直接包含
555
- search in os.path.basename(path_lower) or # 文件名包含
556
- any(fnmatch.fnmatch(path_lower, f'*{s}*') for s in search.split())): # 通配符匹配
557
- # 计算正确的start_position
191
+ if (search in path_lower or # Directly included
192
+ search in os.path.basename(path_lower) or # File name included
193
+ any(fnmatch.fnmatch(path_lower, f'*{s}*') for s in search.split())): # Wildcard matching
194
+ # Calculate the correct start_position
558
195
  yield Completion(path, start_position=-(len(search)))
559
196
 
560
- class SmartCompleter(Completer):
561
- """智能自动完成器,组合词语和文件路径补全"""
562
-
563
- def __init__(self, word_completer: WordCompleter, file_completer: FilePathCompleter):
564
- self.word_completer = word_completer
565
- self.file_completer = file_completer
566
-
567
- def get_completions(self, document, complete_event):
568
- """获取补全建议"""
569
- # 如果当前行以@结尾,使用文件补全
570
- if document.text_before_cursor.strip().endswith('@'):
571
- yield from self.file_completer.get_completions(document, complete_event)
572
- else:
573
- # 否则使用词语补全
574
- yield from self.word_completer.get_completions(document, complete_event)
575
197
 
576
198
  def get_multiline_input(prompt_text: str, root_dir: Optional[str] = ".") -> str:
577
- """获取多行输入,支持文件路径自动完成功能
199
+ """Get multi-line input, support file path auto-completion function
578
200
 
579
201
  Args:
580
- prompt_text: 提示文本
581
- root_dir: 项目根目录,用于文件补全
202
+ prompt_text: Prompt text
203
+ root_dir: Project root directory, for file completion
582
204
 
583
205
  Returns:
584
- str: 用户输入的文本
206
+ str: User input text
585
207
  """
586
- # 创建文件补全器
208
+ # Create file completion
587
209
  file_completer = FilePathCompleter(root_dir or os.getcwd())
588
210
 
589
- # 创建提示样式
211
+ # Create prompt style
590
212
  style = Style.from_dict({
591
213
  'prompt': 'ansicyan bold',
592
214
  'input': 'ansiwhite',
593
215
  })
594
216
 
595
- # 创建会话
217
+ # Create session
596
218
  session = PromptSession(
597
219
  completer=file_completer,
598
220
  style=style,
@@ -601,20 +223,20 @@ def get_multiline_input(prompt_text: str, root_dir: Optional[str] = ".") -> str:
601
223
  complete_while_typing=True
602
224
  )
603
225
 
604
- # 显示初始提示文本
226
+ # Display initial prompt text
605
227
  print(f"\n{prompt_text}")
606
228
 
607
- # 创建提示符
229
+ # Create prompt
608
230
  prompt = FormattedText([
609
231
  ('class:prompt', ">>> ")
610
232
  ])
611
233
 
612
- # 获取输入
234
+ # Get input
613
235
  lines = []
614
236
  try:
615
237
  while True:
616
238
  line = session.prompt(prompt).strip()
617
- if not line: # 空行表示输入结束
239
+ if not line: # Empty line means input end
618
240
  break
619
241
  lines.append(line)
620
242
  except KeyboardInterrupt: