jarvis-ai-assistant 0.1.96__py3-none-any.whl → 0.1.97__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 +138 -144
  3. jarvis/jarvis_codebase/main.py +87 -54
  4. jarvis/jarvis_coder/git_utils.py +4 -7
  5. jarvis/jarvis_coder/main.py +17 -22
  6. jarvis/jarvis_coder/patch_handler.py +141 -441
  7. jarvis/jarvis_coder/plan_generator.py +64 -36
  8. jarvis/jarvis_platform/main.py +1 -1
  9. jarvis/jarvis_rag/main.py +1 -1
  10. jarvis/jarvis_smart_shell/main.py +15 -15
  11. jarvis/main.py +24 -24
  12. jarvis/models/ai8.py +22 -22
  13. jarvis/models/base.py +17 -13
  14. jarvis/models/kimi.py +31 -31
  15. jarvis/models/ollama.py +28 -28
  16. jarvis/models/openai.py +22 -24
  17. jarvis/models/oyi.py +25 -25
  18. jarvis/models/registry.py +33 -34
  19. jarvis/tools/ask_user.py +5 -5
  20. jarvis/tools/base.py +2 -2
  21. jarvis/tools/chdir.py +9 -9
  22. jarvis/tools/codebase_qa.py +4 -4
  23. jarvis/tools/coder.py +4 -4
  24. jarvis/tools/file_ops.py +1 -1
  25. jarvis/tools/generator.py +23 -23
  26. jarvis/tools/methodology.py +4 -4
  27. jarvis/tools/rag.py +4 -4
  28. jarvis/tools/registry.py +38 -38
  29. jarvis/tools/search.py +42 -42
  30. jarvis/tools/shell.py +13 -13
  31. jarvis/tools/sub_agent.py +16 -16
  32. jarvis/tools/thinker.py +41 -41
  33. jarvis/tools/webpage.py +17 -17
  34. jarvis/utils.py +59 -60
  35. {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.97.dist-info}/METADATA +1 -1
  36. jarvis_ai_assistant-0.1.97.dist-info/RECORD +47 -0
  37. jarvis_ai_assistant-0.1.96.dist-info/RECORD +0 -47
  38. {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.97.dist-info}/LICENSE +0 -0
  39. {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.97.dist-info}/WHEEL +0 -0
  40. {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.97.dist-info}/entry_points.txt +0 -0
  41. {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.97.dist-info}/top_level.txt +0 -0
@@ -6,26 +6,15 @@ from jarvis.models.base import BasePlatform
6
6
  from jarvis.models.registry import PlatformRegistry
7
7
  from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, while_success
8
8
 
9
+ class Patch:
10
+ def __init__(self, old_code: str, new_code: str):
11
+ self.old_code = old_code
12
+ self.new_code = new_code
13
+
9
14
  class PatchHandler:
10
- def __init__(self):
11
- self.code_part_model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
12
- self.code_part_model.set_system_message("""你是一个资深程序开发专家,你可以根据代码文件片段,需求和修改计划生成修改代码,供程序自动应用。生成代码的格式如下,可以生成多个修改片段:
13
- <CODE>
14
- > path/to/file
15
- code
16
- </CODE>
17
-
18
- 如:
19
- <CODE>
20
- > /src/a.cpp
21
- // add function
22
- // existing code
23
- int add(int a, int b) {
24
- return a + b;
25
- }
26
- </CODE>""")
27
15
 
28
- def _extract_patches(self, response: str) -> List[Tuple[str, str, str]]:
16
+
17
+ def _extract_patches(self, response: str) -> List[Patch]:
29
18
  """从响应中提取补丁
30
19
 
31
20
  Args:
@@ -34,414 +23,144 @@ int add(int a, int b) {
34
23
  Returns:
35
24
  List[Tuple[str, str, str]]: 补丁列表,每个补丁是 (格式, 文件路径, 补丁内容) 的元组
36
25
  """
37
- patches = []
38
-
39
26
  # 修改后的正则表达式匹配三种补丁格式
40
- fmt_pattern = r'<PATCH_FMT(\d?)>\n?(.*?)\n?</PATCH_FMT\d?>\n?'
41
-
42
- for match in re.finditer(fmt_pattern, response, re.DOTALL):
43
- fmt_type = match.group(1) or "1" # 默认FMT1
44
- patch_content = match.group(2)
45
-
46
- # 提取文件路径和内容
47
- lines = patch_content.split('\n')
48
- if not lines:
49
- continue
50
-
51
- file_path_match = re.search(r'>\s*(.*)', lines[0])
52
- if not file_path_match:
53
- continue
54
-
55
- file_path = file_path_match.group(1)
56
-
57
- # 处理不同格式
58
- if fmt_type == "3":
59
- # FMT3格式:文件删除
60
- if len(lines) < 2 or "CONFIRM_DELETE" not in lines[1]:
61
- continue
62
- patches.append(("FMT3", file_path, "CONFIRM_DELETE"))
63
- elif fmt_type == "1":
64
- # FMT1格式:新旧内容分隔
65
- parts = '\n'.join(lines[1:]).split('@@@@@@')
66
- if len(parts) != 2:
67
- continue
68
- old_content = parts[0]
69
- new_content = parts[1]
70
- patches.append(("FMT1", file_path, f"{old_content}\n@@@@@@\n{new_content}"))
71
- elif fmt_type == "2":
72
- # FMT2格式:全文件替换
73
- if not lines[1:]: # 新增内容校验
74
- continue
75
- full_content = '\n'.join(lines[1:])
76
- patches.append(("FMT2", file_path, full_content))
27
+ fmt_pattern = r'<PATCH>\n>>>>>> SEARCH\n(.*?)\n?(={5,})\n(.*?)\n?<<<<<< REPLACE\n</PATCH>'
28
+ ret = []
29
+ for m in re.finditer(fmt_pattern, response, re.DOTALL):
30
+ ret.append(Patch(m.group(1), m.group(3)))
31
+ return ret
77
32
 
78
- return patches
79
-
80
- def _extract_code(self, response: str) -> Dict[str, List[str]]:
81
- """从响应中提取代码
82
-
83
- Args:
84
- response: 模型响应内容
85
-
86
- Returns:
87
- Dict[str, List[str]]: 代码字典,key为文件路径,value为代码片段列表
88
- """
89
- code_dict = {}
90
- for match in re.finditer(r'<CODE>\n> (.+?)\n(.*)\n</CODE>', response, re.DOTALL):
91
- file_path = match.group(1)
92
- code_list = match.group(2)
93
- if file_path not in code_dict:
94
- code_dict[file_path] = []
95
- code_dict[file_path].append(code_list)
96
- return code_dict
97
-
98
- def make_code_raw_patch(self, related_files: List[Dict], modification_plan: str) -> Dict[str, List[str]]:
99
- """生成修改方案"""
100
- prompt = f"# 修改方案:\n{modification_plan}\n# 相关文件:"
101
- # 添加文件内容到提示
102
- for i, file in enumerate(related_files):
103
- if file["parts"]:
104
- prompt += f"""\n{i}. {file["file_path"]}\n"""
105
- prompt += f"""文件内容片段:\n"""
106
- for i, p in enumerate(file["parts"]):
107
- prompt += f"<PART{i}>\n"
108
- prompt += f'{p}\n'
109
- prompt += f"</PART{i}>\n"
110
33
 
111
- # 调用模型生成代码片段
112
- response = while_success(lambda: self.code_part_model.chat(prompt), 5)
113
- return self._extract_code(response)
114
-
115
- def make_file_formatted_patch(self, file: str, plan: str, code_list: List[str], model: BasePlatform) -> List[Tuple[str, str, str]]:
116
- """生成文件补丁"""
117
- if os.path.exists(file):
118
- content = open(file, "r", encoding="utf-8").read()
119
- else:
120
- content = "<文件不存在,需要创建>"
121
- prompt = f"""\n# 完整修改方案:{plan}\n# 当前修改文件路径:\n{file}\n# 当前修改文件内容:\n<CONTENT>{content}\n</CONTENT>\n# 生成的补丁:"""
122
- for code in code_list:
123
- prompt += f"\n<CODE>\n{code}\n</CODE>"
124
- PrettyOutput.print(f"为{file}生成格式化补丁...", OutputType.PROGRESS)
125
- response = while_success(lambda: model.chat(prompt), 5)
126
- return self._extract_patches(response)
127
-
128
- def _handle_fmt3_delete(self, patch_file_path: str, patch_content: str, temp_map: dict, modified_files: set) -> Tuple[bool, str]:
129
- """处理FMT3文件删除补丁"""
130
- if not os.path.exists(patch_file_path):
131
- return False, f"文件不存在无法删除: {patch_file_path}"
132
-
133
- # 安全检查
134
- if patch_content != "CONFIRM_DELETE":
135
- return False, f"删除确认标记缺失: {patch_file_path}"
136
-
137
- # 执行删除
138
- os.remove(patch_file_path)
139
- os.system(f"git rm {patch_file_path}")
140
- PrettyOutput.print(f"成功删除文件: {patch_file_path}", OutputType.SUCCESS)
141
- if patch_file_path in temp_map:
142
- del temp_map[patch_file_path]
143
- modified_files.add(patch_file_path)
144
- return True, ""
145
-
146
- def _handle_fmt2_full_replace(self, patch_file_path: str, patch_content: str,
147
- temp_map: dict, modified_files: set) -> Tuple[bool, str]:
148
- """处理FMT2全文件替换补丁"""
149
- if not os.path.isabs(patch_file_path):
150
- patch_file_path = os.path.abspath(patch_file_path)
151
-
152
- if patch_file_path not in temp_map:
153
- # 新建文件
154
- try:
155
- os.makedirs(os.path.dirname(patch_file_path), exist_ok=True)
156
- with open(patch_file_path, "w", encoding="utf-8") as f:
157
- f.write(patch_content)
158
- temp_map[patch_file_path] = patch_content
159
- modified_files.add(patch_file_path)
160
- return True, ""
161
- except Exception as e:
162
- return False, f"创建新文件失败 {patch_file_path}: {str(e)}"
163
-
164
- # 替换现有文件
165
- temp_map[patch_file_path] = patch_content
166
- modified_files.add(patch_file_path)
167
- return True, ""
168
-
169
- def _handle_fmt1_diff(self, patch_file_path: str, old_content: str, new_content: str,
170
- temp_map: dict, modified_files: set) -> Tuple[bool, str]:
171
- """处理FMT1差异补丁"""
172
- if patch_file_path not in temp_map and not old_content:
173
- # 处理新文件创建
174
- try:
175
- dir_path = os.path.dirname(patch_file_path)
176
- if dir_path and not os.path.exists(dir_path):
177
- os.makedirs(dir_path, exist_ok=True)
178
- with open(patch_file_path, "w", encoding="utf-8") as f:
179
- f.write(new_content)
180
- os.system(f"git add {patch_file_path}")
181
- PrettyOutput.print(f"成功创建并添加文件: {patch_file_path}", OutputType.SUCCESS)
182
- modified_files.add(patch_file_path)
183
- temp_map[patch_file_path] = new_content
184
- return True, ""
185
- except Exception as e:
186
- return False, f"创建新文件失败 {patch_file_path}: {str(e)}"
187
-
188
- if patch_file_path not in temp_map:
189
- return False, f"文件不存在: {patch_file_path}:{temp_map.keys()}"
190
-
191
- current_content = temp_map[patch_file_path]
192
- if old_content and old_content not in current_content:
193
- return False, (
194
- f"补丁应用失败: {patch_file_path}\n"
195
- f"原因: 未找到要替换的代码\n"
196
- f"期望找到的代码:\n{old_content}\n"
197
- f"实际文件内容:\n{current_content[:200]}..."
198
- )
199
-
200
- temp_map[patch_file_path] = current_content.replace(old_content, new_content)
201
- modified_files.add(patch_file_path)
202
- return True, ""
203
-
204
- def _apply_single_patch(self, fmt: str, patch_file_path: str, patch_content: str,
205
- temp_map: dict, modified_files: set) -> Tuple[bool, str]:
206
- """应用单个补丁"""
207
- try:
208
- if fmt == "FMT3":
209
- return self._handle_fmt3_delete(patch_file_path, patch_content, temp_map, modified_files)
210
-
211
- elif fmt == "FMT2":
212
- return self._handle_fmt2_full_replace(patch_file_path, patch_content, temp_map, modified_files)
213
-
214
- elif fmt == "FMT1":
215
- parts = patch_content.split("@@@@@@")
216
- if len(parts) != 2:
217
- return False, f"补丁格式错误: {patch_file_path},缺少分隔符"
218
- old_content = parts[0]
219
- new_content = parts[1]
220
- return self._handle_fmt1_diff(patch_file_path, old_content, new_content, temp_map, modified_files)
221
-
222
- return False, f"未知的补丁格式: {fmt}"
223
- except Exception as e:
224
- return False, f"处理补丁时发生错误: {str(e)}"
225
-
226
- def _confirm_and_apply_changes(self, file_path: str, temp_map: dict, modified_files: set) -> bool:
34
+ def _confirm_and_apply_changes(self, file_path: str) -> bool:
227
35
  """确认并应用修改"""
228
- os.system(f"git diff {file_path}")
36
+ os.system(f"git diff --cached {file_path}")
229
37
  confirm = input(f"\n是否接受 {file_path} 的修改?(y/n) [y]: ").lower() or "y"
230
38
  if confirm == "y":
231
- # 写入实际文件
232
- try:
233
- dir_path = os.path.dirname(file_path)
234
- if dir_path and not os.path.exists(dir_path):
235
- os.makedirs(dir_path, exist_ok=True)
236
- with open(file_path, "w", encoding="utf-8") as f:
237
- f.write(temp_map[file_path])
238
- PrettyOutput.print(f"成功修改文件: {file_path}", OutputType.SUCCESS)
239
- return True
240
- except Exception as e:
241
- PrettyOutput.print(f"写入文件失败 {file_path}: {str(e)}", OutputType.WARNING)
242
- return False
39
+ return True
243
40
  else:
244
41
  # 回退修改
42
+ os.system(f"git reset {file_path}")
245
43
  os.system(f"git checkout -- {file_path}")
246
44
  PrettyOutput.print(f"已回退 {file_path} 的修改", OutputType.WARNING)
247
- modified_files.discard(file_path)
248
- if file_path in temp_map:
249
- del temp_map[file_path]
250
45
  return False
251
46
 
252
- def apply_patch(self, related_files: List[Dict], plan: str, patches_code: Dict[str, List[str]]) -> Tuple[bool, str]:
47
+
48
+ def apply_file_patch(self, file_path: str, patches: List[Patch]) -> bool:
49
+ """应用文件补丁"""
50
+ if not os.path.exists(file_path):
51
+ base_dir = os.path.dirname(file_path)
52
+ os.makedirs(base_dir, exist_ok=True)
53
+ open(file_path, "w", encoding="utf-8").close()
54
+ file_content = open(file_path, "r", encoding="utf-8").read()
55
+ for i, patch in enumerate(patches):
56
+ if patch.old_code == "" and patch.new_code == "":
57
+ PrettyOutput.print(f"应用第 {i+1}/{len(patches)} 个补丁:删除文件 {file_path}", OutputType.INFO)
58
+ file_content = ""
59
+ os.system(f"git rm {file_path}")
60
+ PrettyOutput.print(f"应用第 {i+1}/{len(patches)} 个补丁成功", OutputType.SUCCESS)
61
+ elif patch.old_code == "":
62
+ PrettyOutput.print(f"应用第 {i+1}/{len(patches)} 个补丁:替换文件 {file_path} 内容:\n{patch.new_code}", OutputType.INFO)
63
+ file_content = patch.new_code
64
+ open(file_path, "w", encoding="utf-8").write(patch.new_code)
65
+ os.system(f"git add {file_path}")
66
+ PrettyOutput.print(f"应用第 {i+1}/{len(patches)} 个补丁成功", OutputType.SUCCESS)
67
+ else:
68
+ PrettyOutput.print(f"应用第 {i+1}/{len(patches)} 个补丁:文件原始内容:\n{patch.old_code}\n替换为:\n{patch.new_code}", OutputType.INFO)
69
+ if file_content.find(patch.old_code) == -1:
70
+ PrettyOutput.print(f"文件 {file_path} 中不存在 {patch.old_code}", OutputType.WARNING)
71
+ os.system(f"git reset {file_path}")
72
+ os.system(f"git checkout -- {file_path}")
73
+ return False
74
+ else:
75
+ file_content = file_content.replace(patch.old_code, patch.new_code, 1)
76
+ open(file_path, "w", encoding="utf-8").write(file_content)
77
+ os.system(f"git add {file_path}")
78
+ PrettyOutput.print(f"应用第 {i+1}/{len(patches)} 个补丁成功", OutputType.SUCCESS)
79
+ return True
80
+
81
+
82
+ def retry_comfirm(self) -> Tuple[str, str]:# 恢复用户选择逻辑
83
+ choice = input("\n请选择操作: (1) 重试 (2) 跳过 (3) 完全中止 [1]: ") or "1"
84
+ if choice == "2":
85
+ return "skip", ""
86
+ if choice == "3":
87
+ return "break", ""
88
+ return "continue", get_multiline_input("请输入补充说明和要求:")
89
+
90
+ def apply_patch(self, feature: str, raw_plan: str, structed_plan: Dict[str, str]) -> Tuple[bool, str]:
253
91
  """应用补丁(主入口)"""
254
- error_info = []
255
- modified_files = set()
256
- file_map = {file["file_path"]: file["file_content"] for file in related_files}
257
- temp_map = file_map.copy()
258
-
259
- for file_path, code_list in patches_code.items():
260
- retry_count = 0
261
- original_code_list = code_list.copy()
262
- additional_info = ""
263
- error_details = ""
264
- model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
265
- model.set_system_message("""你是一个资深程序开发专家,你可以根据修改方案,要修改的代码文件,要修改代码的代码片段,生成给出代码片段的规范的补丁片段供程序自动应用。
266
- 补丁片段格式说明:
267
- 可选三种格式:
268
- 1. 差异模式(适合局部修改):
269
- <PATCH_FMT1>
270
- > path/to/file
271
- old_content
272
- @@@@@@
273
- new_content
274
- </PATCH_FMT1>
275
-
276
- 注意事项:
277
- a、仅输出补丁内容,不要输出任何其他内容
278
- b、如果在大段代码中有零星修改,生成多个补丁
279
- c、要替换的内容,一定要与文件内容完全一致(**包括缩进与空白**),不要有任何多余或者缺失的内容
280
- d、务必保留原始文件的缩进和格式
281
- e、给出的代码是修改的一部分,不用关注除本文件以外的修改
282
-
283
- 2. 全文件模式(适合新建或完全重写):
284
- <PATCH_FMT2>
285
- > path/to/file
286
- def new_function():
287
- print("new code")
288
- </PATCH_FMT2>
289
-
290
- 注意事项:
291
- a、仅输出补丁内容,不要输出任何其他内容
292
- b、给出的代码是修改的一部分,不用关注除本文件以外的修改
293
-
294
- 3. 删除文件模式:
295
- <PATCH_FMT3>
296
- > path/to/file_to_delete
297
- CONFIRM_DELETE
298
- </PATCH_FMT3>
299
-
300
- 注意事项:
301
- 1. 必须包含CONFIRM_DELETE确认标记
302
- """)
303
-
304
-
92
+ for file_path, current_plan in structed_plan.items():
93
+ additional_info = ""
305
94
  while True:
306
- try:
307
- if retry_count == 0: # 首次调用生成格式化补丁
308
- patches = self.make_file_formatted_patch(
309
- file_path, plan, original_code_list, model
310
- )
311
- else: # 重试时直接生成新补丁
312
- # 构建重试提示
313
- original_code_str = '\n'.join(original_code_list)
314
- retry_prompt = f"""根据以下信息重新生成补丁:
315
- 错误信息:{error_details}
316
- 用户补充:{additional_info}
317
- 原始代码片段:
318
- {original_code_str}
319
- 请生成新的补丁,特别注意代码匹配准确性"""
320
-
321
- response = while_success(lambda: model.chat(retry_prompt), 5)
322
- patches = self._extract_patches(response)
323
-
324
- except Exception as e:
325
- error_info.append(f"生成补丁失败: {str(e)}")
326
- return False, "\n".join(error_info)
327
-
328
- # 处理当前文件的所有补丁
329
- file_success = True
330
- current_errors = []
331
- for fmt, patch_file_path, patch_content in patches:
332
- success, msg = self._apply_single_patch(fmt, patch_file_path, patch_content, temp_map, modified_files)
333
- if not success:
334
- current_errors.append(msg)
335
- file_success = False
336
95
 
337
- if not file_success or not self._confirm_and_apply_changes(file_path, temp_map, modified_files):
338
- # 显示错误信息并询问用户操作
339
- PrettyOutput.print(f"\n文件 {file_path} 补丁应用失败:", OutputType.WARNING)
340
- PrettyOutput.print("\n".join(current_errors), OutputType.WARNING)
341
-
342
- # 恢复用户选择逻辑
343
- choice = input("\n请选择操作: (1) 重试 (2) 补充信息后重试 (3) 跳过 (4) 完全中止 [1]: ") or "1"
344
-
345
- if choice == "3":
96
+ if os.path.exists(file_path):
97
+ content = open(file_path, "r", encoding="utf-8").read()
98
+ else:
99
+ content = "<文件不存在,需要创建>"
100
+ prompt = """You are a senior software development expert who can generate code patches based on the complete modification plan, current original code file path, code content, and current file's modification plan. The output format should be as follows:
101
+ <PATCH>
102
+ >>>>>> SEARCH
103
+ old_code
104
+ ======
105
+ new_code
106
+ <<<<<< REPLACE
107
+ </PATCH>
108
+ Rules:
109
+ 1. When old_code is empty, it means replace everything from start to end
110
+ 2. When new_code is empty, it means delete old_code
111
+ 3. When both old_code and new_code are empty, it means delete the file
112
+ Notes:
113
+ 1. Multiple patches can be generated
114
+ 2. old_code will be replaced with new_code, pay attention to context continuity
115
+ 3. Avoid breaking existing code logic when generating patches, e.g., don't insert function definitions inside existing function bodies
116
+ 4. Include sufficient context to avoid ambiguity
117
+ 5. Patches will be merged using file_content.replace(patch.old_code, patch.new_code, 1), so old_code and new_code need to match exactly, including empty lines, line breaks, whitespace, tabs, and comments
118
+ 6. Ensure generated code has correct format (syntax, indentation, line breaks)
119
+ 7. Ensure new_code's indentation and format matches old_code
120
+ 8. Ensure code is inserted in appropriate locations, e.g., code using variables should be after declarations/definitions
121
+ 9. Provide at least 3 lines of context before and after modified code for location
122
+
123
+
124
+ """
125
+ prompt += f"""# Original requirement: {feature}
126
+ # Complete modification plan: {raw_plan}
127
+ # Current file path: {file_path}
128
+ # Current file content:
129
+ <CONTENT>
130
+ {content}
131
+ </CONTENT>
132
+ # Current file modification plan:
133
+ {current_plan}
134
+ { "# Additional information: " + additional_info if additional_info else "" }
135
+ """
136
+
137
+
138
+ PrettyOutput.print(f"为{file_path}生成格式化补丁...", OutputType.PROGRESS)
139
+ response = PlatformRegistry.get_global_platform_registry().get_codegen_platform().chat_until_success(prompt)
140
+ patches = self._extract_patches(response)
141
+
142
+ if not patches or not self.apply_file_patch(file_path, patches) or not self._confirm_and_apply_changes(file_path):
143
+ os.system(f"git reset {file_path}")
144
+ os.system(f"git checkout -- {file_path}")
145
+ PrettyOutput.print("补丁生成失败", OutputType.WARNING)
146
+ act, msg = self.retry_comfirm()
147
+ if act == "break":
148
+ PrettyOutput.print("终止补丁应用", OutputType.WARNING)
149
+ return False, msg
150
+ if act == "skip":
346
151
  PrettyOutput.print(f"跳过文件 {file_path}", OutputType.WARNING)
347
152
  break
348
- if choice == "4":
349
- return False, "用户中止补丁应用"
350
-
351
- # 处理补充信息
352
- if choice == "2":
353
- additional_info = get_multiline_input("请输入补充说明和要求:")
354
- retry_count += 1
355
- continue # 直接进入下一次循环生成新补丁
356
-
357
- # 选择1直接重试
358
- retry_count += 1
359
- continue
153
+ else:
154
+ additional_info += msg + "\n"
155
+ continue
360
156
  else:
361
- continue
362
-
363
- # 所有文件处理完成后写入实际文件
364
- for file_path in modified_files:
365
- try:
366
- dir_path = os.path.dirname(file_path)
367
- if dir_path and not os.path.exists(dir_path):
368
- os.makedirs(dir_path, exist_ok=True)
369
-
370
- with open(file_path, "w", encoding="utf-8") as f:
371
- f.write(temp_map[file_path])
372
-
373
- PrettyOutput.print(f"成功修改文件: {file_path}", OutputType.SUCCESS)
374
-
375
- except Exception as e:
376
- error_info.append(f"写入文件失败 {file_path}: {str(e)}")
377
- return False, "\n".join(error_info)
157
+ break
378
158
 
379
159
  return True, ""
380
160
 
381
- def handle_patch_feedback(self, error_msg: str, feature: str) -> Dict[str, List[str]]:
382
- """处理补丁应用失败的反馈
383
-
384
- Args:
385
- error_msg: 错误信息
386
- feature: 功能描述
387
-
388
- Returns:
389
- List[Tuple[str, str, str]]: 新的补丁列表
390
- """
391
- PrettyOutput.print("补丁应用失败,尝试重新生成", OutputType.WARNING)
392
-
393
- # 获取用户补充信息
394
- additional_info = input("\n请输入补充信息(直接回车跳过):")
395
- PrettyOutput.print(f"开始重新生成补丁", OutputType.INFO)
396
-
397
- # 构建重试提示
398
- retry_prompt = f"""补丁应用失败,请根据以下信息重新生成补丁:
399
-
400
- 错误信息:
401
- {error_msg}
402
-
403
- 原始需求:
404
- {feature}
405
161
 
406
- 用户补充信息:
407
- {additional_info}
408
162
 
409
- 请重新生成补丁,确保:
410
- 1. 代码匹配完全准确
411
- 2. 保持正确的缩进和格式
412
- 3. 避免之前的错误
413
- """
414
- response = while_success(lambda: self.code_part_model.chat(retry_prompt), 5)
415
-
416
- try:
417
- patches = self._extract_code(response)
418
- return patches
419
-
420
- except Exception as e:
421
- PrettyOutput.print(f"解析patch失败: {str(e)}", OutputType.WARNING)
422
- return {}
423
-
424
- def monitor_patch_result(self, success: bool, error_msg: str) -> bool:
425
- """监控补丁应用结果
426
-
427
- Args:
428
- success: 是否成功
429
- error_msg: 错误信息
430
-
431
- Returns:
432
- bool: 是否继续尝试
433
- """
434
- if success:
435
- PrettyOutput.print("补丁应用成功", OutputType.SUCCESS)
436
- return False
437
-
438
- PrettyOutput.print(f"补丁应用失败: {error_msg}", OutputType.WARNING)
439
-
440
- # 询问是否继续尝试
441
- retry = input("\n是否重新尝试?(y/n) [y]: ").lower() or "y"
442
- return retry == "y"
443
-
444
- def handle_patch_application(self, related_files: List[Dict], feature: str, modification_plan: str) -> bool:
163
+ def handle_patch_application(self, feature: str, raw_plan: str, structed_plan: Dict[str,str]) -> bool:
445
164
  """处理补丁应用流程
446
165
 
447
166
  Args:
@@ -452,41 +171,22 @@ CONFIRM_DELETE
452
171
  Returns:
453
172
  bool: 是否成功应用补丁
454
173
  """
455
-
456
- while True:
457
- PrettyOutput.print("开始生成补丁...", OutputType.PROGRESS)
458
- patches = self.make_code_raw_patch(related_files, modification_plan)
459
- while True: # 在当前尝试中循环,直到成功或用户放弃
460
- # 1. 生成补丁
461
- if patches:
462
- # 2. 显示补丁内容
463
- PrettyOutput.print("\n将要应用以下补丁:", OutputType.INFO)
464
- for file_path, patches_code in patches.items():
465
- PrettyOutput.print(f"\n文件: {file_path}", OutputType.INFO)
466
- for i, code_part in enumerate(patches_code):
467
- PrettyOutput.print(f"片段{i}: \n{code_part}", OutputType.INFO)
468
- # 3. 应用补丁
469
- success, error_msg = self.apply_patch(related_files, modification_plan, patches)
470
- if not success:
471
- # 4. 如果应用失败,询问是否重试
472
- should_retry = self.monitor_patch_result(success, error_msg)
473
- if not should_retry:
474
- return False # 用户选择不重试,直接返回失败
475
-
476
- # 5. 处理失败反馈
477
- patches = self.handle_patch_feedback(error_msg, modification_plan)
478
- continue
479
- # 6. 应用成功,让用户确认修改
480
- PrettyOutput.print("\n补丁已应用,请检查修改效果。", OutputType.SUCCESS)
481
- confirm = input("\n是否保留这些修改?(y/n) [y]: ").lower() or "y"
482
- if confirm != "y":
483
- PrettyOutput.print("用户取消修改,正在回退", OutputType.WARNING)
484
- os.system("git reset --hard") # 回退所有修改
485
- user_feed = get_multiline_input("请输入补充修改需求(直接回车结束): ").strip()
486
- if not user_feed or user_feed == "__interrupt__":
487
- return True
488
-
489
- response = while_success(lambda: self.code_part_model.chat(user_feed), 5)
490
- patches = self._extract_code(response)
491
-
492
- continue # 回到外层循环重新开始补丁生成流程
174
+ PrettyOutput.print("\n将要应用以下修改方案:", OutputType.INFO)
175
+ for file_path, patches_code in structed_plan.items():
176
+ PrettyOutput.print(f"\n文件: {file_path}", OutputType.INFO)
177
+ PrettyOutput.print(f"修改方案: \n{patches_code}", OutputType.INFO)
178
+ # 3. 应用补丁
179
+ success, error_msg = self.apply_patch(feature, raw_plan, structed_plan)
180
+ if not success:
181
+ os.system("git reset --hard")
182
+ return False
183
+ # 6. 应用成功,让用户确认修改
184
+ PrettyOutput.print("\n补丁已应用,请检查修改效果。", OutputType.SUCCESS)
185
+ confirm = input("\n是否保留这些修改?(y/n) [y]: ").lower() or "y"
186
+ if confirm != "y":
187
+ PrettyOutput.print("用户取消修改,正在回退", OutputType.WARNING)
188
+ os.system("git reset --hard") # 回退所有修改
189
+ return False
190
+ else:
191
+ return True
192
+