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.

Files changed (54) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +140 -140
  3. jarvis/jarvis_code_agent/code_agent.py +234 -0
  4. jarvis/{jarvis_coder → jarvis_code_agent}/file_select.py +16 -17
  5. jarvis/jarvis_code_agent/patch.py +118 -0
  6. jarvis/jarvis_code_agent/relevant_files.py +66 -0
  7. jarvis/jarvis_codebase/main.py +32 -29
  8. jarvis/jarvis_platform/main.py +5 -3
  9. jarvis/jarvis_rag/main.py +11 -15
  10. jarvis/jarvis_smart_shell/main.py +2 -2
  11. jarvis/models/ai8.py +1 -0
  12. jarvis/models/kimi.py +36 -30
  13. jarvis/models/ollama.py +17 -11
  14. jarvis/models/openai.py +15 -12
  15. jarvis/models/oyi.py +22 -7
  16. jarvis/models/registry.py +1 -25
  17. jarvis/tools/__init__.py +0 -6
  18. jarvis/tools/ask_codebase.py +99 -0
  19. jarvis/tools/ask_user.py +1 -9
  20. jarvis/tools/chdir.py +1 -1
  21. jarvis/tools/code_review.py +163 -0
  22. jarvis/tools/create_code_sub_agent.py +19 -45
  23. jarvis/tools/create_code_test_agent.py +115 -0
  24. jarvis/tools/create_ctags_agent.py +176 -0
  25. jarvis/tools/create_sub_agent.py +2 -2
  26. jarvis/tools/execute_shell.py +2 -2
  27. jarvis/tools/file_operation.py +2 -2
  28. jarvis/tools/find_in_codebase.py +108 -0
  29. jarvis/tools/git_commiter.py +68 -0
  30. jarvis/tools/methodology.py +3 -3
  31. jarvis/tools/rag.py +6 -3
  32. jarvis/tools/read_code.py +147 -0
  33. jarvis/tools/read_webpage.py +1 -1
  34. jarvis/tools/registry.py +92 -68
  35. jarvis/tools/search.py +8 -6
  36. jarvis/tools/select_code_files.py +4 -4
  37. jarvis/utils.py +270 -95
  38. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/METADATA +9 -5
  39. jarvis_ai_assistant-0.1.103.dist-info/RECORD +51 -0
  40. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/entry_points.txt +4 -2
  41. jarvis/jarvis_code_agent/main.py +0 -202
  42. jarvis/jarvis_coder/__init__.py +0 -0
  43. jarvis/jarvis_coder/git_utils.py +0 -123
  44. jarvis/jarvis_coder/main.py +0 -241
  45. jarvis/jarvis_coder/patch_handler.py +0 -340
  46. jarvis/jarvis_coder/plan_generator.py +0 -145
  47. jarvis/tools/execute_code_modification.py +0 -70
  48. jarvis/tools/find_files.py +0 -119
  49. jarvis/tools/generate_tool.py +0 -174
  50. jarvis/tools/thinker.py +0 -151
  51. jarvis_ai_assistant-0.1.101.dist-info/RECORD +0 -51
  52. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/LICENSE +0 -0
  53. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/WHEEL +0 -0
  54. {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
- # Get the current directory and partial file name
77
- current_dir = os.path.dirname(text)
78
- file_prefix = os.path.basename(text)
79
-
80
- # List matching files
81
- search_dir = os.path.join(self.root_dir, current_dir) if current_dir else self.root_dir
82
- if os.path.isdir(search_dir):
83
- for path in self._list_files(current_dir):
84
- if path.startswith(text):
85
- yield Completion(path, start_position=-len(text))
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
- PrettyOutput.print(f"[{i}] {file}", OutputType.INFO)
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("\nPlease enter the file path to supplement (support Tab completion and *? wildcard, input empty line to end):", OutputType.INFO)
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("\nFound the following matching files:", OutputType.INFO)
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 []
@@ -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("\nCheck file changes...", output_type=OutputType.INFO)
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
- with tqdm(total=len(self.git_file_list), desc="Check file status") as pbar:
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
- pbar.update(1)
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
- pbar.update(1)
427
+ progress.advance(task)
427
428
 
428
429
  # If changes are detected, display changes and ask the user
429
430
  if changes_detected:
430
- PrettyOutput.print("\nDetected the following changes:", output_type=OutputType.WARNING)
431
+ output_lines = ["Detected the following changes:"]
431
432
  if new_files:
432
- PrettyOutput.print("\nNew files:", output_type=OutputType.INFO)
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
- PrettyOutput.print("\nModified files:", output_type=OutputType.INFO)
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
- PrettyOutput.print("\nDeleted files:", output_type=OutputType.INFO)
441
- for f in deleted_files:
442
- PrettyOutput.print(f" {f}", output_type=OutputType.INFO)
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("\nRebuild the index? [y/N]").lower().strip()
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("\nRebuilding the vector database...", output_type=OutputType.INFO)
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
- for path, score, desc in initial_results:
718
- PrettyOutput.print(f"File: {path} Similarity: {score:.3f}", output_type=OutputType.INFO)
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
- PrettyOutput.print(f"Found related files: ", output_type=OutputType.SUCCESS)
737
+ message = "Found related files:\n"
735
738
  for path in results:
736
- PrettyOutput.print(f"File: {path}",
737
- output_type=OutputType.INFO)
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("\nCodebase generation completed", output_type=OutputType.SUCCESS)
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
- PrettyOutput.print("\nSearch Results:", output_type=OutputType.INFO)
863
+ output = "Search Results:\n"
861
864
  for path in results:
862
- PrettyOutput.print("\n" + "="*50, output_type=OutputType.INFO)
863
- PrettyOutput.print(f"File: {path}", output_type=OutputType.INFO)
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
- PrettyOutput.print("\nAnswer:", output_type=OutputType.INFO)
868
- PrettyOutput.print(response, output_type=OutputType.INFO)
870
+ output = f"""Answer:\n{response}"""
871
+ PrettyOutput.print(output, output_type=OutputType.INFO)
869
872
 
870
873
  else:
871
874
  parser.print_help()
@@ -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
- PrettyOutput.print(f" • {model_name} - {description}", OutputType.SUCCESS)
29
+ output += f" • {model_name} - {description}\n"
29
30
  else:
30
- PrettyOutput.print(f" • {model_name}", OutputType.SUCCESS)
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 == "__interrupt__" or user_input.strip() == "/bye":
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
- PrettyOutput.print(f"File: {doc.metadata['file_path']}", output_type=OutputType.INFO)
700
- PrettyOutput.print(f"Fragment {doc.metadata['chunk_index'] + 1}/{doc.metadata['total_chunks']}",
701
- output_type=OutputType.INFO)
702
- PrettyOutput.print("\nContent:", output_type=OutputType.INFO)
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
- PrettyOutput.print(f"\nFile: {doc.metadata['file_path']}", output_type=OutputType.INFO)
795
- PrettyOutput.print(f"Fragment {doc.metadata['chunk_index'] + 1}/{doc.metadata['total_chunks']}",
796
- output_type=OutputType.INFO)
797
- PrettyOutput.print("\nContent:", output_type=OutputType.INFO)
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
- PrettyOutput.print("\nAnswer:", output_type=OutputType.INFO)
811
- PrettyOutput.print(response, output_type=OutputType.INFO)
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("\nGenerated command (can be edited, press Enter to execute, Ctrl+C to cancel):")
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("\nExecution cancelled")
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
@@ -14,6 +14,7 @@ class AI8Model(BasePlatform):
14
14
 
15
15
  def get_model_list(self) -> List[Tuple[str, str]]:
16
16
  """获取模型列表"""
17
+ self.get_available_models()
17
18
  return [(name,info['desc']) for name,info in self.models.items()]
18
19
 
19
20
  def __init__(self):
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
- PrettyOutput.print("\nNeed to set KIMI_API_KEY to use Jarvis. Please follow the steps below:", OutputType.INFO)
29
- PrettyOutput.print("\n1. Get Kimi API Key:", OutputType.INFO)
30
- PrettyOutput.print(" • Visit Kimi AI platform: https://kimi.moonshot.cn", OutputType.INFO)
31
- PrettyOutput.print(" • Login to your account", OutputType.INFO)
32
- PrettyOutput.print(" • Open browser developer tools (F12 or right-click -> Inspect)", OutputType.INFO)
33
- PrettyOutput.print(" • Switch to the Network tab", OutputType.INFO)
34
- PrettyOutput.print(" • Send any message", OutputType.INFO)
35
- PrettyOutput.print(" • Find the Authorization header in the request", OutputType.INFO)
36
- PrettyOutput.print(" • Copy the token value (remove the 'Bearer ' prefix)", OutputType.INFO)
37
- PrettyOutput.print("\n2. Set environment variable:", OutputType.INFO)
38
- PrettyOutput.print(" • Method 1: Create or edit ~/.jarvis/env file:", OutputType.INFO)
39
- PrettyOutput.print(" echo 'KIMI_API_KEY=your_key_here' > ~/.jarvis/env", OutputType.INFO)
40
- PrettyOutput.print("\n Method 2: Set environment variable directly:", OutputType.INFO)
41
- PrettyOutput.print(" export KIMI_API_KEY=your_key_here", OutputType.INFO)
42
- PrettyOutput.print("\nAfter setting, run Jarvis again.", OutputType.INFO)
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
- PrettyOutput.print("\n搜索结果:", OutputType.PROGRESS)
314
+ output = ["搜索结果:"]
312
315
  for result in search_results:
313
- PrettyOutput.print(f"- {result['title']}", OutputType.PROGRESS)
316
+ output.append(f"- {result['title']}")
314
317
  if result['date']:
315
- PrettyOutput.print(f" 日期: {result['date']}", OutputType.PROGRESS)
316
- PrettyOutput.print(f" 来源: {result['site_name']}", OutputType.PROGRESS)
318
+ output.append(f" 日期: {result['date']}")
319
+ output.append(f" 来源: {result['site_name']}")
317
320
  if result['snippet']:
318
- PrettyOutput.print(f" 摘要: {result['snippet']}", OutputType.PROGRESS)
319
- PrettyOutput.print(f" 链接: {result['url']}", OutputType.PROGRESS)
320
- PrettyOutput.print("", OutputType.PROGRESS)
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
- PrettyOutput.print("\n引用来源:", OutputType.PROGRESS)
328
+ output = ["引用来源:"]
325
329
  for source in ref_sources:
326
- PrettyOutput.print(f"- [{source['ref_id']}] {source['title']} ({source['source']})", OutputType.PROGRESS)
327
- PrettyOutput.print(f" 链接: {source['url']}", OutputType.PROGRESS)
330
+ output.append(f"- [{source['ref_id']}] {source['title']} ({source['source']})")
331
+ output.append(f" 链接: {source['url']}")
328
332
  if source['abstract']:
329
- PrettyOutput.print(f" 摘要: {source['abstract']}", OutputType.PROGRESS)
333
+ output.append(f" 摘要: {source['abstract']}")
330
334
 
331
335
  # 显示相关段落
332
336
  if source['rag_segments']:
333
- PrettyOutput.print(" 相关段落:", OutputType.PROGRESS)
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
- PrettyOutput.print(f" - {text}", OutputType.PROGRESS)
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
- PrettyOutput.print(f" 原文: {text}", OutputType.PROGRESS)
348
+ output.append(f" 原文: {text}")
345
349
 
346
- PrettyOutput.print("", OutputType.PROGRESS)
350
+ output.append("")
351
+
352
+ PrettyOutput.print("\n".join(output), OutputType.PROGRESS)
347
353
 
348
354
  PrettyOutput.print(full_response, OutputType.RESULT)
349
355