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