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.
- jarvis/__init__.py +1 -1
- jarvis/agent.py +199 -157
- jarvis/jarvis_code_agent/__init__.py +0 -0
- jarvis/jarvis_code_agent/main.py +203 -0
- jarvis/jarvis_codebase/main.py +412 -284
- jarvis/jarvis_coder/file_select.py +209 -0
- jarvis/jarvis_coder/git_utils.py +81 -19
- jarvis/jarvis_coder/main.py +68 -446
- jarvis/jarvis_coder/patch_handler.py +117 -47
- jarvis/jarvis_coder/plan_generator.py +69 -27
- jarvis/jarvis_platform/main.py +38 -38
- jarvis/jarvis_rag/main.py +189 -189
- jarvis/jarvis_smart_shell/main.py +22 -24
- jarvis/models/base.py +6 -1
- jarvis/models/ollama.py +2 -2
- jarvis/models/registry.py +3 -6
- jarvis/tools/ask_user.py +6 -6
- jarvis/tools/codebase_qa.py +5 -7
- jarvis/tools/create_code_sub_agent.py +55 -0
- jarvis/tools/{sub_agent.py → create_sub_agent.py} +4 -1
- jarvis/tools/execute_code_modification.py +72 -0
- jarvis/tools/{file_ops.py → file_operation.py} +13 -14
- jarvis/tools/find_related_files.py +86 -0
- jarvis/tools/methodology.py +25 -25
- jarvis/tools/rag.py +32 -32
- jarvis/tools/registry.py +72 -36
- jarvis/tools/search.py +1 -1
- jarvis/tools/select_code_files.py +64 -0
- jarvis/utils.py +153 -49
- {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.99.dist-info/RECORD +52 -0
- {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/entry_points.txt +2 -1
- jarvis/main.py +0 -155
- jarvis/tools/coder.py +0 -69
- jarvis_ai_assistant-0.1.97.dist-info/RECORD +0 -47
- /jarvis/tools/{shell.py → execute_shell.py} +0 -0
- /jarvis/tools/{generator.py → generate_tool.py} +0 -0
- /jarvis/tools/{webpage.py → read_webpage.py} +0 -0
- {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.97.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import os
|
|
3
3
|
from typing import List, Tuple, Dict
|
|
4
|
+
import yaml
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
|
|
7
|
+
from jarvis.jarvis_coder.git_utils import generate_commit_message, init_git_repo, save_edit_record
|
|
5
8
|
from jarvis.models.base import BasePlatform
|
|
6
9
|
from jarvis.models.registry import PlatformRegistry
|
|
7
|
-
from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, while_success
|
|
10
|
+
from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, get_single_line_input, while_success
|
|
8
11
|
|
|
9
12
|
class Patch:
|
|
10
13
|
def __init__(self, old_code: str, new_code: str):
|
|
@@ -12,16 +15,41 @@ class Patch:
|
|
|
12
15
|
self.new_code = new_code
|
|
13
16
|
|
|
14
17
|
class PatchHandler:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.prompt_file = Path.home() / ".jarvis-coder-patch-prompt"
|
|
20
|
+
self.additional_info = self._load_additional_info()
|
|
21
|
+
self.root_dir = init_git_repo(os.getcwd())
|
|
22
|
+
self.record_dir = os.path.join(self.root_dir, ".jarvis-coder", "record")
|
|
23
|
+
if not os.path.exists(self.record_dir):
|
|
24
|
+
os.makedirs(self.record_dir)
|
|
25
|
+
def _load_additional_info(self) -> str:
|
|
26
|
+
"""Load saved additional info from prompt file"""
|
|
27
|
+
if not self.prompt_file.exists():
|
|
28
|
+
return ""
|
|
29
|
+
try:
|
|
30
|
+
with open(self.prompt_file, 'r') as f:
|
|
31
|
+
data = yaml.safe_load(f)
|
|
32
|
+
return data.get('additional_info', '') if data else ''
|
|
33
|
+
except Exception as e:
|
|
34
|
+
PrettyOutput.print(f"Failed to load additional info: {e}", OutputType.WARNING)
|
|
35
|
+
return ""
|
|
15
36
|
|
|
37
|
+
def _save_additional_info(self, info: str):
|
|
38
|
+
"""Save additional info to prompt file"""
|
|
39
|
+
try:
|
|
40
|
+
with open(self.prompt_file, 'w') as f:
|
|
41
|
+
yaml.dump({'additional_info': info}, f)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
PrettyOutput.print(f"Failed to save additional info: {e}", OutputType.WARNING)
|
|
16
44
|
|
|
17
45
|
def _extract_patches(self, response: str) -> List[Patch]:
|
|
18
|
-
"""
|
|
46
|
+
"""Extract patches from response
|
|
19
47
|
|
|
20
48
|
Args:
|
|
21
|
-
response:
|
|
49
|
+
response: Model response content
|
|
22
50
|
|
|
23
51
|
Returns:
|
|
24
|
-
List[Tuple[str, str, str]]:
|
|
52
|
+
List[Tuple[str, str, str]]: Patch list, each patch is a tuple of (format, file path, patch content)
|
|
25
53
|
"""
|
|
26
54
|
# 修改后的正则表达式匹配三种补丁格式
|
|
27
55
|
fmt_pattern = r'<PATCH>\n>>>>>> SEARCH\n(.*?)\n?(={5,})\n(.*?)\n?<<<<<< REPLACE\n</PATCH>'
|
|
@@ -32,21 +60,53 @@ class PatchHandler:
|
|
|
32
60
|
|
|
33
61
|
|
|
34
62
|
def _confirm_and_apply_changes(self, file_path: str) -> bool:
|
|
35
|
-
"""
|
|
63
|
+
"""Confirm and apply changes"""
|
|
36
64
|
os.system(f"git diff --cached {file_path}")
|
|
37
|
-
confirm =
|
|
65
|
+
confirm = get_single_line_input(f"Accept {file_path} changes? (y/n) [y]").lower() or "y"
|
|
38
66
|
if confirm == "y":
|
|
39
67
|
return True
|
|
40
68
|
else:
|
|
41
|
-
#
|
|
69
|
+
# Rollback changes
|
|
42
70
|
os.system(f"git reset {file_path}")
|
|
43
71
|
os.system(f"git checkout -- {file_path}")
|
|
44
|
-
PrettyOutput.print(f"
|
|
72
|
+
PrettyOutput.print(f"Changes to {file_path} have been rolled back", OutputType.WARNING)
|
|
45
73
|
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _finalize_changes(self) -> None:
|
|
78
|
+
"""Complete changes and commit"""
|
|
79
|
+
PrettyOutput.print("Modification confirmed, committing...", OutputType.INFO)
|
|
80
|
+
|
|
81
|
+
# Add only modified files under git control
|
|
82
|
+
os.system("git add -u")
|
|
83
|
+
|
|
84
|
+
# Then get git diff
|
|
85
|
+
git_diff = os.popen("git diff --cached").read()
|
|
86
|
+
|
|
87
|
+
# Automatically generate commit information, pass in feature
|
|
88
|
+
commit_message = generate_commit_message(git_diff)
|
|
89
|
+
|
|
90
|
+
# Display and confirm commit information
|
|
91
|
+
PrettyOutput.print(f"Automatically generated commit information: {commit_message}", OutputType.INFO)
|
|
92
|
+
user_confirm = get_single_line_input("Use this commit information? (y/n) [y]").lower() or "y"
|
|
93
|
+
|
|
94
|
+
if user_confirm.lower() != "y":
|
|
95
|
+
commit_message = get_single_line_input("Please enter a new commit information")
|
|
96
|
+
|
|
97
|
+
# No need to git add again, it has already been added
|
|
98
|
+
os.system(f"git commit -m '{commit_message}'")
|
|
99
|
+
save_edit_record(self.record_dir, commit_message, git_diff)
|
|
100
|
+
|
|
101
|
+
def _revert_changes(self) -> None:
|
|
102
|
+
"""Revert all changes"""
|
|
103
|
+
PrettyOutput.print("Modification cancelled, reverting changes", OutputType.INFO)
|
|
104
|
+
os.system(f"git reset --hard")
|
|
105
|
+
os.system(f"git clean -df")
|
|
46
106
|
|
|
47
107
|
|
|
48
108
|
def apply_file_patch(self, file_path: str, patches: List[Patch]) -> bool:
|
|
49
|
-
"""
|
|
109
|
+
"""Apply file patch"""
|
|
50
110
|
if not os.path.exists(file_path):
|
|
51
111
|
base_dir = os.path.dirname(file_path)
|
|
52
112
|
os.makedirs(base_dir, exist_ok=True)
|
|
@@ -54,20 +114,20 @@ class PatchHandler:
|
|
|
54
114
|
file_content = open(file_path, "r", encoding="utf-8").read()
|
|
55
115
|
for i, patch in enumerate(patches):
|
|
56
116
|
if patch.old_code == "" and patch.new_code == "":
|
|
57
|
-
PrettyOutput.print(f"
|
|
117
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: Delete file {file_path}", OutputType.INFO)
|
|
58
118
|
file_content = ""
|
|
59
119
|
os.system(f"git rm {file_path}")
|
|
60
|
-
PrettyOutput.print(f"
|
|
120
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
|
|
61
121
|
elif patch.old_code == "":
|
|
62
|
-
PrettyOutput.print(f"
|
|
122
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: Replace file {file_path} content: \n{patch.new_code}", OutputType.INFO)
|
|
63
123
|
file_content = patch.new_code
|
|
64
124
|
open(file_path, "w", encoding="utf-8").write(patch.new_code)
|
|
65
125
|
os.system(f"git add {file_path}")
|
|
66
|
-
PrettyOutput.print(f"
|
|
126
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
|
|
67
127
|
else:
|
|
68
|
-
PrettyOutput.print(f"
|
|
128
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: File original content: \n{patch.old_code}\nReplace with: \n{patch.new_code}", OutputType.INFO)
|
|
69
129
|
if file_content.find(patch.old_code) == -1:
|
|
70
|
-
PrettyOutput.print(f"
|
|
130
|
+
PrettyOutput.print(f"File {file_path} does not contain {patch.old_code}", OutputType.WARNING)
|
|
71
131
|
os.system(f"git reset {file_path}")
|
|
72
132
|
os.system(f"git checkout -- {file_path}")
|
|
73
133
|
return False
|
|
@@ -75,28 +135,36 @@ class PatchHandler:
|
|
|
75
135
|
file_content = file_content.replace(patch.old_code, patch.new_code, 1)
|
|
76
136
|
open(file_path, "w", encoding="utf-8").write(file_content)
|
|
77
137
|
os.system(f"git add {file_path}")
|
|
78
|
-
PrettyOutput.print(f"
|
|
138
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
|
|
79
139
|
return True
|
|
80
140
|
|
|
81
141
|
|
|
82
|
-
def retry_comfirm(self) -> Tuple[str, str]
|
|
83
|
-
choice =
|
|
142
|
+
def retry_comfirm(self) -> Tuple[str, str]:
|
|
143
|
+
choice = get_single_line_input("\nPlease choose an action: (1) Retry (2) Skip (3) Completely stop [1]: ") or "1"
|
|
84
144
|
if choice == "2":
|
|
85
145
|
return "skip", ""
|
|
86
146
|
if choice == "3":
|
|
87
147
|
return "break", ""
|
|
88
|
-
|
|
148
|
+
|
|
149
|
+
feedback = get_multiline_input("Please enter additional information and requirements:")
|
|
150
|
+
if feedback:
|
|
151
|
+
save_prompt = get_single_line_input("Would you like to save this as general feedback for future patches? (y/n) [n]: ").lower() or "n"
|
|
152
|
+
if save_prompt == "y":
|
|
153
|
+
self._save_additional_info(feedback)
|
|
154
|
+
PrettyOutput.print("Feedback saved for future use", OutputType.SUCCESS)
|
|
155
|
+
|
|
156
|
+
return "continue", feedback
|
|
89
157
|
|
|
90
|
-
def apply_patch(self, feature: str,
|
|
91
|
-
"""
|
|
158
|
+
def apply_patch(self, feature: str, structed_plan: Dict[str, str]) -> bool:
|
|
159
|
+
"""Apply patch (main entry)"""
|
|
92
160
|
for file_path, current_plan in structed_plan.items():
|
|
93
|
-
additional_info =
|
|
161
|
+
additional_info = self.additional_info # Initialize with saved info
|
|
94
162
|
while True:
|
|
95
163
|
|
|
96
164
|
if os.path.exists(file_path):
|
|
97
165
|
content = open(file_path, "r", encoding="utf-8").read()
|
|
98
166
|
else:
|
|
99
|
-
content = "
|
|
167
|
+
content = "<File does not exist, need to create>"
|
|
100
168
|
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
169
|
<PATCH>
|
|
102
170
|
>>>>>> SEARCH
|
|
@@ -114,16 +182,17 @@ class PatchHandler:
|
|
|
114
182
|
2. old_code will be replaced with new_code, pay attention to context continuity
|
|
115
183
|
3. Avoid breaking existing code logic when generating patches, e.g., don't insert function definitions inside existing function bodies
|
|
116
184
|
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
|
|
185
|
+
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
186
|
6. Ensure generated code has correct format (syntax, indentation, line breaks)
|
|
119
187
|
7. Ensure new_code's indentation and format matches old_code
|
|
120
188
|
8. Ensure code is inserted in appropriate locations, e.g., code using variables should be after declarations/definitions
|
|
121
189
|
9. Provide at least 3 lines of context before and after modified code for location
|
|
190
|
+
10. Each patch should be no more than 20 lines of code, if it is more than 20 lines, split it into multiple patches
|
|
191
|
+
11. old code's line breaks should be consistent with the original code
|
|
122
192
|
|
|
123
193
|
|
|
124
194
|
"""
|
|
125
195
|
prompt += f"""# Original requirement: {feature}
|
|
126
|
-
# Complete modification plan: {raw_plan}
|
|
127
196
|
# Current file path: {file_path}
|
|
128
197
|
# Current file content:
|
|
129
198
|
<CONTENT>
|
|
@@ -135,57 +204,58 @@ class PatchHandler:
|
|
|
135
204
|
"""
|
|
136
205
|
|
|
137
206
|
|
|
138
|
-
PrettyOutput.print(f"
|
|
207
|
+
PrettyOutput.print(f"Generating formatted patches for {file_path}...", OutputType.PROGRESS)
|
|
139
208
|
response = PlatformRegistry.get_global_platform_registry().get_codegen_platform().chat_until_success(prompt)
|
|
140
209
|
patches = self._extract_patches(response)
|
|
141
210
|
|
|
142
211
|
if not patches or not self.apply_file_patch(file_path, patches) or not self._confirm_and_apply_changes(file_path):
|
|
143
212
|
os.system(f"git reset {file_path}")
|
|
144
213
|
os.system(f"git checkout -- {file_path}")
|
|
145
|
-
PrettyOutput.print("
|
|
214
|
+
PrettyOutput.print("Patch generation failed", OutputType.WARNING)
|
|
146
215
|
act, msg = self.retry_comfirm()
|
|
147
216
|
if act == "break":
|
|
148
|
-
PrettyOutput.print("
|
|
149
|
-
return False
|
|
217
|
+
PrettyOutput.print("Terminate patch application", OutputType.WARNING)
|
|
218
|
+
return False
|
|
150
219
|
if act == "skip":
|
|
151
|
-
PrettyOutput.print(f"
|
|
220
|
+
PrettyOutput.print(f"Skip file {file_path}", OutputType.WARNING)
|
|
152
221
|
break
|
|
153
222
|
else:
|
|
154
223
|
additional_info += msg + "\n"
|
|
155
224
|
continue
|
|
156
225
|
else:
|
|
226
|
+
self._finalize_changes()
|
|
157
227
|
break
|
|
158
228
|
|
|
159
|
-
return True
|
|
229
|
+
return True
|
|
160
230
|
|
|
161
231
|
|
|
162
232
|
|
|
163
|
-
def handle_patch_application(self, feature: str,
|
|
164
|
-
"""
|
|
233
|
+
def handle_patch_application(self, feature: str, structed_plan: Dict[str,str]) -> bool:
|
|
234
|
+
"""Process patch application process
|
|
165
235
|
|
|
166
236
|
Args:
|
|
167
|
-
related_files:
|
|
168
|
-
feature:
|
|
169
|
-
modification_plan:
|
|
237
|
+
related_files: Related files list
|
|
238
|
+
feature: Feature description
|
|
239
|
+
modification_plan: Modification plan
|
|
170
240
|
|
|
171
241
|
Returns:
|
|
172
|
-
bool:
|
|
242
|
+
bool: Whether patch application is successful
|
|
173
243
|
"""
|
|
174
|
-
PrettyOutput.print("\
|
|
244
|
+
PrettyOutput.print("\nThe following modification plan will be applied:", OutputType.INFO)
|
|
175
245
|
for file_path, patches_code in structed_plan.items():
|
|
176
|
-
PrettyOutput.print(f"\
|
|
177
|
-
PrettyOutput.print(f"
|
|
178
|
-
# 3.
|
|
179
|
-
success
|
|
246
|
+
PrettyOutput.print(f"\nFile: {file_path}", OutputType.INFO)
|
|
247
|
+
PrettyOutput.print(f"Modification plan: \n{patches_code}", OutputType.INFO)
|
|
248
|
+
# 3. Apply patches
|
|
249
|
+
success = self.apply_patch(feature, structed_plan)
|
|
180
250
|
if not success:
|
|
181
251
|
os.system("git reset --hard")
|
|
182
252
|
return False
|
|
183
|
-
# 6.
|
|
184
|
-
PrettyOutput.print("\
|
|
185
|
-
confirm =
|
|
253
|
+
# 6. Apply successfully, let user confirm changes
|
|
254
|
+
PrettyOutput.print("\nPatches applied, please check the modification effect.", OutputType.SUCCESS)
|
|
255
|
+
confirm = get_single_line_input("\nKeep these changes? (y/n) [y]: ").lower() or "y"
|
|
186
256
|
if confirm != "y":
|
|
187
|
-
PrettyOutput.print("
|
|
188
|
-
os.system("git reset --hard") #
|
|
257
|
+
PrettyOutput.print("User cancelled changes, rolling back", OutputType.WARNING)
|
|
258
|
+
os.system("git reset --hard") # Rollback all changes
|
|
189
259
|
return False
|
|
190
260
|
else:
|
|
191
261
|
return True
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from typing import Dict, List, Tuple
|
|
3
3
|
from jarvis.models.registry import PlatformRegistry
|
|
4
|
-
from jarvis.utils import PrettyOutput, OutputType, get_multiline_input
|
|
4
|
+
from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, get_single_line_input, is_long_context
|
|
5
5
|
|
|
6
6
|
class PlanGenerator:
|
|
7
|
-
"""
|
|
7
|
+
"""Modification plan generator"""
|
|
8
8
|
|
|
9
9
|
def _build_prompt(self, feature: str, related_files: List[Dict], additional_info: str) -> str:
|
|
10
|
-
"""
|
|
10
|
+
"""Build prompt
|
|
11
11
|
|
|
12
12
|
Args:
|
|
13
|
-
feature:
|
|
14
|
-
related_files:
|
|
15
|
-
additional_info:
|
|
13
|
+
feature: Feature description
|
|
14
|
+
related_files: Related files list
|
|
15
|
+
additional_info: User supplement information
|
|
16
16
|
|
|
17
17
|
Returns:
|
|
18
|
-
str:
|
|
18
|
+
str: Complete prompt
|
|
19
19
|
"""
|
|
20
20
|
prompt = "You are a code modification expert who can generate modification plans based on requirements and relevant code snippets. I need your help to analyze how to implement the following feature:\n\n"
|
|
21
21
|
prompt += f"{feature}\n\n"
|
|
@@ -48,52 +48,94 @@ class PlanGenerator:
|
|
|
48
48
|
return prompt
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
@staticmethod
|
|
52
|
+
def get_key_code(files: List[str], feature: str)->List[Dict[str, List[str]]]:
|
|
53
|
+
"""Extract relevant key code snippets from files"""
|
|
54
|
+
ret = []
|
|
55
|
+
for file in files:
|
|
56
|
+
PrettyOutput.print(f"Analyzing file: {file}", OutputType.INFO)
|
|
57
|
+
model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
|
|
58
|
+
model.set_suppress_output(True)
|
|
59
|
+
file_path = file
|
|
60
|
+
content = open(file_path, "r", encoding="utf-8").read()
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
prompt = f"""You are a code analysis expert who can extract relevant snippets from code.
|
|
64
|
+
Please return in the following format:
|
|
65
|
+
<PART>
|
|
66
|
+
content
|
|
67
|
+
</PART>
|
|
68
|
+
|
|
69
|
+
Multiple snippets can be returned. If the file content is not relevant to the requirement, return empty.
|
|
70
|
+
|
|
71
|
+
Requirement: {feature}
|
|
72
|
+
File path: {file_path}
|
|
73
|
+
Code content:
|
|
74
|
+
{content}
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# 调用大模型进行分析
|
|
78
|
+
response = model.chat_until_success(prompt)
|
|
79
|
+
|
|
80
|
+
parts = re.findall(r'<PART>\n(.*?)\n</PART>', response, re.DOTALL)
|
|
81
|
+
ret.append({"file_path": file, "parts": parts})
|
|
82
|
+
except Exception as e:
|
|
83
|
+
PrettyOutput.print(f"Failed to analyze file: {str(e)}", OutputType.ERROR)
|
|
84
|
+
return ret
|
|
85
|
+
|
|
86
|
+
def generate_plan(self, feature: str, related_files: List[str]) -> Dict[str,str]:
|
|
87
|
+
"""Generate modification plan
|
|
53
88
|
|
|
54
89
|
Args:
|
|
55
|
-
feature:
|
|
56
|
-
related_files:
|
|
90
|
+
feature: Feature description
|
|
91
|
+
related_files: Related files list
|
|
57
92
|
|
|
58
93
|
Returns:
|
|
59
|
-
Tuple[str, Dict[str,str]]:
|
|
94
|
+
Tuple[str, Dict[str,str]]: Modification plan, return None if user cancels
|
|
60
95
|
"""
|
|
61
96
|
additional_info = ""
|
|
97
|
+
file_info = []
|
|
98
|
+
if is_long_context(related_files):
|
|
99
|
+
file_info = PlanGenerator.get_key_code(related_files, feature)
|
|
100
|
+
else:
|
|
101
|
+
for file in related_files:
|
|
102
|
+
file_info.append({"file_path": file, "parts": [open(file, "r", encoding="utf-8").read()]})
|
|
103
|
+
|
|
62
104
|
while True:
|
|
63
|
-
prompt = self._build_prompt(feature,
|
|
64
|
-
#
|
|
65
|
-
PrettyOutput.print("
|
|
105
|
+
prompt = self._build_prompt(feature, file_info, additional_info)
|
|
106
|
+
# Build prompt
|
|
107
|
+
PrettyOutput.print("Start generating modification plan...", OutputType.PROGRESS)
|
|
66
108
|
|
|
67
|
-
#
|
|
109
|
+
# Get modification plan
|
|
68
110
|
raw_plan = PlatformRegistry.get_global_platform_registry().get_thinking_platform().chat_until_success(prompt)
|
|
69
111
|
structed_plan = self._extract_code(raw_plan)
|
|
70
112
|
if not structed_plan:
|
|
71
|
-
PrettyOutput.print("
|
|
72
|
-
tmp = get_multiline_input("
|
|
113
|
+
PrettyOutput.print("Modification plan generation failed, please try again", OutputType.ERROR)
|
|
114
|
+
tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
|
|
73
115
|
if tmp == "__interrupt__" or prompt == "":
|
|
74
|
-
return
|
|
116
|
+
return {}
|
|
75
117
|
additional_info += tmp + "\n"
|
|
76
118
|
continue
|
|
77
|
-
user_input =
|
|
119
|
+
user_input = get_single_line_input("Do you agree with this modification plan? (y/n) [y]").strip().lower() or 'y'
|
|
78
120
|
if user_input == 'y' or user_input == '':
|
|
79
|
-
return
|
|
121
|
+
return structed_plan
|
|
80
122
|
elif user_input == 'n':
|
|
81
|
-
#
|
|
82
|
-
tmp = get_multiline_input("
|
|
123
|
+
# Get user feedback
|
|
124
|
+
tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
|
|
83
125
|
if prompt == "__interrupt__" or prompt == "":
|
|
84
|
-
return
|
|
126
|
+
return {}
|
|
85
127
|
additional_info += tmp + "\n"
|
|
86
128
|
continue
|
|
87
129
|
|
|
88
130
|
|
|
89
131
|
def _extract_code(self, response: str) -> Dict[str, str]:
|
|
90
|
-
"""
|
|
132
|
+
"""Extract code from response
|
|
91
133
|
|
|
92
134
|
Args:
|
|
93
|
-
response:
|
|
135
|
+
response: Model response content
|
|
94
136
|
|
|
95
137
|
Returns:
|
|
96
|
-
Dict[str, List[str]]:
|
|
138
|
+
Dict[str, List[str]]: Code dictionary, key is file path, value is code snippet list
|
|
97
139
|
"""
|
|
98
140
|
code_dict = {}
|
|
99
141
|
for match in re.finditer(r'<PLAN>\n> (.+?)\n(.*?)\n</PLAN>', response, re.DOTALL):
|
jarvis/jarvis_platform/main.py
CHANGED
|
@@ -2,26 +2,26 @@ from jarvis.models.registry import PlatformRegistry
|
|
|
2
2
|
from jarvis.utils import PrettyOutput, OutputType, load_env_from_file, get_multiline_input
|
|
3
3
|
|
|
4
4
|
def list_platforms():
|
|
5
|
-
"""
|
|
5
|
+
"""List all supported platforms and models"""
|
|
6
6
|
registry = PlatformRegistry.get_global_platform_registry()
|
|
7
7
|
platforms = registry.get_available_platforms()
|
|
8
8
|
|
|
9
|
-
PrettyOutput.section("
|
|
9
|
+
PrettyOutput.section("Supported platforms and models", OutputType.SUCCESS)
|
|
10
10
|
|
|
11
11
|
for platform_name in platforms:
|
|
12
|
-
#
|
|
12
|
+
# Create platform instance
|
|
13
13
|
platform = registry.create_platform(platform_name)
|
|
14
14
|
if not platform:
|
|
15
15
|
continue
|
|
16
16
|
|
|
17
|
-
#
|
|
17
|
+
# Get the list of models supported by the platform
|
|
18
18
|
try:
|
|
19
19
|
models = platform.get_model_list()
|
|
20
20
|
|
|
21
|
-
#
|
|
21
|
+
# Print platform name
|
|
22
22
|
PrettyOutput.section(f"{platform_name}", OutputType.SUCCESS)
|
|
23
23
|
|
|
24
|
-
#
|
|
24
|
+
# Print model list
|
|
25
25
|
if models:
|
|
26
26
|
for model_name, description in models:
|
|
27
27
|
if description:
|
|
@@ -29,95 +29,95 @@ def list_platforms():
|
|
|
29
29
|
else:
|
|
30
30
|
PrettyOutput.print(f" • {model_name}", OutputType.SUCCESS)
|
|
31
31
|
else:
|
|
32
|
-
PrettyOutput.print("
|
|
32
|
+
PrettyOutput.print(" • No available model information", OutputType.WARNING)
|
|
33
33
|
|
|
34
34
|
except Exception as e:
|
|
35
|
-
PrettyOutput.print(f"
|
|
35
|
+
PrettyOutput.print(f"Failed to get model list for {platform_name}: {str(e)}", OutputType.WARNING)
|
|
36
36
|
|
|
37
37
|
def chat_with_model(platform_name: str, model_name: str):
|
|
38
|
-
"""
|
|
38
|
+
"""Chat with specified platform and model"""
|
|
39
39
|
registry = PlatformRegistry.get_global_platform_registry()
|
|
40
40
|
|
|
41
|
-
#
|
|
41
|
+
# Create platform instance
|
|
42
42
|
platform = registry.create_platform(platform_name)
|
|
43
43
|
if not platform:
|
|
44
|
-
PrettyOutput.print(f"
|
|
44
|
+
PrettyOutput.print(f"Failed to create platform {platform_name}", OutputType.ERROR)
|
|
45
45
|
return
|
|
46
46
|
|
|
47
47
|
try:
|
|
48
|
-
#
|
|
48
|
+
# Set model
|
|
49
49
|
platform.set_model_name(model_name)
|
|
50
|
-
PrettyOutput.print(f"
|
|
50
|
+
PrettyOutput.print(f"Connected to {platform_name} platform {model_name} model", OutputType.SUCCESS)
|
|
51
51
|
|
|
52
|
-
#
|
|
52
|
+
# Start conversation loop
|
|
53
53
|
while True:
|
|
54
|
-
#
|
|
54
|
+
# Get user input
|
|
55
55
|
user_input = get_multiline_input("")
|
|
56
56
|
|
|
57
|
-
#
|
|
57
|
+
# Check if input is cancelled
|
|
58
58
|
if user_input == "__interrupt__" or user_input.strip() == "/bye":
|
|
59
|
-
PrettyOutput.print("
|
|
59
|
+
PrettyOutput.print("Bye!", OutputType.SUCCESS)
|
|
60
60
|
break
|
|
61
61
|
|
|
62
|
-
#
|
|
62
|
+
# Check if input is empty
|
|
63
63
|
if not user_input.strip():
|
|
64
64
|
continue
|
|
65
65
|
|
|
66
|
-
#
|
|
66
|
+
# Check if it is a clear session command
|
|
67
67
|
if user_input.strip() == "/clear":
|
|
68
68
|
try:
|
|
69
69
|
platform.delete_chat()
|
|
70
|
-
platform.set_model_name(model_name) #
|
|
71
|
-
PrettyOutput.print("
|
|
70
|
+
platform.set_model_name(model_name) # Reinitialize session
|
|
71
|
+
PrettyOutput.print("Session cleared", OutputType.SUCCESS)
|
|
72
72
|
except Exception as e:
|
|
73
|
-
PrettyOutput.print(f"
|
|
73
|
+
PrettyOutput.print(f"Failed to clear session: {str(e)}", OutputType.ERROR)
|
|
74
74
|
continue
|
|
75
75
|
|
|
76
76
|
try:
|
|
77
|
-
#
|
|
77
|
+
# Send to model and get reply
|
|
78
78
|
response = platform.chat_until_success(user_input)
|
|
79
79
|
if not response:
|
|
80
|
-
PrettyOutput.print("
|
|
80
|
+
PrettyOutput.print("No valid reply", OutputType.WARNING)
|
|
81
81
|
|
|
82
82
|
except Exception as e:
|
|
83
|
-
PrettyOutput.print(f"
|
|
83
|
+
PrettyOutput.print(f"Failed to chat: {str(e)}", OutputType.ERROR)
|
|
84
84
|
|
|
85
85
|
except Exception as e:
|
|
86
|
-
PrettyOutput.print(f"
|
|
86
|
+
PrettyOutput.print(f"Failed to initialize conversation: {str(e)}", OutputType.ERROR)
|
|
87
87
|
finally:
|
|
88
|
-
#
|
|
88
|
+
# Clean up resources
|
|
89
89
|
try:
|
|
90
90
|
platform.delete_chat()
|
|
91
91
|
except:
|
|
92
92
|
pass
|
|
93
93
|
|
|
94
94
|
def info_command(args):
|
|
95
|
-
"""
|
|
95
|
+
"""Process info subcommand"""
|
|
96
96
|
list_platforms()
|
|
97
97
|
|
|
98
98
|
def chat_command(args):
|
|
99
|
-
"""
|
|
99
|
+
"""Process chat subcommand"""
|
|
100
100
|
if not args.platform or not args.model:
|
|
101
|
-
PrettyOutput.print("
|
|
101
|
+
PrettyOutput.print("Please specify platform and model. Use 'jarvis info' to view available platforms and models.", OutputType.ERROR)
|
|
102
102
|
return
|
|
103
103
|
chat_with_model(args.platform, args.model)
|
|
104
104
|
|
|
105
105
|
def main():
|
|
106
|
-
"""
|
|
106
|
+
"""Main function"""
|
|
107
107
|
import argparse
|
|
108
108
|
|
|
109
109
|
load_env_from_file()
|
|
110
110
|
|
|
111
111
|
parser = argparse.ArgumentParser(description='Jarvis AI Platform')
|
|
112
|
-
subparsers = parser.add_subparsers(dest='command', help='
|
|
112
|
+
subparsers = parser.add_subparsers(dest='command', help='Available subcommands')
|
|
113
113
|
|
|
114
|
-
# info
|
|
115
|
-
info_parser = subparsers.add_parser('info', help='
|
|
114
|
+
# info subcommand
|
|
115
|
+
info_parser = subparsers.add_parser('info', help='Display supported platforms and models information')
|
|
116
116
|
|
|
117
|
-
# chat
|
|
118
|
-
chat_parser = subparsers.add_parser('chat', help='
|
|
119
|
-
chat_parser.add_argument('--platform', '-p', help='
|
|
120
|
-
chat_parser.add_argument('--model', '-m', help='
|
|
117
|
+
# chat subcommand
|
|
118
|
+
chat_parser = subparsers.add_parser('chat', help='Chat with specified platform and model')
|
|
119
|
+
chat_parser.add_argument('--platform', '-p', help='Specify the platform to use')
|
|
120
|
+
chat_parser.add_argument('--model', '-m', help='Specify the model to use')
|
|
121
121
|
|
|
122
122
|
args = parser.parse_args()
|
|
123
123
|
|