jarvis-ai-assistant 0.1.126__py3-none-any.whl → 0.1.128__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 (28) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +105 -87
  3. jarvis/jarvis_code_agent/code_agent.py +21 -10
  4. jarvis/jarvis_code_agent/patch.py +139 -112
  5. jarvis/jarvis_codebase/main.py +240 -213
  6. jarvis/jarvis_dev/main.py +4 -3
  7. jarvis/jarvis_platform/base.py +6 -5
  8. jarvis/jarvis_platform_manager/main.py +1 -1
  9. jarvis/jarvis_rag/main.py +250 -186
  10. jarvis/jarvis_smart_shell/main.py +0 -1
  11. jarvis/jarvis_tools/ask_codebase.py +4 -3
  12. jarvis/jarvis_tools/chdir.py +22 -22
  13. jarvis/jarvis_tools/code_review.py +38 -33
  14. jarvis/jarvis_tools/execute_shell.py +0 -3
  15. jarvis/jarvis_tools/file_operation.py +56 -55
  16. jarvis/jarvis_tools/git_commiter.py +60 -50
  17. jarvis/jarvis_tools/read_webpage.py +50 -30
  18. jarvis/jarvis_tools/registry.py +40 -53
  19. jarvis/jarvis_tools/search_web.py +61 -36
  20. jarvis/jarvis_tools/tool_generator.py +35 -21
  21. jarvis/jarvis_utils/methodology.py +72 -54
  22. jarvis/jarvis_utils/output.py +1 -0
  23. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.128.dist-info}/METADATA +1 -1
  24. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.128.dist-info}/RECORD +28 -28
  25. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.128.dist-info}/LICENSE +0 -0
  26. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.128.dist-info}/WHEEL +0 -0
  27. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.128.dist-info}/entry_points.txt +0 -0
  28. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.128.dist-info}/top_level.txt +0 -0
@@ -16,31 +16,31 @@ class ChdirTool:
16
16
  }
17
17
 
18
18
  def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
19
- """Execute directory change operation with comprehensive error handling.
19
+ """执行目录切换操作,并提供全面的错误处理。
20
20
 
21
- Args:
22
- args: Dictionary containing 'path' key with target directory path
21
+ 参数:
22
+ args: 包含 'path' 键的字典,目标目录路径
23
23
 
24
- Returns:
25
- Dictionary containing:
26
- - success: Boolean indicating operation status
27
- - stdout: Success message or empty string
28
- - stderr: Error message or empty string
24
+ 返回:
25
+ 字典,包含以下内容:
26
+ - success: 布尔值,表示操作状态
27
+ - stdout: 成功消息或空字符串
28
+ - stderr: 错误消息或空字符串
29
29
 
30
- Raises:
31
- Handles and returns appropriate error messages for:
32
- - Non-existent paths
33
- - Non-directory paths
34
- - Permission errors
35
- - Generic exceptions
30
+ 异常处理:
31
+ 处理并返回适当的错误消息:
32
+ - 不存在的路径
33
+ - 非目录路径
34
+ - 权限错误
35
+ - 其他通用异常
36
36
  """
37
- # Main execution block with comprehensive error handling
37
+ # 主执行块,包含全面的错误处理
38
38
  try:
39
- # Normalize and expand the input path (handles ~ and relative paths)
39
+ # 规范化并展开输入路径(处理 ~ 和相对路径)
40
40
  path = os.path.expanduser(args["path"].strip())
41
41
  path = os.path.abspath(path)
42
42
 
43
- # Validate that the target path exists
43
+ # 验证目标路径是否存在
44
44
  if not os.path.exists(path):
45
45
  return {
46
46
  "success": False,
@@ -48,7 +48,7 @@ class ChdirTool:
48
48
  "stderr": f"目录不存在: {path}"
49
49
  }
50
50
 
51
- # Ensure the path points to a directory, not a file
51
+ # 确保路径指向的是目录,而不是文件
52
52
  if not os.path.isdir(path):
53
53
  return {
54
54
  "success": False,
@@ -56,7 +56,7 @@ class ChdirTool:
56
56
  "stderr": f"路径不是目录: {path}"
57
57
  }
58
58
 
59
- # Capture current directory and attempt to change to new path
59
+ # 获取当前目录并尝试切换到新路径
60
60
  old_path = os.getcwd()
61
61
  os.chdir(path)
62
62
 
@@ -66,17 +66,17 @@ class ChdirTool:
66
66
  "stderr": ""
67
67
  }
68
68
 
69
- # Handle cases where user lacks directory access permissions
69
+ # 处理用户没有目录访问权限的情况
70
70
  except PermissionError:
71
71
  return {
72
72
  "success": False,
73
73
  "stdout": "",
74
74
  "stderr": f"无权限访问目录: {path}"
75
75
  }
76
- # Catch-all for any other unexpected errors during directory change
76
+ # 捕获在目录切换过程中可能出现的其他意外错误
77
77
  except Exception as e:
78
78
  return {
79
79
  "success": False,
80
80
  "stdout": "",
81
81
  "stderr": f"切换目录失败: {str(e)}"
82
- }
82
+ }
@@ -1,5 +1,7 @@
1
1
  from typing import Dict, Any
2
2
  import subprocess
3
+
4
+ from yaspin import yaspin
3
5
  from jarvis.jarvis_platform.registry import PlatformRegistry
4
6
  from jarvis.jarvis_tools.registry import ToolRegistry
5
7
  from jarvis.jarvis_agent import Agent
@@ -41,44 +43,47 @@ class CodeReviewTool:
41
43
  review_type = args.get("review_type", "current").strip()
42
44
 
43
45
  # Build git diff command based on review type
44
- if review_type == "commit":
45
- if "commit_sha" not in args:
46
- return {
47
- "success": False,
48
- "stdout": {},
49
- "stderr": "commit_sha is required for commit review type"
50
- }
51
- commit_sha = args["commit_sha"].strip()
52
- diff_cmd = f"git show {commit_sha} | cat -"
53
- elif review_type == "range":
54
- if "start_commit" not in args or "end_commit" not in args:
55
- return {
56
- "success": False,
57
- "stdout": {},
58
- "stderr": "start_commit and end_commit are required for range review type"
59
- }
60
- start_commit = args["start_commit"].strip()
61
- end_commit = args["end_commit"].strip()
62
- diff_cmd = f"git diff {start_commit}..{end_commit} | cat -"
63
- else: # current changes
64
- diff_cmd = "git diff HEAD | cat -"
46
+ with yaspin(text="正在获取代码变更...", color="cyan") as spinner:
47
+ if review_type == "commit":
48
+ if "commit_sha" not in args:
49
+ return {
50
+ "success": False,
51
+ "stdout": {},
52
+ "stderr": "commit_sha is required for commit review type"
53
+ }
54
+ commit_sha = args["commit_sha"].strip()
55
+ diff_cmd = f"git show {commit_sha} | cat -"
56
+ elif review_type == "range":
57
+ if "start_commit" not in args or "end_commit" not in args:
58
+ return {
59
+ "success": False,
60
+ "stdout": {},
61
+ "stderr": "start_commit and end_commit are required for range review type"
62
+ }
63
+ start_commit = args["start_commit"].strip()
64
+ end_commit = args["end_commit"].strip()
65
+ diff_cmd = f"git diff {start_commit}..{end_commit} | cat -"
66
+ else: # current changes
67
+ diff_cmd = "git diff HEAD | cat -"
65
68
 
66
- # Execute git diff command
67
- try:
68
- diff_output = subprocess.check_output(diff_cmd, shell=True, text=True)
69
- if not diff_output:
69
+ # Execute git diff command
70
+ try:
71
+ diff_output = subprocess.check_output(diff_cmd, shell=True, text=True)
72
+ if not diff_output:
73
+ return {
74
+ "success": False,
75
+ "stdout": {},
76
+ "stderr": "No changes to review"
77
+ }
78
+ PrettyOutput.print(diff_output, OutputType.CODE, lang="diff")
79
+ except subprocess.CalledProcessError as e:
70
80
  return {
71
81
  "success": False,
72
82
  "stdout": {},
73
- "stderr": "No changes to review"
83
+ "stderr": f"Failed to get diff: {str(e)}"
74
84
  }
75
- PrettyOutput.print(diff_output, OutputType.CODE, lang="diff")
76
- except subprocess.CalledProcessError as e:
77
- return {
78
- "success": False,
79
- "stdout": {},
80
- "stderr": f"Failed to get diff: {str(e)}"
81
- }
85
+ spinner.text = "代码变更获取完成"
86
+ spinner.ok("✅")
82
87
 
83
88
  system_prompt = """You are an autonomous code review expert with a tragic past. Perform in-depth analysis with the vigilance born from painful experience:
84
89
 
@@ -73,9 +73,6 @@ class ShellTool:
73
73
  # Use script command to capture both stdout and stderr
74
74
  tee_command = f"script -q -c '{escaped_command}' {output_file}"
75
75
 
76
- # Log command execution
77
- PrettyOutput.print(f"执行命令: {command}", OutputType.INFO)
78
-
79
76
  # Execute command and capture return code
80
77
  return_code = os.system(tee_command)
81
78
 
@@ -1,6 +1,8 @@
1
1
  from typing import Dict, Any
2
2
  import os
3
3
 
4
+ from yaspin import yaspin
5
+
4
6
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
5
7
 
6
8
 
@@ -37,67 +39,66 @@ class FileOperationTool:
37
39
  """Handle operations for a single file"""
38
40
  try:
39
41
  abs_path = os.path.abspath(filepath)
40
- PrettyOutput.print(f"文件操作: {operation} - {abs_path}", OutputType.INFO)
41
-
42
42
  if operation == "read":
43
- if not os.path.exists(abs_path):
44
- PrettyOutput.print(f"文件不存在: {abs_path}", OutputType.WARNING)
45
- return {
46
- "success": False,
47
- "stdout": "",
48
- "stderr": f"文件不存在: {abs_path}"
49
- }
43
+ with yaspin(text=f"正在读取文件: {abs_path}...", color="cyan") as spinner:
44
+ if not os.path.exists(abs_path):
45
+ return {
46
+ "success": False,
47
+ "stdout": "",
48
+ "stderr": f"文件不存在: {abs_path}"
49
+ }
50
+
51
+ if os.path.getsize(abs_path) > 10 * 1024 * 1024: # 10MB
52
+ return {
53
+ "success": False,
54
+ "stdout": "",
55
+ "stderr": "File too large (>10MB)"
56
+ }
57
+
58
+ with open(abs_path, 'r', encoding='utf-8') as f:
59
+ lines = f.readlines()
50
60
 
51
- if os.path.getsize(abs_path) > 10 * 1024 * 1024: # 10MB
52
- PrettyOutput.print(f"文件太大: {abs_path}", OutputType.WARNING)
53
- return {
54
- "success": False,
55
- "stdout": "",
56
- "stderr": "File too large (>10MB)"
57
- }
58
61
 
59
- with open(abs_path, 'r', encoding='utf-8') as f:
60
- lines = f.readlines()
61
-
62
- # Handle line range
63
- total_lines = len(lines)
64
- start_line = start_line if start_line >= 0 else total_lines + start_line + 1
65
- end_line = end_line if end_line >= 0 else total_lines + end_line + 1
66
- start_line = max(1, min(start_line, total_lines))
67
- end_line = max(1, min(end_line, total_lines))
68
- if end_line == -1:
69
- end_line = total_lines
70
-
71
- if start_line > end_line:
72
- error_msg = f"无效的行范围 [{start_line, end_line}] (文件总行数: {total_lines})"
73
- PrettyOutput.print(error_msg, OutputType.WARNING)
62
+ total_lines = len(lines)
63
+ start_line = start_line if start_line >= 0 else total_lines + start_line + 1
64
+ end_line = end_line if end_line >= 0 else total_lines + end_line + 1
65
+ start_line = max(1, min(start_line, total_lines))
66
+ end_line = max(1, min(end_line, total_lines))
67
+ if end_line == -1:
68
+ end_line = total_lines
69
+
70
+ if start_line > end_line:
71
+ spinner.text = "无效的行范围"
72
+ spinner.fail("❌")
73
+ error_msg = f"无效的行范围 [{start_line, end_line}] (文件总行数: {total_lines})"
74
+ return {
75
+ "success": False,
76
+ "stdout": "",
77
+ "stderr": error_msg
78
+ }
79
+
80
+ content = "".join(lines[start_line - 1:end_line])
81
+ output = f"\n文件: {abs_path}\n行: [{start_line}-{end_line}]\n{content}" + "\n\n" + "="*80 + "\n\n"
82
+
83
+ spinner.text = f"文件读取完成: {abs_path}"
84
+ spinner.ok("✅")
74
85
  return {
75
- "success": False,
76
- "stdout": "",
77
- "stderr": error_msg
86
+ "success": True,
87
+ "stdout": output,
88
+ "stderr": ""
78
89
  }
79
-
80
- content = "".join(lines[start_line - 1:end_line])
81
- output = f"\文件: {abs_path}\行: [{start_line}-{end_line}]\n{content}" + "\n\n" + "="*80 + "\n\n"
82
-
83
- return {
84
- "success": True,
85
- "stdout": output,
86
- "stderr": ""
87
- }
88
-
89
90
  elif operation == "write":
90
- os.makedirs(os.path.dirname(os.path.abspath(abs_path)), exist_ok=True)
91
- with open(abs_path, 'w', encoding='utf-8') as f:
92
- f.write(content)
93
-
94
- PrettyOutput.print(f"写入文件: {abs_path}", OutputType.INFO)
95
- return {
96
- "success": True,
97
- "stdout": f"Successfully wrote content to {abs_path}",
98
- "stderr": ""
99
- }
100
- PrettyOutput.print(f"未知操作: {operation}", OutputType.WARNING)
91
+ with yaspin(text=f"正在写入文件: {abs_path}...", color="cyan") as spinner:
92
+ os.makedirs(os.path.dirname(os.path.abspath(abs_path)), exist_ok=True)
93
+ with open(abs_path, 'w', encoding='utf-8') as f:
94
+ f.write(content)
95
+ spinner.text = f"文件写入完成: {abs_path}"
96
+ spinner.ok("✅")
97
+ return {
98
+ "success": True,
99
+ "stdout": f"Successfully wrote content to {abs_path}",
100
+ "stderr": ""
101
+ }
101
102
  return {
102
103
  "success": False,
103
104
  "stdout": "",
@@ -4,6 +4,7 @@ import subprocess
4
4
  from typing import Dict, Any
5
5
  import tempfile
6
6
  import yaml
7
+ from yaspin import yaspin
7
8
  from jarvis.jarvis_platform.registry import PlatformRegistry
8
9
  import sys
9
10
  import argparse
@@ -54,59 +55,68 @@ class GitCommitTool:
54
55
  PrettyOutput.print("没有未提交的更改", OutputType.SUCCESS)
55
56
  return {"success": True, "stdout": "No changes to commit", "stderr": ""}
56
57
 
57
- PrettyOutput.print("准备添加文件到提交...", OutputType.SYSTEM)
58
- subprocess.Popen(
59
- ["git", "add", "."],
60
- stdout=subprocess.DEVNULL,
61
- stderr=subprocess.DEVNULL
62
- ).wait()
63
-
64
- PrettyOutput.print("获取差异...", OutputType.SYSTEM)
65
- process = subprocess.Popen(
66
- ["git", "diff", "--cached", "--exit-code"],
67
- stdout=subprocess.PIPE,
68
- stderr=subprocess.PIPE
69
- )
70
- diff = process.communicate()[0].decode()
71
- PrettyOutput.print(diff, OutputType.CODE, lang="diff")
72
-
73
- prompt = f'''根据以下规则生成提交信息:
74
- 提交信息应使用{args.get('lang', '中文')}书写
75
- # 必需结构
76
- 必须使用以下格式:
77
- <COMMIT_MESSAGE>
78
- <类型>(<范围>): <主题>
79
- 使用祈使语气描述变更内容
80
- </COMMIT_MESSAGE>
81
- # 格式规则
82
- 1. 类型: fix, feat, docs, style, refactor, test, chore
83
- 2. 范围表示模块 (例如: auth, database)
84
- 3. 主题行 <= 72个字符,不以句号结尾
85
- 4. 正文使用现在时态解释每个变更的内容和原因
86
- 5. 不要遗漏任何变更
87
- # 分析材料
88
- {diff}
89
- '''
90
-
91
- PrettyOutput.print("生成提交消息...", OutputType.SYSTEM)
92
- platform = PlatformRegistry().get_codegen_platform()
93
- platform.set_suppress_output(True)
94
- commit_message = platform.chat_until_success(prompt)
95
- commit_message = self._extract_commit_message(commit_message)
96
-
97
- # 使用临时文件处理提交消息
98
- with tempfile.NamedTemporaryFile(mode='w', delete=True) as tmp_file:
99
- tmp_file.write(commit_message)
100
- tmp_file.flush() # 确保内容写入文件
101
- commit_cmd = ["git", "commit", "-F", tmp_file.name]
102
- PrettyOutput.print("提交...", OutputType.INFO)
58
+ with yaspin(text="正在初始化提交流程...", color="cyan") as spinner:
59
+ # 添加文件
60
+ spinner.text = "正在添加文件到提交..."
103
61
  subprocess.Popen(
104
- commit_cmd,
62
+ ["git", "add", "."],
105
63
  stdout=subprocess.DEVNULL,
106
64
  stderr=subprocess.DEVNULL
107
65
  ).wait()
66
+ spinner.write("✅ 添加文件到提交")
67
+
68
+ # 获取差异
69
+ spinner.text = "正在获取代码差异..."
70
+ process = subprocess.Popen(
71
+ ["git", "diff", "--cached", "--exit-code"],
72
+ stdout=subprocess.PIPE,
73
+ stderr=subprocess.PIPE
74
+ )
75
+ diff = process.communicate()[0].decode()
76
+ spinner.write("✅ 获取差异")
77
+
78
+ # 生成提交信息
79
+ spinner.text = "正在生成提交消息..."
80
+ prompt = f'''根据以下规则生成提交信息:
81
+ 提交信息应使用{args.get('lang', '中文')}书写
82
+ # 必需结构
83
+ 必须使用以下格式:
84
+ <COMMIT_MESSAGE>
85
+ <类型>(<范围>): <主题>
86
+ 使用祈使语气描述变更内容
87
+ </COMMIT_MESSAGE>
88
+ # 格式规则
89
+ 1. 类型: fix, feat, docs, style, refactor, test, chore
90
+ 2. 范围表示模块 (例如: auth, database)
91
+ 3. 主题行 <= 72个字符,不以句号结尾
92
+ 4. 正文使用现在时态解释每个变更的内容和原因
93
+ 5. 不要遗漏任何变更
94
+ # 分析材料
95
+ {diff}
96
+ '''
97
+ platform = PlatformRegistry().get_codegen_platform()
98
+ commit_message = platform.chat_until_success(prompt)
99
+ commit_message = self._extract_commit_message(commit_message)
100
+ spinner.write("✅ 生成提交消息")
101
+
102
+ # 执行提交
103
+ spinner.text = "正在准备提交..."
104
+ with tempfile.NamedTemporaryFile(mode='w', delete=True) as tmp_file:
105
+ tmp_file.write(commit_message)
106
+ tmp_file.flush()
107
+ spinner.text = "正在执行提交..."
108
+ commit_cmd = ["git", "commit", "-F", tmp_file.name]
109
+ subprocess.Popen(
110
+ commit_cmd,
111
+ stdout=subprocess.DEVNULL,
112
+ stderr=subprocess.DEVNULL
113
+ ).wait()
114
+ spinner.write("✅ 提交")
115
+
116
+ commit_hash = self._get_last_commit_hash()
117
+ spinner.text = "完成提交"
118
+ spinner.ok("✅")
108
119
 
109
- commit_hash = self._get_last_commit_hash()
110
120
  PrettyOutput.print(f"提交哈希: {commit_hash}\n提交消息: {commit_message}", OutputType.SUCCESS)
111
121
 
112
122
  return {
@@ -116,7 +126,7 @@ class GitCommitTool:
116
126
  "commit_message": commit_message
117
127
  }),
118
128
  "stderr": ""
119
- }
129
+ }
120
130
 
121
131
  except Exception as e:
122
132
  return {
@@ -134,4 +144,4 @@ def main():
134
144
  tool.execute({"lang": args.lang if hasattr(args, 'lang') else 'Chinese'})
135
145
 
136
146
  if __name__ == "__main__":
137
- sys.exit(main())
147
+ sys.exit(main())
@@ -1,6 +1,7 @@
1
1
  from typing import Dict, Any
2
2
  import requests
3
3
  from bs4 import BeautifulSoup
4
+ from yaspin import yaspin
4
5
 
5
6
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
6
7
 
@@ -29,48 +30,67 @@ class WebpageTool:
29
30
  }
30
31
 
31
32
  # Send request
32
- PrettyOutput.print(f"正在读取网页:{url}", OutputType.INFO)
33
- response = requests.get(url, headers=headers, timeout=10)
34
- response.raise_for_status()
33
+ with yaspin(text="正在读取网页...", color="cyan") as spinner:
34
+ response = requests.get(url, headers=headers, timeout=10)
35
+ response.raise_for_status()
36
+ spinner.text = "网页读取完成"
37
+ spinner.ok("✅")
38
+
35
39
 
36
40
  # Use correct encoding
41
+
37
42
  response.encoding = response.apparent_encoding
38
43
 
39
44
  # Parse HTML
40
- soup = BeautifulSoup(response.text, 'html.parser')
45
+ with yaspin(text="正在解析网页...", color="cyan") as spinner:
46
+ soup = BeautifulSoup(response.text, 'html.parser')
47
+ spinner.text = "网页解析完成"
48
+ spinner.ok("✅")
41
49
 
42
50
  # Remove script and style tags
43
- for script in soup(["script", "style"]):
44
- script.decompose()
51
+ with yaspin(text="正在移除脚本和样式...", color="cyan") as spinner:
52
+ for script in soup(["script", "style"]):
53
+ script.decompose()
54
+ spinner.text = "脚本和样式移除完成"
55
+ spinner.ok("✅")
45
56
 
46
57
  # Extract title
47
- title = soup.title.string if soup.title else ""
48
- title = title.strip() if title else "No title"
49
-
50
- # Extract text and links
51
- text_parts = []
52
- links = []
58
+ with yaspin(text="正在提取标题...", color="cyan") as spinner:
59
+ title = soup.title.string if soup.title else ""
60
+ title = title.strip() if title else "No title"
61
+ spinner.text = "标题提取完成"
62
+ spinner.ok("✅")
53
63
 
54
- # Process content and collect links
55
- for element in soup.descendants:
56
- if element.name == 'a' and element.get('href'): # type: ignore
57
- href = element.get('href') # type: ignore
58
- text = element.get_text(strip=True)
59
- if text and href:
60
- links.append(f"[{text}]({href})")
61
- elif isinstance(element, str) and element.strip():
62
- text_parts.append(element.strip())
64
+ with yaspin(text="正在提取文本和链接...", color="cyan") as spinner:
65
+ # Extract text and links
66
+ text_parts = []
67
+ links = []
68
+
69
+ # Process content and collect links
70
+ for element in soup.descendants:
71
+ if element.name == 'a' and element.get('href'): # type: ignore
72
+ href = element.get('href') # type: ignore
73
+ text = element.get_text(strip=True)
74
+ if text and href:
75
+ links.append(f"[{text}]({href})")
76
+ elif isinstance(element, str) and element.strip():
77
+ text_parts.append(element.strip())
78
+ spinner.text = "文本和链接提取完成"
79
+ spinner.ok("✅")
63
80
 
64
81
  # Build output
65
- output = [
66
- f"Title: {title}",
67
- "",
68
- "Text content:",
69
- "\n".join(text_parts),
70
- "",
71
- "Links found:",
72
- "\n".join(links) if links else "No links found"
73
- ]
82
+ with yaspin(text="正在构建输出...", color="cyan") as spinner:
83
+ output = [
84
+ f"Title: {title}",
85
+ "",
86
+ "Text content:",
87
+ "\n".join(text_parts),
88
+ "",
89
+ "Links found:",
90
+ "\n".join(links) if links else "No links found"
91
+ ]
92
+ spinner.text = "输出构建完成"
93
+ spinner.ok("✅")
74
94
 
75
95
  return {
76
96
  "success": True,