jarvis-ai-assistant 0.1.175__py3-none-any.whl → 0.1.177__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 (34) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +22 -35
  3. jarvis/jarvis_agent/jarvis.py +14 -12
  4. jarvis/jarvis_agent/main.py +6 -6
  5. jarvis/jarvis_code_agent/code_agent.py +10 -8
  6. jarvis/jarvis_code_analysis/code_review.py +1 -1
  7. jarvis/jarvis_dev/main.py +1 -1
  8. jarvis/jarvis_git_details/main.py +1 -1
  9. jarvis/jarvis_git_squash/main.py +1 -1
  10. jarvis/jarvis_git_utils/git_commiter.py +1 -1
  11. jarvis/jarvis_multi_agent/main.py +1 -1
  12. jarvis/jarvis_platform/base.py +53 -32
  13. jarvis/jarvis_platform/human.py +4 -3
  14. jarvis/jarvis_platform/kimi.py +22 -170
  15. jarvis/jarvis_platform/openai.py +8 -30
  16. jarvis/jarvis_platform/yuanbao.py +35 -83
  17. jarvis/jarvis_platform_manager/main.py +1 -1
  18. jarvis/jarvis_smart_shell/main.py +4 -2
  19. jarvis/jarvis_tools/ask_codebase.py +8 -2
  20. jarvis/jarvis_tools/cli/main.py +28 -1
  21. jarvis/jarvis_tools/edit_file.py +163 -103
  22. jarvis/jarvis_tools/read_code.py +0 -2
  23. jarvis/jarvis_tools/registry.py +30 -0
  24. jarvis/jarvis_utils/config.py +12 -3
  25. jarvis/jarvis_utils/embedding.py +1 -10
  26. jarvis/jarvis_utils/output.py +78 -41
  27. jarvis/jarvis_utils/utils.py +19 -1
  28. {jarvis_ai_assistant-0.1.175.dist-info → jarvis_ai_assistant-0.1.177.dist-info}/METADATA +5 -4
  29. {jarvis_ai_assistant-0.1.175.dist-info → jarvis_ai_assistant-0.1.177.dist-info}/RECORD +33 -34
  30. {jarvis_ai_assistant-0.1.175.dist-info → jarvis_ai_assistant-0.1.177.dist-info}/WHEEL +1 -1
  31. jarvis/jarvis_agent/file_input_handler.py +0 -108
  32. {jarvis_ai_assistant-0.1.175.dist-info → jarvis_ai_assistant-0.1.177.dist-info}/entry_points.txt +0 -0
  33. {jarvis_ai_assistant-0.1.175.dist-info → jarvis_ai_assistant-0.1.177.dist-info}/licenses/LICENSE +0 -0
  34. {jarvis_ai_assistant-0.1.175.dist-info → jarvis_ai_assistant-0.1.177.dist-info}/top_level.txt +0 -0
@@ -14,11 +14,13 @@
14
14
  - 完善的错误处理和回滚机制
15
15
  - 严格的格式保持要求
16
16
  """
17
+ from typing import List
17
18
  import re
18
19
  from typing import Any, Dict, Tuple
19
20
 
20
21
  import yaml
21
22
  from yaspin import yaspin
23
+ from yaspin.core import Yaspin
22
24
 
23
25
  from jarvis.jarvis_platform.registry import PlatformRegistry
24
26
  from jarvis.jarvis_tools.file_operation import FileOperationTool
@@ -32,30 +34,27 @@ class FileSearchReplaceTool:
32
34
  name = "edit_file"
33
35
  description = """代码编辑工具,用于编辑单个文件
34
36
 
35
- # 代码编辑规范
37
+ # 代码补丁生成指南
36
38
 
37
39
  ## 重要提示
38
40
  此工具可以查看和修改单个文件的代码,只需提供要修改的代码片段即可。应尽量精简内容,只包含必要的上下文和修改部分。特别注意:不要提供完整文件内容,只提供需要修改的部分及其上下文!
39
41
 
40
42
  ## 基本使用
41
43
  1. 指定需要修改的文件路径
42
- 2. 提供一组或多组"reason"和"patch"
43
- 3. 每个patch必须包含修改后的代码和1-2行上下文用于精确定位
44
+ 2. 提供一组或多组修改,每个修改包含"reason"、"search"和"replace"
45
+ 3. 每个修改中,"search"必须包含足够的上下文确保能在文件中**唯一匹配**
44
46
 
45
47
  ## 核心原则
46
48
  1. **精准修改**:只提供需要修改的代码部分及其上下文,不需要展示整个文件内容
47
49
  2. **最小补丁原则**:始终生成最小范围的补丁,只包含必要的上下文和实际修改
48
- 3. **上下文定位**:确保提供的上下文能唯一标识修改位置
49
-
50
- ## 最佳实践
51
- 1. 每个修改应专注于单一职责,避免包含过多无关代码
52
- 2. 不要出现未实现的代码,如:TODO
53
- 3. 示例格式:
54
- ```
55
- # 原有上下文行
56
- if condition: # 修改这行
57
- return new_value
58
- ```
50
+ 3. **上下文完整性**:确保提供的上下文能唯一标识修改位置,避免匹配到多处
51
+
52
+ ## 输出格式规范
53
+ - 每个修改必须包含SEARCH部分和REPLACE部分
54
+ - SEARCH部分是需要查找的原始代码,必须能在原文件中**唯一匹配**
55
+ - REPLACE部分是替换后的新代码
56
+ - 如果修改较大,可以使用多个修改块
57
+
59
58
  """
60
59
  parameters = {
61
60
  "type": "object",
@@ -74,12 +73,15 @@ class FileSearchReplaceTool:
74
73
  "type": "string",
75
74
  "description": "修改的原因"
76
75
  },
77
- "patch": {
76
+ "search": {
78
77
  "type": "string",
79
- "description": "修改后的代码片段,必须包含1-2行上下文代码用于精确定位修改位置,不需要传入完整文件内容"
78
+ "description": "需要查找的原始代码"
79
+ },
80
+ "replace": {
81
+ "type": "string",
82
+ "description": "替换后的新代码"
80
83
  }
81
84
  },
82
- "required": ["reason", "patch"]
83
85
  }
84
86
  }
85
87
  },
@@ -141,8 +143,10 @@ class FileSearchReplaceTool:
141
143
  with open(file_path, 'r', encoding='utf-8') as f:
142
144
  content = f.read()
143
145
  original_content = content
144
-
145
- success, temp_content = patch_apply(file_path, yaml.safe_dump(changes))
146
+ with yaspin(text=f"正在处理文件 {file_path}...", color="cyan") as spinner:
147
+ success, temp_content = fast_edit(file_path, changes, spinner)
148
+ if not success:
149
+ success, temp_content = slow_edit(file_path, yaml.safe_dump(changes), spinner)
146
150
 
147
151
  # 只有当所有替换操作都成功时,才写回文件
148
152
  if success and (temp_content != original_content or not file_exists):
@@ -207,29 +211,42 @@ class FileSearchReplaceTool:
207
211
  "stdout": "",
208
212
  "stderr": error_msg + "\n" + "\n".join(stderr_messages)
209
213
  }
214
+
210
215
 
211
216
 
212
- def patch_apply(filepath: str, patch_content: str) -> Tuple[bool, str]:
217
+ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool, str]:
213
218
  """执行精确的文件编辑操作,使用AI模型生成差异补丁并应用。
214
-
215
- 功能概述:
219
+
220
+ 核心功能:
216
221
  1. 使用AI模型分析补丁内容并生成精确的代码差异
217
222
  2. 应用生成的差异补丁到目标文件
218
- 3. 提供重试机制确保操作可靠性
219
-
223
+ 3. 提供3次重试机制确保操作可靠性
224
+ 4. 支持大文件处理(自动上传到模型平台)
225
+ 5. 严格的格式一致性检查
226
+
220
227
  参数:
221
- filepath: 要编辑的文件路径
222
- patch_content: YAML格式的补丁内容,包含修改原因和代码片段
223
-
228
+ filepath: 要编辑的文件路径(绝对或相对路径)
229
+ patch_content: YAML格式的补丁内容,包含:
230
+ - reason: 修改原因描述
231
+ - search: 需要查找的原始代码(必须包含足够上下文)
232
+ - replace: 替换后的新代码
233
+ spinner: Yaspin实例,用于显示处理状态
234
+
224
235
  返回值:
225
236
  Tuple[bool, str]:
226
- - 第一个元素表示操作是否成功
227
- - 第二个元素是修改后的文件内容(成功时)或错误信息(失败时)
228
-
237
+ - 第一个元素表示操作是否成功(True/False)
238
+ - 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
239
+
229
240
  异常处理:
230
- 1. 捕获并处理文件操作异常
231
- 2. 失败时自动回滚文件修改
232
- 3. 提供详细的执行状态输出
241
+ 1. 文件不存在或权限不足时会捕获异常并返回失败
242
+ 2. 模型生成补丁失败时会自动重试最多3次
243
+ 3. 补丁应用失败时会自动回滚文件修改
244
+
245
+ 实现细节:
246
+ 1. 检查文件是否在工作目录下(影响版本控制)
247
+ 2. 根据文件大小决定是否上传到模型平台
248
+ 3. 使用精确的DIFF格式解析模型生成的补丁
249
+ 4. 确保补丁应用前进行唯一性匹配检查
233
250
  """
234
251
  import os
235
252
  work_dir = os.path.abspath(os.curdir)
@@ -237,20 +254,19 @@ def patch_apply(filepath: str, patch_content: str) -> Tuple[bool, str]:
237
254
  if not filepath.startswith(work_dir):
238
255
  PrettyOutput.print(f"文件 {filepath} 不在工作目录 {work_dir} 下,不会进行版本控制管理", OutputType.WARNING)
239
256
  model = PlatformRegistry().get_normal_platform()
240
- with yaspin(text=f"正在处理文件 {filepath}...", color="cyan") as spinner:
241
- try:
242
- file_content = FileOperationTool().execute({"operation":"read", "files":[{"path":filepath}]})["stdout"]
243
- need_upload_file = is_context_overflow(file_content)
244
- upload_success = False
245
- # 读取原始文件内容
246
- with spinner.hidden():
247
- if need_upload_file and model.upload_files([filepath]):
248
- upload_success = True
257
+ try:
258
+ file_content = FileOperationTool().execute({"operation":"read", "files":[{"path":filepath}]})["stdout"]
259
+ need_upload_file = is_context_overflow(file_content)
260
+ upload_success = False
261
+ # 读取原始文件内容
262
+ with spinner.hidden():
263
+ if need_upload_file and model.upload_files([filepath]):
264
+ upload_success = True
249
265
 
250
266
 
251
- model.set_suppress_output(True)
267
+ model.set_suppress_output(True)
252
268
 
253
- main_prompt = f"""
269
+ main_prompt = f"""
254
270
  # 代码补丁生成专家指南
255
271
 
256
272
  ## 任务描述
@@ -292,66 +308,110 @@ def patch_apply(filepath: str, patch_content: str) -> Tuple[bool, str]:
292
308
  {"<" * 5} REPLACE
293
309
  {ct("DIFF")}
294
310
  """
295
-
296
- for _ in range(3):
297
- file_prompt = ""
298
- if not need_upload_file:
299
- file_prompt = f"""
300
- # 原始代码
301
- {file_content}
302
- """
303
-
304
- response = model.chat_until_success(main_prompt + file_prompt)
311
+
312
+ for _ in range(3):
313
+ file_prompt = ""
314
+ if not need_upload_file:
315
+ file_prompt = f"""
316
+ # 原始代码
317
+ {file_content}
318
+ """
319
+
320
+ response = model.chat_until_success(main_prompt + file_prompt)
321
+ else:
322
+ if upload_success:
323
+ response = model.chat_until_success(main_prompt)
305
324
  else:
306
- if upload_success:
307
- response = model.chat_until_success(main_prompt)
308
- else:
309
- return False, ""
310
-
311
- # 解析差异化补丁
312
- diff_blocks = re.finditer(ot("DIFF")+r'\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?'+ct("DIFF"),
313
- response, re.DOTALL)
314
-
315
- # 读取原始文件内容
316
- with open(filepath, 'r', encoding='utf-8', errors="ignore") as f:
317
- file_content = f.read()
318
-
319
- # 应用所有差异化补丁
320
- modified_content = file_content
321
- patch_count = 0
322
- success = True
323
- for match in diff_blocks:
324
- search_text = match.group(1).strip()
325
- replace_text = match.group(2).strip()
326
- patch_count += 1
327
- # 检查搜索文本是否存在于文件中
328
- if search_text in modified_content:
329
- # 如果有多处,报错
330
- if modified_content.count(search_text) > 1:
331
- spinner.write(f"❌ 补丁 #{patch_count} 应用失败:找到多个匹配的代码段")
332
- success = False
333
- break
334
- # 应用替换
335
- modified_content = modified_content.replace(
336
- search_text, replace_text)
337
- spinner.write(f"✅ 补丁 #{patch_count} 应用成功")
338
- else:
339
- spinner.write(f"❌ 补丁 #{patch_count} 应用失败:无法找到匹配的代码段")
340
- success = False
341
- break
342
- if not success:
343
- revert_file(filepath)
344
- continue
325
+ return False, ""
345
326
 
327
+ # 解析差异化补丁
328
+ diff_blocks = re.finditer(ot("DIFF")+r'\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?'+ct("DIFF"),
329
+ response, re.DOTALL)
346
330
 
347
- spinner.text = f"文件 {filepath} 修改完成,应用了 {patch_count} 个补丁"
348
- spinner.ok("✅")
349
- return True, modified_content
350
- spinner.text = f"文件 {filepath} 修改失败"
351
- spinner.fail("❌")
352
- return False, ""
331
+ patches = []
332
+ for match in diff_blocks:
333
+ patches.append({
334
+ "search": match.group(1).strip(),
335
+ "replace": match.group(2).strip()
336
+ })
353
337
 
354
- except Exception as e:
355
- spinner.text = f"文件修改失败: {str(e)}"
356
- spinner.fail("❌")
357
- return False, ""
338
+ success, modified_content = fast_edit(filepath, patches, spinner)
339
+ if success:
340
+ return True, modified_content
341
+ spinner.text = f"文件 {filepath} 修改失败"
342
+ spinner.fail("❌")
343
+ return False, ""
344
+
345
+ except Exception as e:
346
+ spinner.text = f"文件修改失败: {str(e)}"
347
+ spinner.fail("❌")
348
+ return False, ""
349
+
350
+
351
+ def fast_edit(filepath: str, patches: List[Dict[str,str]], spinner: Yaspin) -> Tuple[bool, str]:
352
+ """快速应用预先生成的补丁到目标文件。
353
+
354
+ 核心功能:
355
+ 1. 直接应用已生成的代码补丁
356
+ 2. 执行严格的唯一匹配检查
357
+ 3. 提供详细的补丁应用状态反馈
358
+ 4. 失败时自动回滚文件修改
359
+
360
+ 参数:
361
+ filepath: 要编辑的文件路径(绝对或相对路径)
362
+ patches: 补丁列表,每个补丁包含:
363
+ - search: 需要查找的原始代码
364
+ - replace: 替换后的新代码
365
+ spinner: Yaspin实例,用于显示处理状态
366
+
367
+ 返回值:
368
+ Tuple[bool, str]:
369
+ - 第一个元素表示操作是否成功(True/False)
370
+ - 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
371
+
372
+ 异常处理:
373
+ 1. 文件不存在或权限不足时会捕获异常并返回失败
374
+ 2. 补丁不匹配或有多处匹配时会返回失败
375
+ 3. 失败时会自动回滚文件修改
376
+
377
+ 实现细节:
378
+ 1. 读取文件内容到内存
379
+ 2. 依次应用每个补丁,检查唯一匹配性
380
+ 3. 记录每个补丁的应用状态
381
+ 4. 所有补丁成功应用后才写入文件
382
+ """
383
+ # 读取原始文件内容
384
+ with open(filepath, 'r', encoding='utf-8', errors="ignore") as f:
385
+ file_content = f.read()
386
+
387
+ # 应用所有差异化补丁
388
+ modified_content = file_content
389
+ patch_count = 0
390
+ success = True
391
+ for patch in patches:
392
+ search_text = patch["search"]
393
+ replace_text = patch["replace"]
394
+ patch_count += 1
395
+ # 检查搜索文本是否存在于文件中
396
+ if search_text in modified_content:
397
+ # 如果有多处,报错
398
+ if modified_content.count(search_text) > 1:
399
+ spinner.write(f"❌ 补丁 #{patch_count} 应用失败:找到多个匹配的代码段")
400
+ success = False
401
+ break
402
+ # 应用替换
403
+ modified_content = modified_content.replace(
404
+ search_text, replace_text)
405
+ spinner.write(f"✅ 补丁 #{patch_count} 应用成功")
406
+ else:
407
+ spinner.write(f"❌ 补丁 #{patch_count} 应用失败:无法找到匹配的代码段")
408
+ success = False
409
+ break
410
+ if not success:
411
+ revert_file(filepath)
412
+ return False, ""
413
+
414
+
415
+ spinner.text = f"文件 {filepath} 修改完成,应用了 {patch_count} 个补丁"
416
+ spinner.ok("✅")
417
+ return True, modified_content
@@ -98,8 +98,6 @@ class ReadCodeTool:
98
98
  spinner.text = f"文件读取完成: {abs_path}"
99
99
  spinner.ok("✅")
100
100
 
101
- PrettyOutput.print(output, OutputType.SUCCESS)
102
-
103
101
  return {
104
102
  "success": True,
105
103
  "stdout": output,
@@ -178,6 +178,32 @@ class ToolRegistry(OutputHandlerProtocol):
178
178
  self._load_external_tools()
179
179
  self._load_mcp_tools()
180
180
 
181
+ def _get_tool_stats(self) -> Dict[str, int]:
182
+ """从数据目录获取工具调用统计"""
183
+ stats_file = Path(get_data_dir()) / "tool_stat.yaml"
184
+ if stats_file.exists():
185
+ try:
186
+ with open(stats_file, "r", encoding="utf-8") as f:
187
+ return yaml.safe_load(f) or {}
188
+ except Exception as e:
189
+ PrettyOutput.print(
190
+ f"加载工具调用统计失败: {str(e)}", OutputType.WARNING
191
+ )
192
+ return {}
193
+
194
+ def _update_tool_stats(self, name: str) -> None:
195
+ """更新工具调用统计"""
196
+ stats = self._get_tool_stats()
197
+ stats[name] = stats.get(name, 0) + 1
198
+ stats_file = Path(get_data_dir()) / "tool_stat.yaml"
199
+ try:
200
+ with open(stats_file, "w", encoding="utf-8") as f:
201
+ yaml.safe_dump(stats, f)
202
+ except Exception as e:
203
+ PrettyOutput.print(
204
+ f"保存工具调用统计失败: {str(e)}", OutputType.WARNING
205
+ )
206
+
181
207
  def use_tools(self, name: List[str]) -> None:
182
208
  """使用指定工具
183
209
 
@@ -559,6 +585,10 @@ class ToolRegistry(OutputHandlerProtocol):
559
585
  "stderr": f"工具 {name} 不存在,可用的工具有: {', '.join(self.tools.keys())}",
560
586
  "stdout": "",
561
587
  }
588
+
589
+ # 更新工具调用统计
590
+ self._update_tool_stats(name)
591
+
562
592
  return tool.execute(arguments)
563
593
 
564
594
  def _format_tool_output(self, stdout: str, stderr: str) -> str:
@@ -38,7 +38,7 @@ def get_max_token_count() -> int:
38
38
  返回:
39
39
  int: 模型能处理的最大token数量。
40
40
  """
41
- return int(os.getenv('JARVIS_MAX_TOKEN_COUNT', '102400000'))
41
+ return int(os.getenv('JARVIS_MAX_TOKEN_COUNT', '960000'))
42
42
 
43
43
  def get_max_input_token_count() -> int:
44
44
  """
@@ -158,6 +158,15 @@ def get_max_big_content_size() -> int:
158
158
  获取最大大内容大小。
159
159
 
160
160
  返回:
161
- int: 最大大内容大小,默认为1MB
161
+ int: 最大大内容大小
162
162
  """
163
- return int(os.getenv('JARVIS_MAX_BIG_CONTENT_SIZE', '96000'))
163
+ return int(os.getenv('JARVIS_MAX_BIG_CONTENT_SIZE', '1024000'))
164
+
165
+ def get_pretty_output() -> bool:
166
+ """
167
+ 获取是否启用PrettyOutput。
168
+
169
+ 返回:
170
+ bool: 如果启用PrettyOutput则返回True,默认为True
171
+ """
172
+ return os.getenv('JARVIS_PRETTY_OUTPUT', 'false') == 'true'
@@ -69,16 +69,7 @@ def split_text_into_chunks(text: str, max_length: int = 512, min_length: int = 5
69
69
 
70
70
  # 处理最后一个块
71
71
  if current_chunk:
72
- if current_tokens >= min_length:
73
- chunks.append(current_chunk)
74
- elif chunks: # 如果最后一个块太短,尝试与前面的块合并
75
- last_chunk = chunks[-1]
76
- combined = last_chunk + current_chunk
77
- combined_tokens = get_context_token_count(combined)
78
- if combined_tokens <= max_length:
79
- chunks[-1] = combined
80
- else:
81
- chunks.append(current_chunk)
72
+ chunks.append(current_chunk) # 直接添加最后一个块,无论长度如何
82
73
 
83
74
  return chunks
84
75
 
@@ -10,14 +10,16 @@
10
10
  """
11
11
  from enum import Enum
12
12
  from datetime import datetime
13
- from typing import Optional
13
+ from typing import Optional, Tuple
14
14
  from rich.panel import Panel
15
15
  from rich.text import Text
16
16
  from rich.syntax import Syntax
17
17
  from rich.style import Style as RichStyle
18
18
  from pygments.lexers import guess_lexer
19
19
  from pygments.util import ClassNotFound
20
+ from jarvis.jarvis_utils.config import get_pretty_output
20
21
  from jarvis.jarvis_utils.globals import console, get_agent_list
22
+ from rich.box import SIMPLE
21
23
  class OutputType(Enum):
22
24
  """
23
25
  输出类型枚举,用于分类和样式化不同类型的消息。
@@ -125,7 +127,7 @@ class PrettyOutput:
125
127
  except (ClassNotFound, Exception):
126
128
  return default_lang
127
129
  @staticmethod
128
- def _format(output_type: OutputType, timestamp: bool = True) -> Text:
130
+ def _format(output_type: OutputType, timestamp: bool = True) -> str:
129
131
  """
130
132
  使用时间戳和图标格式化输出头。
131
133
 
@@ -136,14 +138,13 @@ class PrettyOutput:
136
138
  返回:
137
139
  Text: 格式化后的rich Text对象
138
140
  """
139
- formatted = Text()
141
+ icon = PrettyOutput._ICONS.get(output_type, "")
142
+ formatted = f"{icon} "
140
143
  if timestamp:
141
- formatted.append(f"[{datetime.now().strftime('%H:%M:%S')}][{output_type.value}]", style=output_type.value)
144
+ formatted+=f"[{datetime.now().strftime('%H:%M:%S')}][{output_type.value}]"
142
145
  agent_info = get_agent_list()
143
146
  if agent_info:
144
- formatted.append(f"[{agent_info}]", style="blue")
145
- icon = PrettyOutput._ICONS.get(output_type, "")
146
- formatted.append(f" {icon} ", style=output_type.value)
147
+ formatted+=f"[{agent_info}]"
147
148
  return formatted
148
149
  @staticmethod
149
150
  def print(text: str, output_type: OutputType, timestamp: bool = True, lang: Optional[str] = None, traceback: bool = False):
@@ -158,35 +159,55 @@ class PrettyOutput:
158
159
  traceback: 是否显示错误的回溯信息
159
160
  """
160
161
  styles = {
161
- OutputType.SYSTEM: RichStyle(color="bright_cyan", bgcolor="#1a1a1a", frame=True, meta={"icon": "🤖"}),
162
- OutputType.CODE: RichStyle(color="green", bgcolor="#1a1a1a", frame=True, meta={"icon": "📝"}),
163
- OutputType.RESULT: RichStyle(color="bright_blue", bgcolor="#1a1a1a", frame=True, meta={"icon": "✨"}),
164
- OutputType.ERROR: RichStyle(color="red", frame=True, bgcolor="dark_red", meta={"icon": "❌"}),
165
- OutputType.INFO: RichStyle(color="gold1", frame=True, bgcolor="grey11", meta={"icon": "ℹ️"}),
166
- OutputType.PLANNING: RichStyle(color="purple", bold=True, frame=True, meta={"icon": "📋"}),
167
- OutputType.PROGRESS: RichStyle(color="white", encircle=True, frame=True, meta={"icon": "⏳"}),
168
- OutputType.SUCCESS: RichStyle(color="bright_green", bold=True, strike=False, meta={"icon": "✅"}),
169
- OutputType.WARNING: RichStyle(color="yellow", bold=True, blink2=True, bgcolor="dark_orange", meta={"icon": "⚠️"}),
170
- OutputType.DEBUG: RichStyle(color="grey58", dim=True, conceal=True, meta={"icon": "🔍"}),
171
- OutputType.USER: RichStyle(color="spring_green2", frame=True, meta={"icon": "👤"}),
172
- OutputType.TOOL: RichStyle(color="dark_sea_green4", bgcolor="grey19", frame=True, meta={"icon": "🔧"}),
162
+ OutputType.SYSTEM: dict( bgcolor="#1e2b3c"),
163
+ OutputType.CODE: dict( bgcolor="#1c2b1c"),
164
+ OutputType.RESULT: dict( bgcolor="#1c1c2b"),
165
+ OutputType.ERROR: dict( bgcolor="#2b1c1c"),
166
+ OutputType.INFO: dict( bgcolor="#2b2b1c", meta={"icon": "ℹ️"}),
167
+ OutputType.PLANNING: dict( bgcolor="#2b1c2b"),
168
+ OutputType.PROGRESS: dict( bgcolor="#1c1c1c"),
169
+ OutputType.SUCCESS: dict( bgcolor="#1c2b1c"),
170
+ OutputType.WARNING: dict( bgcolor="#2b2b1c"),
171
+ OutputType.DEBUG: dict( bgcolor="#1c1c1c"),
172
+ OutputType.USER: dict( bgcolor="#1c2b2b"),
173
+ OutputType.TOOL: dict( bgcolor="#1c2b2b"),
174
+ }
175
+
176
+ header_styles = {
177
+ OutputType.SYSTEM: RichStyle(color="bright_cyan", bgcolor="#1e2b3c", frame=True, meta={"icon": "🤖"}),
178
+ OutputType.CODE: RichStyle(color="green", bgcolor="#1c2b1c", frame=True, meta={"icon": "📝"}),
179
+ OutputType.RESULT: RichStyle(color="bright_blue", bgcolor="#1c1c2b", frame=True, meta={"icon": "✨"}),
180
+ OutputType.ERROR: RichStyle(color="red", frame=True, bgcolor="#2b1c1c", meta={"icon": "❌"}),
181
+ OutputType.INFO: RichStyle(color="gold1", frame=True, bgcolor="#2b2b1c", meta={"icon": "ℹ️"}),
182
+ OutputType.PLANNING: RichStyle(color="purple", bold=True, frame=True, bgcolor="#2b1c2b", meta={"icon": "📋"}),
183
+ OutputType.PROGRESS: RichStyle(color="white", encircle=True, frame=True, bgcolor="#1c1c1c", meta={"icon": "⏳"}),
184
+ OutputType.SUCCESS: RichStyle(color="bright_green", bold=True, strike=False, bgcolor="#1c2b1c", meta={"icon": "✅"}),
185
+ OutputType.WARNING: RichStyle(color="yellow", bold=True, blink2=True, bgcolor="#2b2b1c", meta={"icon": "⚠️"}),
186
+ OutputType.DEBUG: RichStyle(color="grey58", dim=True, conceal=True, bgcolor="#1c1c1c", meta={"icon": "🔍"}),
187
+ OutputType.USER: RichStyle(color="spring_green2", frame=True, bgcolor="#1c2b2b", meta={"icon": "👤"}),
188
+ OutputType.TOOL: RichStyle(color="dark_sea_green4", bgcolor="#1c2b2b", frame=True, meta={"icon": "🔧"}),
173
189
  }
190
+
174
191
  lang = lang if lang is not None else PrettyOutput._detect_language(text, default_lang='markdown')
175
- header = PrettyOutput._format(output_type, timestamp)
176
- content = Syntax(text, lang, theme="monokai", word_wrap=True)
192
+ header = Text(PrettyOutput._format(output_type, timestamp), style=header_styles[output_type])
193
+ content = Syntax(text, lang, theme="monokai", word_wrap=True, background_color=styles[output_type]["bgcolor"])
177
194
  panel = Panel(
178
195
  content,
179
- style=styles[output_type],
180
- border_style=styles[output_type],
196
+ border_style=header_styles[output_type],
181
197
  title=header,
182
198
  title_align="left",
183
199
  padding=(0, 0),
184
200
  highlight=True,
185
- # box=HEAVY,
186
201
  )
187
- console.print()
188
- console.print(panel)
189
- if traceback or output_type == OutputType.ERROR:
202
+ if get_pretty_output():
203
+ console.print(panel)
204
+ else:
205
+ if len(text.strip().splitlines()) > 1:
206
+ console.print(header)
207
+ console.print(content)
208
+ else:
209
+ console.print(header, content)
210
+ if traceback:
190
211
  console.print_exception()
191
212
  @staticmethod
192
213
  def section(title: str, output_type: OutputType = OutputType.INFO):
@@ -197,23 +218,39 @@ class PrettyOutput:
197
218
  title: 章节标题文本
198
219
  output_type: 输出类型(影响样式)
199
220
  """
221
+ text = Text(title, style=output_type.value, justify="center")
200
222
  panel = Panel(
201
- Text(title, style=output_type.value, justify="center"),
223
+ text,
202
224
  border_style=output_type.value
203
225
  )
204
- console.print()
205
- console.print(panel)
206
- console.print()
226
+ if get_pretty_output():
227
+ console.print(panel)
228
+ else:
229
+ console.print(text)
207
230
 
208
231
  @staticmethod
209
- def _get_style(output_type: OutputType) -> RichStyle:
210
- """
211
- 获取预定义的RichStyle用于输出类型。
212
-
213
- 参数:
214
- output_type: 要获取样式的输出类型
215
-
216
- 返回:
217
- RichStyle: 对应的样式
232
+ def print_gradient_text(text: str, start_color: Tuple[int, int, int], end_color: Tuple[int, int, int]) -> None:
233
+ """打印带有渐变色彩的文本。
234
+
235
+ Args:
236
+ text: 要打印的文本
237
+ start_color: 起始RGB颜色元组 (r, g, b)
238
+ end_color: 结束RGB颜色元组 (r, g, b)
218
239
  """
219
- return console.get_style(output_type.value)
240
+ lines = text.strip('\n').split('\n')
241
+ total_lines = len(lines)
242
+ colored_lines = []
243
+ for i, line in enumerate(lines):
244
+ # 计算当前行的渐变颜色
245
+ r = int(start_color[0] + (end_color[0] - start_color[0]) * i / (total_lines - 1))
246
+ g = int(start_color[1] + (end_color[1] - start_color[1]) * i / (total_lines - 1))
247
+ b = int(start_color[2] + (end_color[2] - start_color[2]) * i / (total_lines - 1))
248
+
249
+ # 使用ANSI转义序列设置颜色
250
+ colored_lines.append(f"\033[38;2;{r};{g};{b}m{line}\033[0m")
251
+ colored_text = Text('\n'.join(colored_lines), style=OutputType.TOOL.value, justify="center")
252
+ panel = Panel(
253
+ colored_text,
254
+ box=SIMPLE
255
+ )
256
+ console.print(panel)
@@ -5,11 +5,13 @@ import hashlib
5
5
  import tarfile
6
6
  from pathlib import Path
7
7
  from typing import Any, Callable
8
+
9
+ from jarvis import __version__
8
10
  from jarvis.jarvis_utils.config import get_max_big_content_size, get_data_dir
9
11
  from jarvis.jarvis_utils.embedding import get_context_token_count
10
12
  from jarvis.jarvis_utils.input import get_single_line_input
11
13
  from jarvis.jarvis_utils.output import PrettyOutput, OutputType
12
- def init_env() -> None:
14
+ def init_env(welcome_str: str) -> None:
13
15
  """初始化环境变量从jarvis_data/env文件
14
16
 
15
17
  功能:
@@ -18,6 +20,22 @@ def init_env() -> None:
18
20
  3. 处理文件读取异常
19
21
  4. 检查git仓库状态并在落后时更新
20
22
  """
23
+
24
+ jarvis_ascii_art = f"""
25
+ ██╗ █████╗ ██████╗ ██╗ ██╗██╗███████╗
26
+ ██║██╔══██╗██╔══██╗██║ ██║██║██╔════╝
27
+ ██║███████║██████╔╝██║ ██║██║███████╗
28
+ ██╗██║██╔══██║██╔══██╗╚██╗ ██╔╝██║╚════██║
29
+ ╚████║██║ ██║██║ ██║ ╚████╔╝ ██║███████║
30
+ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═╝╚══════╝
31
+ {welcome_str}
32
+
33
+ https://github.com/skyfireitdiy/Jarvis
34
+ v{__version__}
35
+ """
36
+ if welcome_str:
37
+ PrettyOutput.print_gradient_text(jarvis_ascii_art, (0, 120, 255), (0, 255, 200))
38
+
21
39
  jarvis_dir = Path(get_data_dir())
22
40
  env_file = jarvis_dir / "env"
23
41