jarvis-ai-assistant 0.1.101__py3-none-any.whl → 0.1.103__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 +140 -140
- jarvis/jarvis_code_agent/code_agent.py +234 -0
- jarvis/{jarvis_coder → jarvis_code_agent}/file_select.py +16 -17
- jarvis/jarvis_code_agent/patch.py +118 -0
- jarvis/jarvis_code_agent/relevant_files.py +66 -0
- jarvis/jarvis_codebase/main.py +32 -29
- jarvis/jarvis_platform/main.py +5 -3
- jarvis/jarvis_rag/main.py +11 -15
- jarvis/jarvis_smart_shell/main.py +2 -2
- jarvis/models/ai8.py +1 -0
- jarvis/models/kimi.py +36 -30
- jarvis/models/ollama.py +17 -11
- jarvis/models/openai.py +15 -12
- jarvis/models/oyi.py +22 -7
- jarvis/models/registry.py +1 -25
- jarvis/tools/__init__.py +0 -6
- jarvis/tools/ask_codebase.py +99 -0
- jarvis/tools/ask_user.py +1 -9
- jarvis/tools/chdir.py +1 -1
- jarvis/tools/code_review.py +163 -0
- jarvis/tools/create_code_sub_agent.py +19 -45
- jarvis/tools/create_code_test_agent.py +115 -0
- jarvis/tools/create_ctags_agent.py +176 -0
- jarvis/tools/create_sub_agent.py +2 -2
- jarvis/tools/execute_shell.py +2 -2
- jarvis/tools/file_operation.py +2 -2
- jarvis/tools/find_in_codebase.py +108 -0
- jarvis/tools/git_commiter.py +68 -0
- jarvis/tools/methodology.py +3 -3
- jarvis/tools/rag.py +6 -3
- jarvis/tools/read_code.py +147 -0
- jarvis/tools/read_webpage.py +1 -1
- jarvis/tools/registry.py +92 -68
- jarvis/tools/search.py +8 -6
- jarvis/tools/select_code_files.py +4 -4
- jarvis/utils.py +270 -95
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/METADATA +9 -5
- jarvis_ai_assistant-0.1.103.dist-info/RECORD +51 -0
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/entry_points.txt +4 -2
- jarvis/jarvis_code_agent/main.py +0 -202
- jarvis/jarvis_coder/__init__.py +0 -0
- jarvis/jarvis_coder/git_utils.py +0 -123
- jarvis/jarvis_coder/main.py +0 -241
- jarvis/jarvis_coder/patch_handler.py +0 -340
- jarvis/jarvis_coder/plan_generator.py +0 -145
- jarvis/tools/execute_code_modification.py +0 -70
- jarvis/tools/find_files.py +0 -119
- jarvis/tools/generate_tool.py +0 -174
- jarvis/tools/thinker.py +0 -151
- jarvis_ai_assistant-0.1.101.dist-info/RECORD +0 -51
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import os
|
|
3
2
|
import re
|
|
4
3
|
from typing import Dict, List
|
|
@@ -64,25 +63,23 @@ def _get_file_completer(root_dir: str) -> Completer:
|
|
|
64
63
|
self.root_dir = root_dir
|
|
65
64
|
|
|
66
65
|
def get_completions(self, document, complete_event):
|
|
67
|
-
# Get the text of the current input
|
|
68
66
|
text = document.text_before_cursor
|
|
69
67
|
|
|
70
|
-
# If the input is empty, return all files in the root directory
|
|
71
68
|
if not text:
|
|
72
69
|
for path in self._list_files(""):
|
|
73
70
|
yield Completion(path, start_position=0)
|
|
74
71
|
return
|
|
75
72
|
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
# Generate fuzzy matching pattern
|
|
74
|
+
pattern = '.*'.join(map(re.escape, text))
|
|
75
|
+
try:
|
|
76
|
+
regex = re.compile(pattern, re.IGNORECASE)
|
|
77
|
+
except re.error:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
for path in self._list_files(""):
|
|
81
|
+
if regex.search(path):
|
|
82
|
+
yield Completion(path, start_position=-len(text))
|
|
86
83
|
|
|
87
84
|
def _list_files(self, current_dir: str) -> List[str]:
|
|
88
85
|
"""List all files in the specified directory (recursively)"""
|
|
@@ -93,7 +90,6 @@ def _get_file_completer(root_dir: str) -> Completer:
|
|
|
93
90
|
for filename in filenames:
|
|
94
91
|
full_path = os.path.join(root, filename)
|
|
95
92
|
rel_path = os.path.relpath(full_path, self.root_dir)
|
|
96
|
-
# Ignore .git directory and other hidden files
|
|
97
93
|
if not any(part.startswith('.') for part in rel_path.split(os.sep)):
|
|
98
94
|
files.append(rel_path)
|
|
99
95
|
|
|
@@ -133,10 +129,13 @@ def select_files(related_files: List[str], root_dir: str) -> List[str]:
|
|
|
133
129
|
"""Let the user select and supplement related files"""
|
|
134
130
|
PrettyOutput.section("Related files", OutputType.INFO)
|
|
135
131
|
|
|
132
|
+
output = ""
|
|
136
133
|
# Display found files
|
|
137
134
|
selected_files = list(related_files) # Default select all
|
|
138
135
|
for i, file in enumerate(related_files, 1):
|
|
139
|
-
|
|
136
|
+
output += f"[{i}] {file}\n"
|
|
137
|
+
|
|
138
|
+
PrettyOutput.print(output, OutputType.INFO)
|
|
140
139
|
|
|
141
140
|
# Ask the user if they need to adjust the file list
|
|
142
141
|
user_input = get_single_line_input("Do you need to adjust the file list? (y/n) [n]").strip().lower() or 'n'
|
|
@@ -160,7 +159,7 @@ def select_files(related_files: List[str], root_dir: str) -> List[str]:
|
|
|
160
159
|
)
|
|
161
160
|
|
|
162
161
|
while True:
|
|
163
|
-
PrettyOutput.print("
|
|
162
|
+
PrettyOutput.print("Please enter the file path to supplement (support Tab completion and *? wildcard, input empty line to end):", OutputType.INFO)
|
|
164
163
|
try:
|
|
165
164
|
file_path = session.prompt(">>> ").strip()
|
|
166
165
|
except KeyboardInterrupt:
|
|
@@ -177,7 +176,7 @@ def select_files(related_files: List[str], root_dir: str) -> List[str]:
|
|
|
177
176
|
continue
|
|
178
177
|
|
|
179
178
|
# Display matching files
|
|
180
|
-
PrettyOutput.print("
|
|
179
|
+
PrettyOutput.print("Found the following matching files:", OutputType.INFO)
|
|
181
180
|
for i, path in enumerate(matches, 1):
|
|
182
181
|
PrettyOutput.print(f"[{i}] {path}", OutputType.INFO)
|
|
183
182
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, Any, List, Tuple
|
|
3
|
+
import os
|
|
4
|
+
from jarvis.tools.git_commiter import GitCommitTool
|
|
5
|
+
from jarvis.tools.read_code import ReadCodeTool
|
|
6
|
+
from jarvis.utils import OutputType, PrettyOutput, has_uncommitted_changes, make_choice_input, user_confirm
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
|
|
10
|
+
"""Parse patches from string with format:
|
|
11
|
+
<PATCH>
|
|
12
|
+
> /path/to/file start_line,end_line
|
|
13
|
+
content_line1
|
|
14
|
+
content_line2
|
|
15
|
+
...
|
|
16
|
+
</PATCH>
|
|
17
|
+
"""
|
|
18
|
+
result = {}
|
|
19
|
+
patches = re.findall(r"<PATCH>(.*?)</PATCH>", patch_str, re.DOTALL)
|
|
20
|
+
|
|
21
|
+
for patch in patches:
|
|
22
|
+
lines = patch.strip().split('\n')
|
|
23
|
+
if not lines:
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
# Parse file path and line range
|
|
27
|
+
file_info = lines[0].strip()
|
|
28
|
+
if not file_info.startswith('>'):
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
# Extract file path and line range
|
|
32
|
+
match = re.match(r'>\s*([^\s]+)\s+(\d+),(\d+)', file_info)
|
|
33
|
+
if not match:
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
filepath = match.group(1).strip()
|
|
37
|
+
start_line = int(match.group(2))
|
|
38
|
+
end_line = int(match.group(3))
|
|
39
|
+
|
|
40
|
+
# Get content lines (skip the first line with file info)
|
|
41
|
+
content = '\n'.join(lines[1:])
|
|
42
|
+
|
|
43
|
+
if filepath not in result:
|
|
44
|
+
result[filepath] = []
|
|
45
|
+
|
|
46
|
+
# Store in result dictionary
|
|
47
|
+
result[filepath].append({
|
|
48
|
+
'start_line': start_line,
|
|
49
|
+
'end_line': end_line,
|
|
50
|
+
'content': content
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
# Sort patches by start line in reverse order to apply from bottom to top
|
|
54
|
+
for filepath in result:
|
|
55
|
+
result[filepath].sort(key=lambda x: x['start_line'], reverse=True)
|
|
56
|
+
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def apply_patch(output_str: str)->str:
|
|
61
|
+
"""Apply patches to files"""
|
|
62
|
+
patches = _parse_patch(output_str)
|
|
63
|
+
if not patches:
|
|
64
|
+
return ""
|
|
65
|
+
|
|
66
|
+
for filepath, patch_info in patches.items():
|
|
67
|
+
try:
|
|
68
|
+
# Check if file exists
|
|
69
|
+
if not os.path.exists(filepath):
|
|
70
|
+
PrettyOutput.print(f"File not found: {filepath}", OutputType.WARNING)
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
# Read original file content
|
|
74
|
+
lines = open(filepath, 'r', encoding='utf-8').readlines()
|
|
75
|
+
|
|
76
|
+
# Apply patch
|
|
77
|
+
for patch in patch_info:
|
|
78
|
+
start_line = patch['start_line']
|
|
79
|
+
end_line = patch['end_line']
|
|
80
|
+
new_content = patch['content'].splitlines(keepends=True)
|
|
81
|
+
|
|
82
|
+
if new_content and new_content[-1] and new_content[-1][-1] != '\n':
|
|
83
|
+
new_content[-1] += '\n'
|
|
84
|
+
# Validate line numbers
|
|
85
|
+
if start_line < 0 or end_line > len(lines) + 1 or start_line > end_line:
|
|
86
|
+
PrettyOutput.print(f"Invalid line range [{start_line}, {end_line}) for file: {filepath}", OutputType.WARNING)
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# Create new content
|
|
90
|
+
lines[start_line:end_line] = new_content
|
|
91
|
+
|
|
92
|
+
# Write back to file
|
|
93
|
+
open(filepath, 'w', encoding='utf-8').writelines(lines)
|
|
94
|
+
|
|
95
|
+
PrettyOutput.print(f"Applied patch to {filepath} successfully\n", OutputType.SUCCESS)
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
PrettyOutput.print(f"Error applying patch to {filepath}: {str(e)}", OutputType.ERROR)
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
if has_uncommitted_changes():
|
|
102
|
+
handle_commit_workflow()
|
|
103
|
+
return ""
|
|
104
|
+
|
|
105
|
+
def handle_commit_workflow()->bool:
|
|
106
|
+
"""Handle the git commit workflow and return the commit details.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
tuple[bool, str, str]: (continue_execution, commit_id, commit_message)
|
|
110
|
+
"""
|
|
111
|
+
if not user_confirm("Do you want to commit the code?", default=True):
|
|
112
|
+
os.system("git reset HEAD")
|
|
113
|
+
os.system("git checkout -- .")
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
git_commiter = GitCommitTool()
|
|
117
|
+
commit_result = git_commiter.execute({})
|
|
118
|
+
return commit_result["success"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from jarvis.agent import Agent
|
|
10
|
+
from jarvis.jarvis_code_agent.file_select import select_files
|
|
11
|
+
from jarvis.jarvis_codebase.main import CodeBase
|
|
12
|
+
from jarvis.models.registry import PlatformRegistry
|
|
13
|
+
from jarvis.tools.registry import ToolRegistry
|
|
14
|
+
from jarvis.utils import OutputType, PrettyOutput, is_disable_codebase
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def find_relevant_files(user_input: str, root_dir: str) -> List[str]:
|
|
18
|
+
try:
|
|
19
|
+
files_from_codebase = []
|
|
20
|
+
if not is_disable_codebase():
|
|
21
|
+
PrettyOutput.print("Find files from codebase...", OutputType.INFO)
|
|
22
|
+
codebase = CodeBase(root_dir)
|
|
23
|
+
files_from_codebase = codebase.search_similar(user_input)
|
|
24
|
+
|
|
25
|
+
PrettyOutput.print("Find files by agent...", OutputType.INFO)
|
|
26
|
+
find_file_tool_registry = ToolRegistry()
|
|
27
|
+
find_file_tool_registry.use_tools(["read_code", "execute_shell"])
|
|
28
|
+
find_file_agent = Agent(
|
|
29
|
+
system_prompt="""You are a file agent, you are responsible for finding files related to the user's requirement.
|
|
30
|
+
You can use `read_code` tool to read the code and analyze the code, and `execute_shell` tool to execute shell command(such as `grep/find/ls/git/ctags`) to find files.
|
|
31
|
+
|
|
32
|
+
IMPORTANT:
|
|
33
|
+
- Only provide the file path, do not provide any other information.
|
|
34
|
+
- If you can't find the file, please provide empty list.
|
|
35
|
+
- Don't modify the code, just find related files.
|
|
36
|
+
""",
|
|
37
|
+
name="FindFileAgent",
|
|
38
|
+
is_sub_agent=True,
|
|
39
|
+
tool_registry=find_file_tool_registry,
|
|
40
|
+
platform=PlatformRegistry().get_normal_platform(),
|
|
41
|
+
auto_complete=True,
|
|
42
|
+
summary_prompt="""Please provide the file path as this format(yaml list), if you can't find the file, please provide empty list:
|
|
43
|
+
<FILE_PATH>
|
|
44
|
+
- file_path1
|
|
45
|
+
- file_path2
|
|
46
|
+
</FILE_PATH>
|
|
47
|
+
""")
|
|
48
|
+
prompt = f"Find files related about '{user_input}'\n"
|
|
49
|
+
if files_from_codebase:
|
|
50
|
+
prompt += f"\n\nFiles maybe related: {files_from_codebase}\n\n Please read above files first"
|
|
51
|
+
output = find_file_agent.run(prompt)
|
|
52
|
+
|
|
53
|
+
rsp_from_agent = re.findall(r'<FILE_PATH>(.*?)</FILE_PATH>', output, re.DOTALL)
|
|
54
|
+
files_from_agent = []
|
|
55
|
+
if rsp_from_agent:
|
|
56
|
+
try:
|
|
57
|
+
files_from_agent = yaml.safe_load(rsp_from_agent[0])
|
|
58
|
+
except Exception as e:
|
|
59
|
+
files_from_agent = []
|
|
60
|
+
else:
|
|
61
|
+
files_from_agent = []
|
|
62
|
+
|
|
63
|
+
selected_files = select_files(files_from_agent, os.getcwd())
|
|
64
|
+
return selected_files
|
|
65
|
+
except Exception as e:
|
|
66
|
+
return []
|
jarvis/jarvis_codebase/main.py
CHANGED
|
@@ -59,7 +59,7 @@ class CodeBase:
|
|
|
59
59
|
"""Get the list of files in the git repository, excluding the .jarvis-codebase directory"""
|
|
60
60
|
files = os.popen("git ls-files").read().splitlines()
|
|
61
61
|
# Filter out files in the .jarvis-codebase directory
|
|
62
|
-
return [f for f in files if not f.startswith(".jarvis
|
|
62
|
+
return [f for f in files if not f.startswith(".jarvis")]
|
|
63
63
|
|
|
64
64
|
def is_text_file(self, file_path: str):
|
|
65
65
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
@@ -390,7 +390,7 @@ Content: {content}
|
|
|
390
390
|
self.git_file_list = self.get_git_file_list()
|
|
391
391
|
|
|
392
392
|
# Check file changes
|
|
393
|
-
PrettyOutput.print("
|
|
393
|
+
PrettyOutput.print("Check file changes...", output_type=OutputType.INFO)
|
|
394
394
|
changes_detected = False
|
|
395
395
|
new_files = []
|
|
396
396
|
modified_files = []
|
|
@@ -403,12 +403,13 @@ Content: {content}
|
|
|
403
403
|
deleted_files.append(file_path)
|
|
404
404
|
files_to_delete.append(file_path)
|
|
405
405
|
changes_detected = True
|
|
406
|
-
|
|
407
406
|
# Check new and modified files
|
|
408
|
-
|
|
407
|
+
from rich.progress import Progress
|
|
408
|
+
with Progress() as progress:
|
|
409
|
+
task = progress.add_task("Check file status", total=len(self.git_file_list))
|
|
409
410
|
for file_path in self.git_file_list:
|
|
410
411
|
if not os.path.exists(file_path) or not self.is_text_file(file_path):
|
|
411
|
-
|
|
412
|
+
progress.advance(task)
|
|
412
413
|
continue
|
|
413
414
|
|
|
414
415
|
try:
|
|
@@ -423,29 +424,28 @@ Content: {content}
|
|
|
423
424
|
except Exception as e:
|
|
424
425
|
PrettyOutput.print(f"Failed to check file {file_path}: {str(e)}",
|
|
425
426
|
output_type=OutputType.ERROR)
|
|
426
|
-
|
|
427
|
+
progress.advance(task)
|
|
427
428
|
|
|
428
429
|
# If changes are detected, display changes and ask the user
|
|
429
430
|
if changes_detected:
|
|
430
|
-
|
|
431
|
+
output_lines = ["Detected the following changes:"]
|
|
431
432
|
if new_files:
|
|
432
|
-
|
|
433
|
-
for f in new_files
|
|
434
|
-
PrettyOutput.print(f" {f}", output_type=OutputType.INFO)
|
|
433
|
+
output_lines.append("New files:")
|
|
434
|
+
output_lines.extend(f" {f}" for f in new_files)
|
|
435
435
|
if modified_files:
|
|
436
|
-
|
|
437
|
-
for f in modified_files
|
|
438
|
-
PrettyOutput.print(f" {f}", output_type=OutputType.INFO)
|
|
436
|
+
output_lines.append("Modified files:")
|
|
437
|
+
output_lines.extend(f" {f}" for f in modified_files)
|
|
439
438
|
if deleted_files:
|
|
440
|
-
|
|
441
|
-
for f in deleted_files
|
|
442
|
-
|
|
439
|
+
output_lines.append("Deleted files:")
|
|
440
|
+
output_lines.extend(f" {f}" for f in deleted_files)
|
|
441
|
+
|
|
442
|
+
PrettyOutput.print("\n".join(output_lines), output_type=OutputType.WARNING)
|
|
443
443
|
|
|
444
444
|
# If force is True, continue directly
|
|
445
445
|
if not force:
|
|
446
446
|
# Ask the user whether to continue
|
|
447
447
|
while True:
|
|
448
|
-
response = get_single_line_input("
|
|
448
|
+
response = get_single_line_input("Rebuild the index? [y/N]").lower().strip()
|
|
449
449
|
if response in ['y', 'yes']:
|
|
450
450
|
break
|
|
451
451
|
elif response in ['', 'n', 'no']:
|
|
@@ -487,7 +487,7 @@ Content: {content}
|
|
|
487
487
|
pbar.update(1)
|
|
488
488
|
|
|
489
489
|
if processed_files:
|
|
490
|
-
PrettyOutput.print("
|
|
490
|
+
PrettyOutput.print("Rebuilding the vector database...", output_type=OutputType.INFO)
|
|
491
491
|
self.gen_vector_db_from_cache()
|
|
492
492
|
PrettyOutput.print(f"Successfully generated the index for {len(processed_files)} files",
|
|
493
493
|
output_type=OutputType.SUCCESS)
|
|
@@ -693,6 +693,7 @@ Please output 3 expressions directly, separated by two line breaks, without numb
|
|
|
693
693
|
def search_similar(self, query: str, top_k: int = 30) -> List[str]:
|
|
694
694
|
"""Search related files"""
|
|
695
695
|
try:
|
|
696
|
+
self.generate_codebase()
|
|
696
697
|
if self.index is None:
|
|
697
698
|
return []
|
|
698
699
|
# Generate the query variants
|
|
@@ -714,8 +715,10 @@ Please output 3 expressions directly, separated by two line breaks, without numb
|
|
|
714
715
|
# Filter low-scoring results
|
|
715
716
|
initial_results = [(path, score, desc) for path, score, desc in initial_results if score >= 0.5]
|
|
716
717
|
|
|
717
|
-
|
|
718
|
-
|
|
718
|
+
message = "Found related files:\n"
|
|
719
|
+
for path, score, _ in initial_results:
|
|
720
|
+
message += f"File: {path} Similarity: {score:.3f}\n"
|
|
721
|
+
PrettyOutput.print(message.rstrip(), output_type=OutputType.INFO)
|
|
719
722
|
|
|
720
723
|
# Reorder the preliminary results
|
|
721
724
|
return self.pick_results(query, [path for path, _, _ in initial_results])
|
|
@@ -731,10 +734,10 @@ Please output 3 expressions directly, separated by two line breaks, without numb
|
|
|
731
734
|
PrettyOutput.print("No related files found", output_type=OutputType.WARNING)
|
|
732
735
|
return ""
|
|
733
736
|
|
|
734
|
-
|
|
737
|
+
message = "Found related files:\n"
|
|
735
738
|
for path in results:
|
|
736
|
-
|
|
737
|
-
|
|
739
|
+
message += f"File: {path}\n"
|
|
740
|
+
PrettyOutput.print(message.rstrip(), output_type=OutputType.SUCCESS)
|
|
738
741
|
|
|
739
742
|
prompt = f"""You are a code expert, please answer the user's question based on the following file information:
|
|
740
743
|
"""
|
|
@@ -847,7 +850,7 @@ def main():
|
|
|
847
850
|
if args.command == 'generate':
|
|
848
851
|
try:
|
|
849
852
|
codebase.generate_codebase(force=args.force)
|
|
850
|
-
PrettyOutput.print("
|
|
853
|
+
PrettyOutput.print("Codebase generation completed", output_type=OutputType.SUCCESS)
|
|
851
854
|
except Exception as e:
|
|
852
855
|
PrettyOutput.print(f"Error during codebase generation: {str(e)}", output_type=OutputType.ERROR)
|
|
853
856
|
|
|
@@ -857,15 +860,15 @@ def main():
|
|
|
857
860
|
PrettyOutput.print("No similar files found", output_type=OutputType.WARNING)
|
|
858
861
|
return
|
|
859
862
|
|
|
860
|
-
|
|
863
|
+
output = "Search Results:\n"
|
|
861
864
|
for path in results:
|
|
862
|
-
|
|
863
|
-
|
|
865
|
+
output += f"""{path}\n"""
|
|
866
|
+
PrettyOutput.print(output, output_type=OutputType.INFO)
|
|
864
867
|
|
|
865
868
|
elif args.command == 'ask':
|
|
866
869
|
response = codebase.ask_codebase(args.question, args.top_k)
|
|
867
|
-
|
|
868
|
-
PrettyOutput.print(
|
|
870
|
+
output = f"""Answer:\n{response}"""
|
|
871
|
+
PrettyOutput.print(output, output_type=OutputType.INFO)
|
|
869
872
|
|
|
870
873
|
else:
|
|
871
874
|
parser.print_help()
|
jarvis/jarvis_platform/main.py
CHANGED
|
@@ -21,13 +21,15 @@ def list_platforms():
|
|
|
21
21
|
# Print platform name
|
|
22
22
|
PrettyOutput.section(f"{platform_name}", OutputType.SUCCESS)
|
|
23
23
|
|
|
24
|
+
output = ""
|
|
24
25
|
# Print model list
|
|
25
26
|
if models:
|
|
26
27
|
for model_name, description in models:
|
|
27
28
|
if description:
|
|
28
|
-
|
|
29
|
+
output += f" • {model_name} - {description}\n"
|
|
29
30
|
else:
|
|
30
|
-
|
|
31
|
+
output += f" • {model_name}\n"
|
|
32
|
+
PrettyOutput.print(output, OutputType.SUCCESS)
|
|
31
33
|
else:
|
|
32
34
|
PrettyOutput.print(" • No available model information", OutputType.WARNING)
|
|
33
35
|
|
|
@@ -55,7 +57,7 @@ def chat_with_model(platform_name: str, model_name: str):
|
|
|
55
57
|
user_input = get_multiline_input("")
|
|
56
58
|
|
|
57
59
|
# Check if input is cancelled
|
|
58
|
-
if user_input
|
|
60
|
+
if user_input.strip() == "/bye":
|
|
59
61
|
PrettyOutput.print("Bye!", OutputType.SUCCESS)
|
|
60
62
|
break
|
|
61
63
|
|
jarvis/jarvis_rag/main.py
CHANGED
|
@@ -116,7 +116,7 @@ class PDFProcessor(FileProcessor):
|
|
|
116
116
|
text_parts = []
|
|
117
117
|
with fitz.open(file_path) as doc: # type: ignore
|
|
118
118
|
for page in doc:
|
|
119
|
-
text_parts.append(page.get_text())
|
|
119
|
+
text_parts.append(page.get_text()) # type: ignore
|
|
120
120
|
return "\n".join(text_parts)
|
|
121
121
|
|
|
122
122
|
class DocxProcessor(FileProcessor):
|
|
@@ -696,12 +696,10 @@ Content: {doc.content}
|
|
|
696
696
|
|
|
697
697
|
# Display found document fragments
|
|
698
698
|
for doc in results:
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
PrettyOutput.print(
|
|
703
|
-
content = doc.content.encode('utf-8', errors='replace').decode('utf-8')
|
|
704
|
-
PrettyOutput.print(content, output_type=OutputType.INFO)
|
|
699
|
+
output = f"""File: {doc.metadata['file_path']}\n"""
|
|
700
|
+
output += f"""Fragment {doc.metadata['chunk_index'] + 1}/{doc.metadata['total_chunks']}\n"""
|
|
701
|
+
output += f"""Content:\n{doc.content}\n"""
|
|
702
|
+
PrettyOutput.print(output, output_type=OutputType.INFO)
|
|
705
703
|
|
|
706
704
|
# Build base prompt
|
|
707
705
|
base_prompt = f"""Please answer the user's question based on the following document fragments. If the document content is not sufficient to answer the question completely, please clearly indicate.
|
|
@@ -791,12 +789,10 @@ def main():
|
|
|
791
789
|
return 1
|
|
792
790
|
|
|
793
791
|
for doc in results:
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
PrettyOutput.print(
|
|
798
|
-
content = doc.content.encode('utf-8', errors='replace').decode('utf-8')
|
|
799
|
-
PrettyOutput.print(content, output_type=OutputType.INFO)
|
|
792
|
+
output = f"""File: {doc.metadata['file_path']}\n"""
|
|
793
|
+
output += f"""Fragment {doc.metadata['chunk_index'] + 1}/{doc.metadata['total_chunks']}\n"""
|
|
794
|
+
output += f"""Content:\n{doc.content}\n"""
|
|
795
|
+
PrettyOutput.print(output, output_type=OutputType.INFO)
|
|
800
796
|
return 0
|
|
801
797
|
|
|
802
798
|
if args.ask:
|
|
@@ -807,8 +803,8 @@ def main():
|
|
|
807
803
|
return 1
|
|
808
804
|
|
|
809
805
|
# Display answer
|
|
810
|
-
|
|
811
|
-
PrettyOutput.print(
|
|
806
|
+
output = f"""Answer:\n{response}"""
|
|
807
|
+
PrettyOutput.print(output, output_type=OutputType.INFO)
|
|
812
808
|
return 0
|
|
813
809
|
|
|
814
810
|
PrettyOutput.print("Please specify operation parameters. Use -h to view help.", output_type=OutputType.WARNING)
|
|
@@ -13,7 +13,7 @@ from jarvis.utils import PrettyOutput, OutputType, init_env
|
|
|
13
13
|
def execute_command(command: str) -> None:
|
|
14
14
|
"""Show command and allow user to edit, then execute, Ctrl+C to cancel"""
|
|
15
15
|
try:
|
|
16
|
-
print("
|
|
16
|
+
print("Generated command (can be edited, press Enter to execute, Ctrl+C to cancel):")
|
|
17
17
|
# Pre-fill input line
|
|
18
18
|
readline.set_startup_hook(lambda: readline.insert_text(command))
|
|
19
19
|
try:
|
|
@@ -21,7 +21,7 @@ def execute_command(command: str) -> None:
|
|
|
21
21
|
if edited_command.strip(): # Ensure command is not empty
|
|
22
22
|
os.system(edited_command)
|
|
23
23
|
except KeyboardInterrupt:
|
|
24
|
-
print("
|
|
24
|
+
print("Execution cancelled")
|
|
25
25
|
finally:
|
|
26
26
|
readline.set_startup_hook() # Clear pre-filled
|
|
27
27
|
except Exception as e:
|
jarvis/models/ai8.py
CHANGED
jarvis/models/kimi.py
CHANGED
|
@@ -25,21 +25,24 @@ class KimiModel(BasePlatform):
|
|
|
25
25
|
self.chat_id = ""
|
|
26
26
|
self.api_key = os.getenv("KIMI_API_KEY")
|
|
27
27
|
if not self.api_key:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
message = (
|
|
29
|
+
"Need to set KIMI_API_KEY to use Jarvis. Please follow the steps below:\n"
|
|
30
|
+
"1. Get Kimi API Key:\n"
|
|
31
|
+
" • Visit Kimi AI platform: https://kimi.moonshot.cn\n"
|
|
32
|
+
" • Login to your account\n"
|
|
33
|
+
" • Open browser developer tools (F12 or right-click -> Inspect)\n"
|
|
34
|
+
" • Switch to the Network tab\n"
|
|
35
|
+
" • Send any message\n"
|
|
36
|
+
" • Find the Authorization header in the request\n"
|
|
37
|
+
" • Copy the token value (remove the 'Bearer ' prefix)\n"
|
|
38
|
+
"2. Set environment variable:\n"
|
|
39
|
+
" • Method 1: Create or edit ~/.jarvis/env file:\n"
|
|
40
|
+
" echo 'KIMI_API_KEY=your_key_here' > ~/.jarvis/env\n"
|
|
41
|
+
" • Method 2: Set environment variable directly:\n"
|
|
42
|
+
" export KIMI_API_KEY=your_key_here\n"
|
|
43
|
+
"After setting, run Jarvis again."
|
|
44
|
+
)
|
|
45
|
+
PrettyOutput.print(message, OutputType.INFO)
|
|
43
46
|
PrettyOutput.print("KIMI_API_KEY is not set", OutputType.WARNING)
|
|
44
47
|
self.auth_header = f"Bearer {self.api_key}"
|
|
45
48
|
self.chat_id = ""
|
|
@@ -308,42 +311,45 @@ class KimiModel(BasePlatform):
|
|
|
308
311
|
|
|
309
312
|
# 显示搜索结果摘要
|
|
310
313
|
if search_results and not self.suppress_output:
|
|
311
|
-
|
|
314
|
+
output = ["搜索结果:"]
|
|
312
315
|
for result in search_results:
|
|
313
|
-
|
|
316
|
+
output.append(f"- {result['title']}")
|
|
314
317
|
if result['date']:
|
|
315
|
-
|
|
316
|
-
|
|
318
|
+
output.append(f" 日期: {result['date']}")
|
|
319
|
+
output.append(f" 来源: {result['site_name']}")
|
|
317
320
|
if result['snippet']:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
+
output.append(f" 摘要: {result['snippet']}")
|
|
322
|
+
output.append(f" 链接: {result['url']}")
|
|
323
|
+
output.append("")
|
|
324
|
+
PrettyOutput.print("\n".join(output), OutputType.PROGRESS)
|
|
321
325
|
|
|
322
326
|
# 显示引用来源
|
|
323
327
|
if ref_sources and not self.suppress_output:
|
|
324
|
-
|
|
328
|
+
output = ["引用来源:"]
|
|
325
329
|
for source in ref_sources:
|
|
326
|
-
|
|
327
|
-
|
|
330
|
+
output.append(f"- [{source['ref_id']}] {source['title']} ({source['source']})")
|
|
331
|
+
output.append(f" 链接: {source['url']}")
|
|
328
332
|
if source['abstract']:
|
|
329
|
-
|
|
333
|
+
output.append(f" 摘要: {source['abstract']}")
|
|
330
334
|
|
|
331
335
|
# 显示相关段落
|
|
332
336
|
if source['rag_segments']:
|
|
333
|
-
|
|
337
|
+
output.append(" 相关段落:")
|
|
334
338
|
for segment in source['rag_segments']:
|
|
335
339
|
text = segment.get('text', '').replace('\n', ' ').strip()
|
|
336
340
|
if text:
|
|
337
|
-
|
|
341
|
+
output.append(f" - {text}")
|
|
338
342
|
|
|
339
343
|
# 显示原文引用
|
|
340
344
|
origin = source['origin']
|
|
341
345
|
if origin:
|
|
342
346
|
text = origin.get('text', '')
|
|
343
347
|
if text:
|
|
344
|
-
|
|
348
|
+
output.append(f" 原文: {text}")
|
|
345
349
|
|
|
346
|
-
|
|
350
|
+
output.append("")
|
|
351
|
+
|
|
352
|
+
PrettyOutput.print("\n".join(output), OutputType.PROGRESS)
|
|
347
353
|
|
|
348
354
|
PrettyOutput.print(full_response, OutputType.RESULT)
|
|
349
355
|
|