jarvis-ai-assistant 0.1.101__py3-none-any.whl → 0.1.102__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.

@@ -1,241 +0,0 @@
1
- import os
2
- import threading
3
- from typing import Dict, Any, List, Optional
4
- import re
5
-
6
- from jarvis.jarvis_coder.file_select import select_files
7
- from jarvis.utils import OutputType, PrettyOutput, find_git_root, get_max_context_length, is_long_context, init_env, while_success
8
- from jarvis.models.registry import PlatformRegistry
9
- from jarvis.jarvis_codebase.main import CodeBase
10
- from prompt_toolkit import PromptSession
11
- from prompt_toolkit.completion import WordCompleter, Completer, Completion
12
- from prompt_toolkit.formatted_text import FormattedText
13
- from prompt_toolkit.styles import Style
14
- import fnmatch
15
- from .patch_handler import PatchHandler
16
- from .git_utils import generate_commit_message, init_git_repo, save_edit_record
17
- from .plan_generator import PlanGenerator
18
-
19
- # 全局锁对象
20
- index_lock = threading.Lock()
21
-
22
- class JarvisCoder:
23
- def __init__(self, root_dir: str, language: Optional[str] = "python"):
24
- """Initialize code modification tool"""
25
- self.root_dir = root_dir
26
- self.language = language
27
- self._init_directories()
28
- self._init_codebase()
29
-
30
- def _init_directories(self):
31
- """Initialize directories"""
32
- self.max_context_length = get_max_context_length()
33
- self.root_dir = init_git_repo(self.root_dir)
34
-
35
- def _init_codebase(self):
36
- """Initialize codebase"""
37
- self._codebase = CodeBase(self.root_dir)
38
-
39
-
40
- def _load_related_files(self, feature: str) -> List[str]:
41
- """Load related file content"""
42
- ret = []
43
- # Ensure the index database is generated
44
- if not self._codebase.is_index_generated():
45
- PrettyOutput.print("Index database not generated, generating...", OutputType.WARNING)
46
- self._codebase.generate_codebase()
47
-
48
- related_files = self._codebase.search_similar(feature)
49
- for file in related_files:
50
- PrettyOutput.print(f"Related file: {file}", OutputType.SUCCESS)
51
- ret.append(file)
52
- return ret
53
-
54
-
55
-
56
- def execute(self, feature: str) -> Dict[str, Any]:
57
- """Execute code modification"""
58
- try:
59
- # Get and select related files
60
- initial_files = self._load_related_files(feature)
61
- selected_files = select_files(initial_files, self.root_dir)
62
-
63
- # Get modification plan
64
- structed_plan = PlanGenerator().generate_plan(feature, selected_files)
65
- if not structed_plan:
66
- return {
67
- "success": False,
68
- "stdout": "",
69
- "stderr": "Failed to generate modification plan, please modify the requirement and try again",
70
- }
71
-
72
- # Execute modification
73
- if PatchHandler().handle_patch_application(feature, structed_plan):
74
- return {
75
- "success": True,
76
- "stdout": "Code modification successful",
77
- "stderr": "",
78
- }
79
- else:
80
- return {
81
- "success": False,
82
- "stdout": "",
83
- "stderr": "Code modification failed, please modify the requirement and try again",
84
- }
85
-
86
- except Exception as e:
87
- return {
88
- "success": False,
89
- "stdout": "",
90
- "stderr": f"Execution failed: {str(e)}, please modify the requirement and try again",
91
- }
92
-
93
- def main():
94
- """Command line entry"""
95
- import argparse
96
-
97
- init_env()
98
-
99
- parser = argparse.ArgumentParser(description='Code modification tool')
100
- parser.add_argument('-d', '--dir', help='Project root directory', default=os.getcwd())
101
- parser.add_argument('-l', '--language', help='Programming language', default="python")
102
- args = parser.parse_args()
103
-
104
- tool = JarvisCoder(args.dir, args.language)
105
-
106
- # Loop through requirements
107
- while True:
108
- try:
109
- # Get requirements, pass in project root directory
110
- feature = get_multiline_input("Please enter the development requirements (input empty line to exit):", tool.root_dir)
111
-
112
- if not feature or feature == "__interrupt__":
113
- break
114
-
115
- # Execute modification
116
- result = tool.execute(feature)
117
-
118
- # Display results
119
- if result["success"]:
120
- PrettyOutput.print(result["stdout"], OutputType.SUCCESS)
121
- else:
122
- if result.get("stderr"):
123
- PrettyOutput.print(result["stderr"], OutputType.WARNING)
124
- PrettyOutput.print("\nYou can modify the requirements and try again", OutputType.INFO)
125
-
126
- except KeyboardInterrupt:
127
- print("\nUser interrupted execution")
128
- break
129
- except Exception as e:
130
- PrettyOutput.print(f"Execution failed: {str(e)}", OutputType.ERROR)
131
- PrettyOutput.print("\nYou can modify the requirements and try again", OutputType.INFO)
132
- continue
133
-
134
- return 0
135
-
136
- if __name__ == "__main__":
137
- exit(main())
138
-
139
- class FilePathCompleter(Completer):
140
- """File path auto-completer"""
141
-
142
- def __init__(self, root_dir: str):
143
- self.root_dir = root_dir
144
- self._file_list = None
145
-
146
- def _get_files(self) -> List[str]:
147
- """Get the list of files managed by git"""
148
- if self._file_list is None:
149
- try:
150
- # Switch to project root directory
151
- old_cwd = os.getcwd()
152
- os.chdir(self.root_dir)
153
-
154
- # Get the list of files managed by git
155
- self._file_list = os.popen("git ls-files").read().splitlines()
156
-
157
- # Restore working directory
158
- os.chdir(old_cwd)
159
- except Exception as e:
160
- PrettyOutput.print(f"Failed to get file list: {str(e)}", OutputType.WARNING)
161
- self._file_list = []
162
- return self._file_list
163
-
164
- def get_completions(self, document, complete_event):
165
- """Get completion suggestions"""
166
- text_before_cursor = document.text_before_cursor
167
-
168
- # Check if @ was just entered
169
- if text_before_cursor.endswith('@'):
170
- # Display all files
171
- for path in self._get_files():
172
- yield Completion(path, start_position=0)
173
- return
174
-
175
- # Check if there was an @ before, and get the search word after @
176
- at_pos = text_before_cursor.rfind('@')
177
- if at_pos == -1:
178
- return
179
-
180
- search = text_before_cursor[at_pos + 1:].lower().strip()
181
-
182
- # Provide matching file suggestions
183
- for path in self._get_files():
184
- path_lower = path.lower()
185
- if (search in path_lower or # Directly included
186
- search in os.path.basename(path_lower) or # File name included
187
- any(fnmatch.fnmatch(path_lower, f'*{s}*') for s in search.split())): # Wildcard matching
188
- # Calculate the correct start_position
189
- yield Completion(path, start_position=-(len(search)))
190
-
191
-
192
- def get_multiline_input(prompt_text: str, root_dir: Optional[str] = ".") -> str:
193
- """Get multi-line input, support file path auto-completion function
194
-
195
- Args:
196
- prompt_text: Prompt text
197
- root_dir: Project root directory, for file completion
198
-
199
- Returns:
200
- str: User input text
201
- """
202
- # Create file completion
203
- file_completer = FilePathCompleter(root_dir or os.getcwd())
204
-
205
- # Create prompt style
206
- style = Style.from_dict({
207
- 'prompt': 'ansicyan bold',
208
- 'input': 'ansiwhite',
209
- })
210
-
211
- # Create session
212
- session = PromptSession(
213
- completer=file_completer,
214
- style=style,
215
- multiline=False,
216
- enable_history_search=True,
217
- complete_while_typing=True
218
- )
219
-
220
- # Display initial prompt text
221
- print(f"\n{prompt_text}")
222
-
223
- # Create prompt
224
- prompt = FormattedText([
225
- ('class:prompt', ">>> ")
226
- ])
227
-
228
- # Get input
229
- lines = []
230
- try:
231
- while True:
232
- line = session.prompt(prompt).strip()
233
- if not line: # Empty line means input end
234
- break
235
- lines.append(line)
236
- except KeyboardInterrupt:
237
- return "__interrupt__"
238
- except EOFError:
239
- pass
240
-
241
- return "\n".join(lines)
@@ -1,145 +0,0 @@
1
- import re
2
- from typing import Dict, List, Tuple
3
- from jarvis.models.registry import PlatformRegistry
4
- from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, get_single_line_input, is_long_context
5
-
6
- class PlanGenerator:
7
- """Modification plan generator"""
8
-
9
- def _build_prompt(self, feature: str, related_files: List[Dict], additional_info: str) -> str:
10
- """Build prompt
11
-
12
- Args:
13
- feature: Feature description
14
- related_files: Related files list
15
- additional_info: User supplement information
16
-
17
- Returns:
18
- str: Complete prompt
19
- """
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
- prompt += f"{feature}\n\n"
22
-
23
- prompt += "Here are the relevant code file snippets:\n\n"
24
-
25
- for file in related_files:
26
- prompt += f"File: {file['file_path']}\n"
27
- for part in file["parts"]:
28
- prompt += f"<PART>\n{part}\n</PART>\n"
29
-
30
- prompt += "\nPlease provide detailed modifications needed to implement this feature. Include:\n"
31
- prompt += "1. Which files need to be modified\n"
32
- prompt += "2. How to modify each file, no explanation needed\n"
33
- prompt += "3. Don't assume other files or code exist, only generate modification plans based on provided file contents and description\n"
34
- prompt += "4. Don't implement features outside the requirement\n"
35
- prompt += "5. Output only one modification plan per file (can be multiple lines)\n"
36
- prompt += "6. Output format as follows:\n"
37
- prompt += "<PLAN>\n"
38
- prompt += "> path/to/file1\n"
39
- prompt += "modification plan\n"
40
- prompt += "</PLAN>\n"
41
- prompt += "<PLAN>\n"
42
- prompt += "> path/to/file2\n"
43
- prompt += "modification plan\n"
44
- prompt += "</PLAN>\n"
45
- if additional_info:
46
- prompt += f"# Additional information:\n{additional_info}\n"
47
-
48
- return prompt
49
-
50
-
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
88
-
89
- Args:
90
- feature: Feature description
91
- related_files: Related files list
92
-
93
- Returns:
94
- Tuple[str, Dict[str,str]]: Modification plan, return None if user cancels
95
- """
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
-
104
- while True:
105
- prompt = self._build_prompt(feature, file_info, additional_info)
106
- # Build prompt
107
- PrettyOutput.print("Start generating modification plan...", OutputType.PROGRESS)
108
-
109
- # Get modification plan
110
- raw_plan = PlatformRegistry.get_global_platform_registry().get_thinking_platform().chat_until_success(prompt)
111
- structed_plan = self._extract_code(raw_plan)
112
- if not structed_plan:
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):")
115
- if tmp == "__interrupt__" or prompt == "":
116
- return {}
117
- additional_info += tmp + "\n"
118
- continue
119
- user_input = get_single_line_input("Do you agree with this modification plan? (y/n) [y]").strip().lower() or 'y'
120
- if user_input == 'y' or user_input == '':
121
- return structed_plan
122
- elif user_input == 'n':
123
- # Get user feedback
124
- tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
125
- if prompt == "__interrupt__" or prompt == "":
126
- return {}
127
- additional_info += tmp + "\n"
128
- continue
129
-
130
-
131
- def _extract_code(self, response: str) -> Dict[str, str]:
132
- """Extract code from response
133
-
134
- Args:
135
- response: Model response content
136
-
137
- Returns:
138
- Dict[str, List[str]]: Code dictionary, key is file path, value is code snippet list
139
- """
140
- code_dict = {}
141
- for match in re.finditer(r'<PLAN>\n> (.+?)\n(.*?)\n</PLAN>', response, re.DOTALL):
142
- file_path = match.group(1)
143
- code_part = match.group(2)
144
- code_dict[file_path] = code_part
145
- return code_dict
File without changes