jarvis-ai-assistant 0.1.208__py3-none-any.whl → 0.1.210__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +9 -59
- jarvis/jarvis_agent/edit_file_handler.py +1 -1
- jarvis/jarvis_code_agent/code_agent.py +55 -8
- jarvis/jarvis_code_agent/lint.py +1 -1
- jarvis/jarvis_data/config_schema.json +0 -25
- jarvis/jarvis_git_utils/git_commiter.py +2 -2
- jarvis/jarvis_platform/kimi.py +20 -11
- jarvis/jarvis_platform/tongyi.py +84 -74
- jarvis/jarvis_platform/yuanbao.py +60 -54
- jarvis/jarvis_tools/ask_user.py +0 -1
- jarvis/jarvis_tools/file_analyzer.py +0 -3
- jarvis/jarvis_utils/config.py +4 -49
- jarvis/jarvis_utils/embedding.py +6 -51
- jarvis/jarvis_utils/git_utils.py +74 -11
- jarvis/jarvis_utils/http.py +169 -0
- jarvis/jarvis_utils/utils.py +186 -63
- {jarvis_ai_assistant-0.1.208.dist-info → jarvis_ai_assistant-0.1.210.dist-info}/METADATA +5 -10
- {jarvis_ai_assistant-0.1.208.dist-info → jarvis_ai_assistant-0.1.210.dist-info}/RECORD +23 -24
- {jarvis_ai_assistant-0.1.208.dist-info → jarvis_ai_assistant-0.1.210.dist-info}/entry_points.txt +1 -0
- jarvis/jarvis_data/huggingface.tar.gz +0 -0
- jarvis/jarvis_utils/jarvis_history.py +0 -98
- {jarvis_ai_assistant-0.1.208.dist-info → jarvis_ai_assistant-0.1.210.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.208.dist-info → jarvis_ai_assistant-0.1.210.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.208.dist-info → jarvis_ai_assistant-0.1.210.dist-info}/top_level.txt +0 -0
jarvis/jarvis_utils/git_utils.py
CHANGED
@@ -9,18 +9,19 @@ Git工具模块
|
|
9
9
|
- 获取最新提交的哈希值
|
10
10
|
- 从Git差异中提取修改的行范围
|
11
11
|
"""
|
12
|
+
import datetime
|
12
13
|
import os
|
13
14
|
import re
|
14
15
|
import subprocess
|
16
|
+
import sys
|
15
17
|
from typing import Any, Dict, List, Set, Tuple
|
16
18
|
|
17
|
-
from jarvis.jarvis_utils.config import
|
18
|
-
is_confirm_before_apply_patch)
|
19
|
+
from jarvis.jarvis_utils.config import is_confirm_before_apply_patch
|
19
20
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
20
21
|
from jarvis.jarvis_utils.utils import user_confirm
|
21
22
|
|
22
23
|
|
23
|
-
def
|
24
|
+
def find_git_root_and_cd(start_dir: str = ".") -> str:
|
24
25
|
"""
|
25
26
|
切换到给定路径的Git根目录,如果不是Git仓库则初始化。
|
26
27
|
|
@@ -213,7 +214,9 @@ def handle_commit_workflow() -> bool:
|
|
213
214
|
Returns:
|
214
215
|
bool: 提交是否成功
|
215
216
|
"""
|
216
|
-
if is_confirm_before_apply_patch() and not user_confirm(
|
217
|
+
if is_confirm_before_apply_patch() and not user_confirm(
|
218
|
+
"是否要提交代码?", default=True
|
219
|
+
):
|
217
220
|
revert_change()
|
218
221
|
return False
|
219
222
|
|
@@ -337,13 +340,24 @@ def check_and_update_git_repo(repo_path: str) -> bool:
|
|
337
340
|
bool: 是否执行了更新
|
338
341
|
"""
|
339
342
|
curr_dir = os.path.abspath(os.getcwd())
|
340
|
-
git_root =
|
343
|
+
git_root = find_git_root_and_cd(repo_path)
|
341
344
|
if git_root is None:
|
342
345
|
return False
|
343
346
|
|
344
347
|
try:
|
345
|
-
|
346
|
-
|
348
|
+
# 检查最新提交时间是否为今天
|
349
|
+
commit_date_result = subprocess.run(
|
350
|
+
["git", "log", "-1", "--format=%cd", "--date=short"],
|
351
|
+
cwd=git_root,
|
352
|
+
capture_output=True,
|
353
|
+
text=True,
|
354
|
+
)
|
355
|
+
if commit_date_result.returncode == 0:
|
356
|
+
commit_date = commit_date_result.stdout.strip()
|
357
|
+
today = datetime.date.today().strftime("%Y-%m-%d")
|
358
|
+
if commit_date == today:
|
359
|
+
return False
|
360
|
+
|
347
361
|
# 检查是否有未提交的修改
|
348
362
|
if has_uncommitted_changes():
|
349
363
|
return False
|
@@ -394,7 +408,49 @@ def check_and_update_git_repo(repo_path: str) -> bool:
|
|
394
408
|
f"Jarvis已更新到tag {remote_tag_result.stdout.strip()}",
|
395
409
|
OutputType.SUCCESS,
|
396
410
|
)
|
397
|
-
|
411
|
+
|
412
|
+
# 执行pip安装更新代码
|
413
|
+
try:
|
414
|
+
PrettyOutput.print("正在安装更新后的代码...", OutputType.INFO)
|
415
|
+
|
416
|
+
# 检查是否在虚拟环境中
|
417
|
+
in_venv = hasattr(sys, 'real_prefix') or (
|
418
|
+
hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix
|
419
|
+
)
|
420
|
+
|
421
|
+
# 尝试普通安装
|
422
|
+
install_cmd = [sys.executable, "-m", "pip", "install", "-e", "."]
|
423
|
+
result = subprocess.run(
|
424
|
+
install_cmd,
|
425
|
+
cwd=git_root,
|
426
|
+
capture_output=True,
|
427
|
+
text=True
|
428
|
+
)
|
429
|
+
|
430
|
+
if result.returncode == 0:
|
431
|
+
PrettyOutput.print("代码更新安装成功", OutputType.SUCCESS)
|
432
|
+
return True
|
433
|
+
|
434
|
+
# 处理权限错误
|
435
|
+
error_msg = result.stderr.strip()
|
436
|
+
if not in_venv and ("Permission denied" in error_msg or "not writeable" in error_msg):
|
437
|
+
if user_confirm("检测到权限问题,是否尝试用户级安装(--user)?", True):
|
438
|
+
user_result = subprocess.run(
|
439
|
+
install_cmd + ["--user"],
|
440
|
+
cwd=git_root,
|
441
|
+
capture_output=True,
|
442
|
+
text=True
|
443
|
+
)
|
444
|
+
if user_result.returncode == 0:
|
445
|
+
PrettyOutput.print("用户级代码安装成功", OutputType.SUCCESS)
|
446
|
+
return True
|
447
|
+
error_msg = user_result.stderr.strip()
|
448
|
+
|
449
|
+
PrettyOutput.print(f"代码安装失败: {error_msg}", OutputType.ERROR)
|
450
|
+
return False
|
451
|
+
except Exception as e:
|
452
|
+
PrettyOutput.print(f"安装过程中发生意外错误: {str(e)}", OutputType.ERROR)
|
453
|
+
return False
|
398
454
|
return False
|
399
455
|
except Exception as e:
|
400
456
|
PrettyOutput.print(f"Git仓库更新检查失败: {e}", OutputType.WARNING)
|
@@ -424,7 +480,9 @@ def get_diff_file_list() -> List[str]:
|
|
424
480
|
subprocess.run(["git", "reset"], check=True)
|
425
481
|
|
426
482
|
if result.returncode != 0:
|
427
|
-
PrettyOutput.print(
|
483
|
+
PrettyOutput.print(
|
484
|
+
f"获取差异文件列表失败: {result.stderr}", OutputType.ERROR
|
485
|
+
)
|
428
486
|
return []
|
429
487
|
|
430
488
|
return [f for f in result.stdout.splitlines() if f]
|
@@ -559,7 +617,9 @@ def confirm_add_new_files() -> None:
|
|
559
617
|
need_confirm = True
|
560
618
|
|
561
619
|
if binary_files:
|
562
|
-
output_lines.append(
|
620
|
+
output_lines.append(
|
621
|
+
f"检测到{len(binary_files)}个二进制文件(选择N将重新检测)"
|
622
|
+
)
|
563
623
|
output_lines.append("二进制文件列表:")
|
564
624
|
output_lines.extend(f" - {file}" for file in binary_files)
|
565
625
|
need_confirm = True
|
@@ -580,7 +640,10 @@ def confirm_add_new_files() -> None:
|
|
580
640
|
if not _check_conditions(new_files, added_lines, binary_files):
|
581
641
|
break
|
582
642
|
|
583
|
-
if not user_confirm(
|
643
|
+
if not user_confirm(
|
644
|
+
"是否要添加这些变更(如果不需要请修改.gitignore文件以忽略不需要的文件)?",
|
645
|
+
False,
|
646
|
+
):
|
584
647
|
continue
|
585
648
|
|
586
649
|
break
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
from typing import Any, Dict, Optional, Union, AsyncGenerator, Generator
|
5
|
+
|
6
|
+
|
7
|
+
def get_httpx_client() -> httpx.Client:
|
8
|
+
"""
|
9
|
+
获取一个配置好的 httpx.Client 对象
|
10
|
+
|
11
|
+
返回:
|
12
|
+
httpx.Client 对象
|
13
|
+
"""
|
14
|
+
client = httpx.Client(
|
15
|
+
timeout=httpx.Timeout(None), # 永不超时
|
16
|
+
headers={
|
17
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
18
|
+
},
|
19
|
+
)
|
20
|
+
return client
|
21
|
+
|
22
|
+
|
23
|
+
def get_async_httpx_client() -> httpx.AsyncClient:
|
24
|
+
"""
|
25
|
+
获取一个配置好的 httpx.AsyncClient 对象
|
26
|
+
|
27
|
+
返回:
|
28
|
+
httpx.AsyncClient 对象
|
29
|
+
"""
|
30
|
+
client = httpx.AsyncClient(
|
31
|
+
timeout=httpx.Timeout(None), # 永不超时
|
32
|
+
headers={
|
33
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
34
|
+
},
|
35
|
+
)
|
36
|
+
return client
|
37
|
+
|
38
|
+
|
39
|
+
# 增强版本的 HTTP 请求方法(使用 httpx 实现,带重试机制,解决连接中断问题)
|
40
|
+
def post(
|
41
|
+
url: str,
|
42
|
+
data: Optional[Any] = None,
|
43
|
+
json: Optional[Dict[str, Any]] = None,
|
44
|
+
**kwargs,
|
45
|
+
) -> httpx.Response:
|
46
|
+
"""
|
47
|
+
发送增强版永不超时的 POST 请求,使用 httpx 实现,包含重试机制
|
48
|
+
|
49
|
+
参数:
|
50
|
+
url: 请求的 URL
|
51
|
+
data: (可选) 请求体数据 (表单数据或原始数据)
|
52
|
+
json: (可选) JSON 数据,会自动设置 Content-Type
|
53
|
+
**kwargs: 其他传递给 httpx.post 的参数
|
54
|
+
|
55
|
+
返回:
|
56
|
+
httpx.Response 对象
|
57
|
+
|
58
|
+
注意:
|
59
|
+
此方法使用 httpx 实现,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
|
60
|
+
"""
|
61
|
+
client = get_httpx_client()
|
62
|
+
try:
|
63
|
+
response = client.post(url=url, data=data, json=json, **kwargs)
|
64
|
+
response.raise_for_status()
|
65
|
+
return response
|
66
|
+
finally:
|
67
|
+
client.close()
|
68
|
+
|
69
|
+
|
70
|
+
def get(url: str, **kwargs) -> httpx.Response:
|
71
|
+
"""
|
72
|
+
发送增强版永不超时的 GET 请求,使用 httpx 实现,包含重试机制
|
73
|
+
|
74
|
+
参数:
|
75
|
+
url: 请求的 URL
|
76
|
+
**kwargs: 其他传递给 httpx.get 的参数
|
77
|
+
|
78
|
+
返回:
|
79
|
+
httpx.Response 对象
|
80
|
+
|
81
|
+
注意:
|
82
|
+
此方法使用 httpx 实现,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
|
83
|
+
"""
|
84
|
+
client = get_httpx_client()
|
85
|
+
try:
|
86
|
+
response = client.get(url=url, **kwargs)
|
87
|
+
response.raise_for_status()
|
88
|
+
return response
|
89
|
+
finally:
|
90
|
+
client.close()
|
91
|
+
|
92
|
+
|
93
|
+
def put(url: str, data: Optional[Any] = None, **kwargs) -> httpx.Response:
|
94
|
+
"""
|
95
|
+
发送增强版永不超时的 PUT 请求,使用 httpx 实现,包含重试机制
|
96
|
+
|
97
|
+
参数:
|
98
|
+
url: 请求的 URL
|
99
|
+
data: (可选) 请求体数据 (表单数据或原始数据)
|
100
|
+
**kwargs: 其他传递给 httpx.put 的参数
|
101
|
+
|
102
|
+
返回:
|
103
|
+
httpx.Response 对象
|
104
|
+
|
105
|
+
注意:
|
106
|
+
此方法使用 httpx 实现,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
|
107
|
+
"""
|
108
|
+
client = get_httpx_client()
|
109
|
+
try:
|
110
|
+
response = client.put(url=url, data=data, **kwargs)
|
111
|
+
response.raise_for_status()
|
112
|
+
return response
|
113
|
+
finally:
|
114
|
+
client.close()
|
115
|
+
|
116
|
+
|
117
|
+
def delete(url: str, **kwargs) -> httpx.Response:
|
118
|
+
"""
|
119
|
+
发送增强版永不超时的 DELETE 请求,使用 httpx 实现,包含重试机制
|
120
|
+
|
121
|
+
参数:
|
122
|
+
url: 请求的 URL
|
123
|
+
**kwargs: 其他传递给 httpx.delete 的参数
|
124
|
+
|
125
|
+
返回:
|
126
|
+
httpx.Response 对象
|
127
|
+
|
128
|
+
注意:
|
129
|
+
此方法使用 httpx 实现,包含自动重试机制,适用于解决"Response ended prematurely"等连接问题
|
130
|
+
"""
|
131
|
+
client = get_httpx_client()
|
132
|
+
try:
|
133
|
+
response = client.delete(url=url, **kwargs)
|
134
|
+
response.raise_for_status()
|
135
|
+
return response
|
136
|
+
finally:
|
137
|
+
client.close()
|
138
|
+
|
139
|
+
|
140
|
+
# 同步流式POST请求方法
|
141
|
+
def stream_post(
|
142
|
+
url: str,
|
143
|
+
data: Optional[Any] = None,
|
144
|
+
json: Optional[Dict[str, Any]] = None,
|
145
|
+
**kwargs,
|
146
|
+
) -> Generator[bytes, None, None]:
|
147
|
+
"""
|
148
|
+
发送流式 POST 请求,使用 httpx 实现,返回标准 Generator
|
149
|
+
|
150
|
+
参数:
|
151
|
+
url: 请求的 URL
|
152
|
+
data: (可选) 请求体数据 (表单数据或原始数据)
|
153
|
+
json: (可选) JSON 数据,会自动设置 Content-Type
|
154
|
+
**kwargs: 其他传递给 httpx.post 的参数
|
155
|
+
|
156
|
+
返回:
|
157
|
+
Generator[bytes, None, None]: 字节流生成器
|
158
|
+
|
159
|
+
注意:
|
160
|
+
此方法使用 httpx 实现流式请求,适用于处理大文件下载或流式响应
|
161
|
+
"""
|
162
|
+
client = get_httpx_client()
|
163
|
+
try:
|
164
|
+
with client.stream("POST", url, data=data, json=json, **kwargs) as response:
|
165
|
+
response.raise_for_status()
|
166
|
+
for chunk in response.iter_bytes():
|
167
|
+
yield chunk
|
168
|
+
finally:
|
169
|
+
client.close()
|
jarvis/jarvis_utils/utils.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
import hashlib
|
3
|
+
import json
|
3
4
|
import os
|
4
5
|
import signal
|
5
6
|
import subprocess
|
@@ -9,35 +10,24 @@ import time
|
|
9
10
|
from pathlib import Path
|
10
11
|
from typing import Any, Callable, Dict, Optional
|
11
12
|
|
12
|
-
import yaml
|
13
|
+
import yaml # type: ignore
|
13
14
|
|
14
15
|
from jarvis import __version__
|
15
|
-
from jarvis.jarvis_utils.config import (
|
16
|
-
|
16
|
+
from jarvis.jarvis_utils.config import (
|
17
|
+
get_data_dir,
|
18
|
+
get_max_big_content_size,
|
19
|
+
set_global_env_data,
|
20
|
+
)
|
17
21
|
from jarvis.jarvis_utils.embedding import get_context_token_count
|
18
|
-
from jarvis.jarvis_utils.globals import
|
19
|
-
set_interrupt)
|
22
|
+
from jarvis.jarvis_utils.globals import get_in_chat, get_interrupt, set_interrupt
|
20
23
|
from jarvis.jarvis_utils.input import get_single_line_input
|
21
24
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
22
25
|
|
23
26
|
g_config_file = None
|
24
27
|
|
25
28
|
|
26
|
-
def
|
27
|
-
"""
|
28
|
-
功能:
|
29
|
-
1. 创建不存在的jarvis_data目录
|
30
|
-
2. 加载环境变量到os.environ
|
31
|
-
3. 处理文件读取异常
|
32
|
-
4. 检查git仓库状态并在落后时更新
|
33
|
-
5. 统计当前命令使用次数
|
34
|
-
6. 注册SIGINT信号处理函数
|
35
|
-
|
36
|
-
参数:
|
37
|
-
welcome_str: 欢迎信息字符串
|
38
|
-
config_file: 配置文件路径,默认为None(使用~/.jarvis/config.yaml)
|
39
|
-
"""
|
40
|
-
# 保存原始信号处理函数
|
29
|
+
def _setup_signal_handler() -> None:
|
30
|
+
"""设置SIGINT信号处理函数"""
|
41
31
|
original_sigint = signal.getsignal(signal.SIGINT)
|
42
32
|
|
43
33
|
def sigint_handler(signum, frame):
|
@@ -50,7 +40,16 @@ def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
|
|
50
40
|
original_sigint(signum, frame)
|
51
41
|
|
52
42
|
signal.signal(signal.SIGINT, sigint_handler)
|
53
|
-
|
43
|
+
|
44
|
+
|
45
|
+
def _show_welcome_message(welcome_str: str) -> None:
|
46
|
+
"""显示欢迎信息
|
47
|
+
|
48
|
+
参数:
|
49
|
+
welcome_str: 欢迎信息字符串
|
50
|
+
"""
|
51
|
+
if not welcome_str:
|
52
|
+
return
|
54
53
|
|
55
54
|
jarvis_ascii_art = f"""
|
56
55
|
██╗ █████╗ ██████╗ ██╗ ██╗██╗███████╗
|
@@ -64,21 +63,16 @@ def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
|
|
64
63
|
https://github.com/skyfireitdiy/Jarvis
|
65
64
|
v{__version__}
|
66
65
|
"""
|
67
|
-
|
68
|
-
PrettyOutput.print_gradient_text(jarvis_ascii_art, (0, 120, 255), (0, 255, 200))
|
66
|
+
PrettyOutput.print_gradient_text(jarvis_ascii_art, (0, 120, 255), (0, 255, 200))
|
69
67
|
|
70
|
-
global g_config_file
|
71
|
-
g_config_file = config_file
|
72
68
|
|
73
|
-
|
74
|
-
|
75
|
-
# 现在获取最终的数据目录(可能被配置文件修改)
|
69
|
+
def _extract_huggingface_models() -> None:
|
70
|
+
"""解压HuggingFace模型"""
|
76
71
|
data_dir = Path(get_data_dir())
|
77
72
|
script_dir = Path(os.path.dirname(os.path.dirname(__file__)))
|
78
73
|
hf_archive = script_dir / "jarvis_data" / "huggingface.tar.gz"
|
79
|
-
|
80
|
-
# 检查并解压huggingface模型
|
81
74
|
hf_dir = data_dir / "huggingface" / "hub"
|
75
|
+
|
82
76
|
if not hf_dir.exists() and hf_archive.exists():
|
83
77
|
try:
|
84
78
|
PrettyOutput.print("正在解压HuggingFace模型...", OutputType.INFO)
|
@@ -88,13 +82,47 @@ def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
|
|
88
82
|
except Exception as e:
|
89
83
|
PrettyOutput.print(f"解压HuggingFace模型失败: {e}", OutputType.ERROR)
|
90
84
|
|
91
|
-
|
85
|
+
|
86
|
+
def _check_git_updates() -> bool:
|
87
|
+
"""检查并更新git仓库
|
88
|
+
|
89
|
+
返回:
|
90
|
+
bool: 是否需要重启进程
|
91
|
+
"""
|
92
|
+
script_dir = Path(os.path.dirname(os.path.dirname(__file__)))
|
92
93
|
from jarvis.jarvis_utils.git_utils import check_and_update_git_repo
|
93
94
|
|
94
|
-
|
95
|
-
|
95
|
+
return check_and_update_git_repo(str(script_dir))
|
96
|
+
|
97
|
+
|
98
|
+
def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
|
99
|
+
"""初始化Jarvis环境
|
100
|
+
|
101
|
+
参数:
|
102
|
+
welcome_str: 欢迎信息字符串
|
103
|
+
config_file: 配置文件路径,默认为None(使用~/.jarvis/config.yaml)
|
104
|
+
"""
|
105
|
+
# 1. 设置信号处理
|
106
|
+
_setup_signal_handler()
|
107
|
+
|
108
|
+
# 2. 统计命令使用
|
109
|
+
count_cmd_usage()
|
110
|
+
|
111
|
+
# 3. 显示欢迎信息
|
112
|
+
if welcome_str:
|
113
|
+
_show_welcome_message(welcome_str)
|
114
|
+
|
115
|
+
# 4. 设置配置文件
|
116
|
+
global g_config_file
|
117
|
+
g_config_file = config_file
|
118
|
+
load_config()
|
119
|
+
|
120
|
+
# 5. 解压模型
|
121
|
+
_extract_huggingface_models()
|
122
|
+
|
123
|
+
# 6. 检查git更新
|
124
|
+
if _check_git_updates():
|
96
125
|
os.execv(sys.executable, [sys.executable] + sys.argv)
|
97
|
-
# 如果execv失败,退出当前进程
|
98
126
|
sys.exit(0)
|
99
127
|
|
100
128
|
|
@@ -111,47 +139,142 @@ def load_config():
|
|
111
139
|
old_config_file = config_file_path.parent / "env"
|
112
140
|
if old_config_file.exists(): # 旧的配置文件存在
|
113
141
|
_read_old_config_file(old_config_file)
|
142
|
+
else:
|
143
|
+
# 生成默认配置文件
|
144
|
+
schema_path = (
|
145
|
+
Path(__file__).parent.parent / "jarvis_data" / "config_schema.json"
|
146
|
+
)
|
147
|
+
if schema_path.exists():
|
148
|
+
try:
|
149
|
+
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
150
|
+
generate_default_config(str(schema_path), str(config_file_path))
|
151
|
+
PrettyOutput.print(
|
152
|
+
f"已生成默认配置文件: {config_file_path}", OutputType.INFO
|
153
|
+
)
|
154
|
+
except Exception as e:
|
155
|
+
PrettyOutput.print(f"生成默认配置文件失败: {e}", OutputType.ERROR)
|
114
156
|
else:
|
115
|
-
|
157
|
+
_load_and_process_config(str(config_file_path.parent), str(config_file_path))
|
116
158
|
|
117
159
|
|
118
|
-
|
119
|
-
"""读取并解析YAML格式的配置文件
|
160
|
+
from typing import Tuple
|
120
161
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
3. 将配置数据保存到全局变量
|
125
|
-
4. 设置环境变量(如果配置中有ENV字段)
|
162
|
+
|
163
|
+
def _load_config_file(config_file: str) -> Tuple[str, dict]:
|
164
|
+
"""读取并解析YAML格式的配置文件
|
126
165
|
|
127
166
|
参数:
|
128
|
-
jarvis_dir: Jarvis数据目录路径
|
129
167
|
config_file: 配置文件路径
|
168
|
+
|
169
|
+
返回:
|
170
|
+
Tuple[str, dict]: (文件原始内容, 解析后的配置字典)
|
130
171
|
"""
|
131
172
|
with open(config_file, "r", encoding="utf-8") as f:
|
132
173
|
content = f.read()
|
133
174
|
config_data = yaml.safe_load(content) or {}
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
|
175
|
+
return content, config_data
|
176
|
+
|
177
|
+
|
178
|
+
def _ensure_schema_declaration(
|
179
|
+
jarvis_dir: str, config_file: str, content: str, config_data: dict
|
180
|
+
) -> None:
|
181
|
+
"""确保配置文件包含schema声明
|
182
|
+
|
183
|
+
参数:
|
184
|
+
jarvis_dir: Jarvis数据目录路径
|
185
|
+
config_file: 配置文件路径
|
186
|
+
content: 配置文件原始内容
|
187
|
+
config_data: 解析后的配置字典
|
188
|
+
"""
|
189
|
+
if (
|
190
|
+
isinstance(config_data, dict)
|
191
|
+
and "# yaml-language-server: $schema=" not in content
|
192
|
+
):
|
193
|
+
schema_path = Path(
|
194
|
+
os.path.relpath(
|
195
|
+
Path(__file__).parent.parent / "jarvis_data" / "config_schema.json",
|
196
|
+
start=jarvis_dir,
|
154
197
|
)
|
198
|
+
)
|
199
|
+
with open(config_file, "w", encoding="utf-8") as f:
|
200
|
+
f.write(f"# yaml-language-server: $schema={schema_path}\n")
|
201
|
+
f.write(content)
|
202
|
+
|
203
|
+
|
204
|
+
def _process_env_variables(config_data: dict) -> None:
|
205
|
+
"""处理配置中的环境变量
|
206
|
+
|
207
|
+
参数:
|
208
|
+
config_data: 解析后的配置字典
|
209
|
+
"""
|
210
|
+
if "ENV" in config_data and isinstance(config_data["ENV"], dict):
|
211
|
+
os.environ.update(
|
212
|
+
{str(k): str(v) for k, v in config_data["ENV"].items() if v is not None}
|
213
|
+
)
|
214
|
+
|
215
|
+
|
216
|
+
def _load_and_process_config(jarvis_dir: str, config_file: str) -> None:
|
217
|
+
"""加载并处理配置文件
|
218
|
+
|
219
|
+
功能:
|
220
|
+
1. 读取配置文件
|
221
|
+
2. 确保schema声明存在
|
222
|
+
3. 保存配置到全局变量
|
223
|
+
4. 处理环境变量
|
224
|
+
|
225
|
+
参数:
|
226
|
+
jarvis_dir: Jarvis数据目录路径
|
227
|
+
config_file: 配置文件路径
|
228
|
+
"""
|
229
|
+
content, config_data = _load_config_file(config_file)
|
230
|
+
_ensure_schema_declaration(jarvis_dir, config_file, content, config_data)
|
231
|
+
set_global_env_data(config_data)
|
232
|
+
_process_env_variables(config_data)
|
233
|
+
|
234
|
+
|
235
|
+
def generate_default_config(schema_path: str, output_path: str) -> None:
|
236
|
+
"""从schema文件生成默认的YAML格式配置文件
|
237
|
+
|
238
|
+
功能:
|
239
|
+
1. 从schema文件读取配置结构
|
240
|
+
2. 根据schema中的default值生成默认配置
|
241
|
+
3. 自动添加schema声明
|
242
|
+
4. 处理嵌套的schema结构
|
243
|
+
5. 保留注释和格式
|
244
|
+
|
245
|
+
参数:
|
246
|
+
schema_path: schema文件路径
|
247
|
+
output_path: 生成的配置文件路径
|
248
|
+
"""
|
249
|
+
with open(schema_path, "r", encoding="utf-8") as f:
|
250
|
+
schema = json.load(f)
|
251
|
+
|
252
|
+
def _generate_from_schema(schema_dict: Dict[str, Any]) -> Dict[str, Any]:
|
253
|
+
config = {}
|
254
|
+
if "properties" in schema_dict:
|
255
|
+
for key, value in schema_dict["properties"].items():
|
256
|
+
if "default" in value:
|
257
|
+
config[key] = value["default"]
|
258
|
+
elif "properties" in value: # 处理嵌套对象
|
259
|
+
config[key] = _generate_from_schema(value)
|
260
|
+
elif value.get("type") == "array": # 处理列表类型
|
261
|
+
config[key] = []
|
262
|
+
return config
|
263
|
+
|
264
|
+
default_config = _generate_from_schema(schema)
|
265
|
+
|
266
|
+
# 添加schema声明
|
267
|
+
rel_schema_path = Path(
|
268
|
+
os.path.relpath(
|
269
|
+
Path(schema_path),
|
270
|
+
start=Path(output_path).parent,
|
271
|
+
)
|
272
|
+
)
|
273
|
+
content = f"# yaml-language-server: $schema={rel_schema_path}\n"
|
274
|
+
content += yaml.dump(default_config, allow_unicode=True, sort_keys=False)
|
275
|
+
|
276
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
277
|
+
f.write(content)
|
155
278
|
|
156
279
|
|
157
280
|
def _read_old_config_file(config_file):
|