jarvis-ai-assistant 0.2.7__py3-none-any.whl → 0.3.0__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 +267 -240
- jarvis/jarvis_agent/agent_manager.py +85 -0
- jarvis/jarvis_agent/config_editor.py +53 -0
- jarvis/jarvis_agent/file_methodology_manager.py +105 -0
- jarvis/jarvis_agent/jarvis.py +37 -398
- jarvis/jarvis_agent/memory_manager.py +133 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompts.py +18 -3
- jarvis/jarvis_agent/share_manager.py +176 -0
- jarvis/jarvis_agent/task_analyzer.py +126 -0
- jarvis/jarvis_agent/task_manager.py +111 -0
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_code_agent/code_agent.py +26 -20
- jarvis/jarvis_data/config_schema.json +37 -0
- jarvis/jarvis_platform/ai8.py +13 -1
- jarvis/jarvis_platform/base.py +20 -5
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +10 -0
- jarvis/jarvis_platform/openai.py +20 -0
- jarvis/jarvis_platform/tongyi.py +14 -9
- jarvis/jarvis_platform/yuanbao.py +10 -0
- jarvis/jarvis_platform_manager/main.py +12 -12
- jarvis/jarvis_tools/registry.py +79 -20
- jarvis/jarvis_tools/retrieve_memory.py +36 -8
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +64 -0
- jarvis/jarvis_utils/git_utils.py +17 -7
- jarvis/jarvis_utils/globals.py +18 -12
- jarvis/jarvis_utils/input.py +118 -16
- jarvis/jarvis_utils/methodology.py +48 -5
- jarvis/jarvis_utils/utils.py +196 -106
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/RECORD +38 -28
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/top_level.txt +0 -0
jarvis/jarvis_utils/input.py
CHANGED
@@ -9,40 +9,141 @@
|
|
9
9
|
- 用于输入控制的自定义键绑定
|
10
10
|
"""
|
11
11
|
import os
|
12
|
-
from typing import Iterable
|
12
|
+
from typing import Iterable, List
|
13
13
|
|
14
14
|
from colorama import Fore
|
15
|
-
from colorama import Style as ColoramaStyle
|
16
|
-
from fuzzywuzzy import process
|
17
|
-
from prompt_toolkit import PromptSession
|
18
|
-
from prompt_toolkit.
|
15
|
+
from colorama import Style as ColoramaStyle
|
16
|
+
from fuzzywuzzy import process
|
17
|
+
from prompt_toolkit import PromptSession
|
18
|
+
from prompt_toolkit.application import Application
|
19
|
+
from prompt_toolkit.completion import CompleteEvent
|
19
20
|
from prompt_toolkit.completion import (
|
20
21
|
Completer,
|
21
22
|
Completion,
|
22
23
|
PathCompleter,
|
23
24
|
)
|
24
|
-
from prompt_toolkit.document import Document
|
25
|
-
from prompt_toolkit.formatted_text import FormattedText
|
26
|
-
from prompt_toolkit.history import FileHistory
|
27
|
-
from prompt_toolkit.key_binding import KeyBindings
|
28
|
-
from prompt_toolkit.
|
29
|
-
|
25
|
+
from prompt_toolkit.document import Document
|
26
|
+
from prompt_toolkit.formatted_text import FormattedText
|
27
|
+
from prompt_toolkit.history import FileHistory
|
28
|
+
from prompt_toolkit.key_binding import KeyBindings
|
29
|
+
from prompt_toolkit.layout.containers import Window
|
30
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
31
|
+
from prompt_toolkit.layout.layout import Layout
|
32
|
+
from prompt_toolkit.styles import Style as PromptStyle
|
33
|
+
|
34
|
+
from jarvis.jarvis_utils.clipboard import copy_to_clipboard
|
30
35
|
from jarvis.jarvis_utils.config import get_data_dir, get_replace_map
|
36
|
+
from jarvis.jarvis_utils.globals import get_message_history
|
31
37
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
32
38
|
from jarvis.jarvis_utils.tag import ot
|
33
|
-
from jarvis.jarvis_utils.utils import copy_to_clipboard
|
34
39
|
|
35
40
|
# Sentinel value to indicate that Ctrl+O was pressed
|
36
41
|
CTRL_O_SENTINEL = "__CTRL_O_PRESSED__"
|
37
42
|
|
38
43
|
|
39
|
-
def get_single_line_input(tip: str) -> str:
|
44
|
+
def get_single_line_input(tip: str, default: str = "") -> str:
|
40
45
|
"""
|
41
46
|
获取支持历史记录的单行输入。
|
42
47
|
"""
|
43
48
|
session: PromptSession = PromptSession(history=None)
|
44
49
|
style = PromptStyle.from_dict({"prompt": "ansicyan"})
|
45
|
-
return session.prompt(f"{tip}", style=style)
|
50
|
+
return session.prompt(f"{tip}", default=default, style=style)
|
51
|
+
|
52
|
+
|
53
|
+
def get_choice(tip: str, choices: List[str]) -> str:
|
54
|
+
"""
|
55
|
+
提供一个可滚动的选择列表供用户选择。
|
56
|
+
"""
|
57
|
+
if not choices:
|
58
|
+
raise ValueError("Choices cannot be empty.")
|
59
|
+
|
60
|
+
try:
|
61
|
+
terminal_height = os.get_terminal_size().lines
|
62
|
+
except OSError:
|
63
|
+
terminal_height = 25 # 如果无法确定终端大小,则使用默认高度
|
64
|
+
|
65
|
+
# 为提示和缓冲区保留行
|
66
|
+
max_visible_choices = max(5, terminal_height - 4)
|
67
|
+
|
68
|
+
bindings = KeyBindings()
|
69
|
+
selected_index = 0
|
70
|
+
start_index = 0
|
71
|
+
|
72
|
+
@bindings.add("up")
|
73
|
+
def _(event):
|
74
|
+
nonlocal selected_index, start_index
|
75
|
+
selected_index = (selected_index - 1 + len(choices)) % len(choices)
|
76
|
+
if selected_index < start_index:
|
77
|
+
start_index = selected_index
|
78
|
+
elif selected_index == len(choices) - 1: # 支持从第一项上翻到最后一项时滚动
|
79
|
+
start_index = max(0, len(choices) - max_visible_choices)
|
80
|
+
event.app.invalidate()
|
81
|
+
|
82
|
+
@bindings.add("down")
|
83
|
+
def _(event):
|
84
|
+
nonlocal selected_index, start_index
|
85
|
+
selected_index = (selected_index + 1) % len(choices)
|
86
|
+
if selected_index >= start_index + max_visible_choices:
|
87
|
+
start_index = selected_index - max_visible_choices + 1
|
88
|
+
elif selected_index == 0: # 支持从最后一项下翻到第一项时滚动
|
89
|
+
start_index = 0
|
90
|
+
event.app.invalidate()
|
91
|
+
|
92
|
+
@bindings.add("enter")
|
93
|
+
def _(event):
|
94
|
+
event.app.exit(result=choices[selected_index])
|
95
|
+
|
96
|
+
def get_prompt_tokens():
|
97
|
+
tokens = [("class:question", f"{tip} (使用上下箭头选择, Enter确认)\n")]
|
98
|
+
|
99
|
+
end_index = min(start_index + max_visible_choices, len(choices))
|
100
|
+
visible_choices_slice = choices[start_index:end_index]
|
101
|
+
|
102
|
+
if start_index > 0:
|
103
|
+
tokens.append(("class:indicator", " ... (更多选项在上方) ...\n"))
|
104
|
+
|
105
|
+
for i, choice in enumerate(visible_choices_slice, start=start_index):
|
106
|
+
if i == selected_index:
|
107
|
+
tokens.append(("class:selected", f"> {choice}\n"))
|
108
|
+
else:
|
109
|
+
tokens.append(("", f" {choice}\n"))
|
110
|
+
|
111
|
+
if end_index < len(choices):
|
112
|
+
tokens.append(("class:indicator", " ... (更多选项在下方) ...\n"))
|
113
|
+
|
114
|
+
return FormattedText(tokens)
|
115
|
+
|
116
|
+
style = PromptStyle.from_dict(
|
117
|
+
{
|
118
|
+
"question": "bold",
|
119
|
+
"selected": "bg:#696969 #ffffff",
|
120
|
+
"indicator": "fg:gray",
|
121
|
+
}
|
122
|
+
)
|
123
|
+
|
124
|
+
layout = Layout(
|
125
|
+
container=Window(
|
126
|
+
content=FormattedTextControl(
|
127
|
+
text=get_prompt_tokens,
|
128
|
+
focusable=True,
|
129
|
+
key_bindings=bindings,
|
130
|
+
)
|
131
|
+
)
|
132
|
+
)
|
133
|
+
|
134
|
+
app: Application = Application(
|
135
|
+
layout=layout,
|
136
|
+
key_bindings=bindings,
|
137
|
+
style=style,
|
138
|
+
mouse_support=True,
|
139
|
+
full_screen=True,
|
140
|
+
)
|
141
|
+
|
142
|
+
try:
|
143
|
+
result = app.run()
|
144
|
+
return result if result is not None else ""
|
145
|
+
except (KeyboardInterrupt, EOFError):
|
146
|
+
return ""
|
46
147
|
|
47
148
|
|
48
149
|
class FileCompleter(Completer):
|
@@ -160,7 +261,6 @@ def _show_history_and_copy():
|
|
160
261
|
Displays message history and handles copying to clipboard.
|
161
262
|
This function uses standard I/O and is safe to call outside a prompt session.
|
162
263
|
"""
|
163
|
-
from jarvis.jarvis_utils.globals import get_message_history
|
164
264
|
|
165
265
|
history = get_message_history()
|
166
266
|
if not history:
|
@@ -170,7 +270,9 @@ def _show_history_and_copy():
|
|
170
270
|
print("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20)
|
171
271
|
for i, msg in enumerate(history):
|
172
272
|
cleaned_msg = msg.replace("\n", r"\n")
|
173
|
-
display_msg = (
|
273
|
+
display_msg = (
|
274
|
+
(cleaned_msg[:70] + "...") if len(cleaned_msg) > 70 else cleaned_msg
|
275
|
+
)
|
174
276
|
print(f" {i + 1}: {display_msg.strip()}")
|
175
277
|
print("=" * 58 + "\n")
|
176
278
|
|
@@ -19,10 +19,12 @@ from jarvis.jarvis_utils.config import (
|
|
19
19
|
get_data_dir,
|
20
20
|
get_methodology_dirs,
|
21
21
|
get_central_methodology_repo,
|
22
|
+
get_max_input_token_count,
|
22
23
|
)
|
23
24
|
from jarvis.jarvis_utils.globals import get_agent, current_agent_name
|
24
25
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
25
26
|
from jarvis.jarvis_utils.utils import is_context_overflow, daily_check_git_updates
|
27
|
+
from jarvis.jarvis_utils.embedding import get_context_token_count
|
26
28
|
|
27
29
|
|
28
30
|
def _get_methodology_directory() -> str:
|
@@ -273,14 +275,16 @@ def load_methodology(user_input: str, tool_registery: Optional[Any] = None) -> s
|
|
273
275
|
if not selected_methodologies:
|
274
276
|
return "没有历史方法论可参考"
|
275
277
|
|
278
|
+
# 获取最大输入token数的2/3作为方法论的token限制
|
279
|
+
max_input_tokens = get_max_input_token_count()
|
280
|
+
methodology_token_limit = int(max_input_tokens * 2 / 3)
|
281
|
+
|
276
282
|
# 步骤3:将选择出来的方法论内容提供给大模型生成步骤
|
277
|
-
|
283
|
+
# 首先构建基础提示词部分
|
284
|
+
base_prompt = f"""以下是与用户需求相关的方法论内容:
|
278
285
|
|
279
286
|
"""
|
280
|
-
|
281
|
-
final_prompt += f"## {problem_type}\n\n{content}\n\n---\n\n"
|
282
|
-
|
283
|
-
final_prompt += f"""以下是所有可用的工具内容:
|
287
|
+
suffix_prompt = f"""以下是所有可用的工具内容:
|
284
288
|
|
285
289
|
{prompt}
|
286
290
|
|
@@ -300,6 +304,45 @@ def load_methodology(user_input: str, tool_registery: Optional[Any] = None) -> s
|
|
300
304
|
除以上要求外,不要输出任何内容
|
301
305
|
"""
|
302
306
|
|
307
|
+
# 计算基础部分的token数
|
308
|
+
base_tokens = get_context_token_count(base_prompt + suffix_prompt)
|
309
|
+
available_tokens = methodology_token_limit - base_tokens
|
310
|
+
|
311
|
+
# 基于token限制筛选方法论内容
|
312
|
+
final_prompt = base_prompt
|
313
|
+
selected_count = 0
|
314
|
+
total_methodology_tokens = 0
|
315
|
+
|
316
|
+
for problem_type, content in selected_methodologies.items():
|
317
|
+
methodology_text = f"## {problem_type}\n\n{content}\n\n---\n\n"
|
318
|
+
methodology_tokens = get_context_token_count(methodology_text)
|
319
|
+
|
320
|
+
# 检查是否会超过token限制
|
321
|
+
if total_methodology_tokens + methodology_tokens > available_tokens:
|
322
|
+
PrettyOutput.print(
|
323
|
+
f"达到方法论token限制 ({total_methodology_tokens}/{available_tokens}),停止加载更多方法论",
|
324
|
+
OutputType.INFO,
|
325
|
+
)
|
326
|
+
break
|
327
|
+
|
328
|
+
final_prompt += methodology_text
|
329
|
+
total_methodology_tokens += methodology_tokens
|
330
|
+
selected_count += 1
|
331
|
+
|
332
|
+
# 如果一个方法论都没有加载成功
|
333
|
+
if selected_count == 0:
|
334
|
+
PrettyOutput.print(
|
335
|
+
"警告:由于token限制,无法加载任何方法论内容", OutputType.WARNING
|
336
|
+
)
|
337
|
+
return "没有历史方法论可参考"
|
338
|
+
|
339
|
+
final_prompt += suffix_prompt
|
340
|
+
|
341
|
+
PrettyOutput.print(
|
342
|
+
f"成功加载 {selected_count} 个方法论,总token数: {total_methodology_tokens}",
|
343
|
+
OutputType.INFO,
|
344
|
+
)
|
345
|
+
|
303
346
|
# 如果内容不大,直接使用chat_until_success
|
304
347
|
return platform.chat_until_success(final_prompt)
|
305
348
|
|
jarvis/jarvis_utils/utils.py
CHANGED
@@ -330,11 +330,18 @@ def _show_usage_stats() -> None:
|
|
330
330
|
if total_changes > 0:
|
331
331
|
parts.append(f"代码修改 {total_changes:,} 次")
|
332
332
|
if total_lines_modified > 0:
|
333
|
-
parts.append(f"
|
333
|
+
parts.append(f"修改代码行数 {total_lines_modified:,} 行")
|
334
334
|
|
335
335
|
if parts:
|
336
336
|
summary_content.append(f"📈 总计: {', '.join(parts)}")
|
337
337
|
|
338
|
+
# 添加代码采纳率显示
|
339
|
+
adoption_metrics = categorized_stats["adoption"]["metrics"]
|
340
|
+
if "adoption_rate" in adoption_metrics:
|
341
|
+
summary_content.append(
|
342
|
+
f"✅ 代码采纳率: {adoption_metrics['adoption_rate']}"
|
343
|
+
)
|
344
|
+
|
338
345
|
# 计算节省的时间
|
339
346
|
time_saved_seconds = 0
|
340
347
|
tool_stats = categorized_stats["tool"]["metrics"]
|
@@ -484,6 +491,153 @@ def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
|
|
484
491
|
sys.exit(0)
|
485
492
|
|
486
493
|
|
494
|
+
def _interactive_config_setup(config_file_path: Path):
|
495
|
+
"""交互式配置引导"""
|
496
|
+
from jarvis.jarvis_platform.registry import PlatformRegistry
|
497
|
+
from jarvis.jarvis_utils.input import (
|
498
|
+
get_choice,
|
499
|
+
get_single_line_input as get_input,
|
500
|
+
user_confirm as get_yes_no,
|
501
|
+
)
|
502
|
+
|
503
|
+
PrettyOutput.print(
|
504
|
+
"欢迎使用 Jarvis!未找到配置文件,现在开始引导配置。", OutputType.INFO
|
505
|
+
)
|
506
|
+
|
507
|
+
# 1. 选择平台
|
508
|
+
registry = PlatformRegistry.get_global_platform_registry()
|
509
|
+
platforms = registry.get_available_platforms()
|
510
|
+
platform_name = get_choice("请选择您要使用的AI平台", platforms)
|
511
|
+
|
512
|
+
# 2. 配置环境变量
|
513
|
+
platform_class = registry.platforms.get(platform_name)
|
514
|
+
if not platform_class:
|
515
|
+
PrettyOutput.print(f"平台 '{platform_name}' 加载失败。", OutputType.ERROR)
|
516
|
+
sys.exit(1)
|
517
|
+
|
518
|
+
env_vars = {}
|
519
|
+
required_keys = platform_class.get_required_env_keys()
|
520
|
+
defaults = platform_class.get_env_defaults()
|
521
|
+
if required_keys:
|
522
|
+
PrettyOutput.print(
|
523
|
+
f"请输入 {platform_name} 平台所需的配置信息:", OutputType.INFO
|
524
|
+
)
|
525
|
+
for key in required_keys:
|
526
|
+
default_value = defaults.get(key, "")
|
527
|
+
prompt_text = f" - {key}"
|
528
|
+
if default_value:
|
529
|
+
prompt_text += f" (默认: {default_value})"
|
530
|
+
prompt_text += ": "
|
531
|
+
|
532
|
+
value = get_input(prompt_text, default=default_value)
|
533
|
+
env_vars[key] = value
|
534
|
+
os.environ[key] = value # 立即设置环境变量以便后续测试
|
535
|
+
|
536
|
+
# 3. 选择模型
|
537
|
+
try:
|
538
|
+
platform_instance = registry.create_platform(platform_name)
|
539
|
+
if not platform_instance:
|
540
|
+
PrettyOutput.print(f"无法创建平台 '{platform_name}'。", OutputType.ERROR)
|
541
|
+
sys.exit(1)
|
542
|
+
|
543
|
+
model_list_tuples = platform_instance.get_model_list()
|
544
|
+
model_choices = [f"{name} ({desc})" for name, desc in model_list_tuples]
|
545
|
+
model_display_name = get_choice("请选择要使用的模型", model_choices)
|
546
|
+
|
547
|
+
# 从显示名称反向查找模型ID
|
548
|
+
selected_index = model_choices.index(model_display_name)
|
549
|
+
model_name, _ = model_list_tuples[selected_index]
|
550
|
+
|
551
|
+
except Exception:
|
552
|
+
PrettyOutput.print("获取模型列表失败", OutputType.ERROR)
|
553
|
+
if not get_yes_no("无法获取模型列表,是否继续配置?"):
|
554
|
+
sys.exit(1)
|
555
|
+
model_name = get_input("请输入模型名称:")
|
556
|
+
|
557
|
+
# 4. 测试配置
|
558
|
+
PrettyOutput.print("正在测试配置...", OutputType.INFO)
|
559
|
+
test_passed = False
|
560
|
+
try:
|
561
|
+
platform_instance = registry.create_platform(platform_name)
|
562
|
+
if platform_instance:
|
563
|
+
platform_instance.set_model_name(model_name)
|
564
|
+
response_generator = platform_instance.chat("hello")
|
565
|
+
response = "".join(response_generator)
|
566
|
+
if response:
|
567
|
+
PrettyOutput.print(
|
568
|
+
f"测试成功,模型响应: {response}", OutputType.SUCCESS
|
569
|
+
)
|
570
|
+
test_passed = True
|
571
|
+
else:
|
572
|
+
PrettyOutput.print("测试失败,模型没有响应。", OutputType.ERROR)
|
573
|
+
else:
|
574
|
+
PrettyOutput.print("测试失败,无法创建平台实例。", OutputType.ERROR)
|
575
|
+
except Exception:
|
576
|
+
PrettyOutput.print("测试失败", OutputType.ERROR)
|
577
|
+
|
578
|
+
# 5. 生成并保存配置
|
579
|
+
config_data = {
|
580
|
+
"ENV": env_vars,
|
581
|
+
"JARVIS_PLATFORM": platform_name,
|
582
|
+
"JARVIS_THINKING_PLATFORM": platform_name,
|
583
|
+
"JARVIS_MODEL": model_name,
|
584
|
+
"JARVIS_THINKING_MODEL": model_name,
|
585
|
+
}
|
586
|
+
|
587
|
+
if test_passed:
|
588
|
+
PrettyOutput.print("配置已测试通过,将为您生成配置文件。", OutputType.SUCCESS)
|
589
|
+
else:
|
590
|
+
if not get_yes_no("配置测试失败,您确定要保存这个配置吗?"):
|
591
|
+
PrettyOutput.print("配置未保存。", OutputType.INFO)
|
592
|
+
sys.exit(0)
|
593
|
+
|
594
|
+
try:
|
595
|
+
schema_path = (
|
596
|
+
Path(__file__).parent.parent / "jarvis_data" / "config_schema.json"
|
597
|
+
)
|
598
|
+
if schema_path.exists():
|
599
|
+
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
600
|
+
# 使用现有的函数生成默认结构,然后覆盖引导配置
|
601
|
+
generate_default_config(str(schema_path.absolute()), str(config_file_path))
|
602
|
+
|
603
|
+
# 读取刚生成的默认配置
|
604
|
+
with open(config_file_path, "r", encoding="utf-8") as f:
|
605
|
+
content = f.read()
|
606
|
+
default_config = yaml.safe_load(
|
607
|
+
content.split("\n", 1)[1]
|
608
|
+
) # 跳过 schema 行
|
609
|
+
|
610
|
+
# 合并用户配置
|
611
|
+
if default_config is None:
|
612
|
+
default_config = {}
|
613
|
+
default_config.update(config_data)
|
614
|
+
|
615
|
+
# 写回合并后的配置
|
616
|
+
final_content = (
|
617
|
+
f"# yaml-language-server: $schema={str(schema_path.absolute())}\n"
|
618
|
+
)
|
619
|
+
final_content += yaml.dump(
|
620
|
+
default_config, allow_unicode=True, sort_keys=False
|
621
|
+
)
|
622
|
+
with open(config_file_path, "w", encoding="utf-8") as f:
|
623
|
+
f.write(final_content)
|
624
|
+
|
625
|
+
PrettyOutput.print(
|
626
|
+
f"配置文件已生成: {config_file_path}", OutputType.SUCCESS
|
627
|
+
)
|
628
|
+
PrettyOutput.print("配置完成,请重新启动Jarvis。", OutputType.INFO)
|
629
|
+
sys.exit(0)
|
630
|
+
else:
|
631
|
+
PrettyOutput.print(
|
632
|
+
"未找到config schema,无法生成配置文件。", OutputType.ERROR
|
633
|
+
)
|
634
|
+
sys.exit(1)
|
635
|
+
|
636
|
+
except Exception:
|
637
|
+
PrettyOutput.print("生成配置文件失败", OutputType.ERROR)
|
638
|
+
sys.exit(1)
|
639
|
+
|
640
|
+
|
487
641
|
def load_config():
|
488
642
|
config_file = g_config_file
|
489
643
|
config_file_path = (
|
@@ -498,22 +652,7 @@ def load_config():
|
|
498
652
|
if old_config_file.exists(): # 旧的配置文件存在
|
499
653
|
_read_old_config_file(old_config_file)
|
500
654
|
else:
|
501
|
-
|
502
|
-
schema_path = (
|
503
|
-
Path(__file__).parent.parent / "jarvis_data" / "config_schema.json"
|
504
|
-
)
|
505
|
-
if schema_path.exists():
|
506
|
-
try:
|
507
|
-
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
508
|
-
generate_default_config(
|
509
|
-
str(schema_path.absolute()), str(config_file_path)
|
510
|
-
)
|
511
|
-
PrettyOutput.print(
|
512
|
-
f"已生成默认配置文件: {config_file_path}", OutputType.INFO
|
513
|
-
)
|
514
|
-
sys.exit(0)
|
515
|
-
except Exception as e:
|
516
|
-
PrettyOutput.print(f"生成默认配置文件失败: {e}", OutputType.ERROR)
|
655
|
+
_interactive_config_setup(config_file_path)
|
517
656
|
else:
|
518
657
|
_load_and_process_config(str(config_file_path.parent), str(config_file_path))
|
519
658
|
|
@@ -521,6 +660,9 @@ def load_config():
|
|
521
660
|
from typing import Tuple
|
522
661
|
|
523
662
|
|
663
|
+
from typing import Tuple
|
664
|
+
|
665
|
+
|
524
666
|
def _load_config_file(config_file: str) -> Tuple[str, dict]:
|
525
667
|
"""读取并解析YAML格式的配置文件
|
526
668
|
|
@@ -587,10 +729,24 @@ def _load_and_process_config(jarvis_dir: str, config_file: str) -> None:
|
|
587
729
|
jarvis_dir: Jarvis数据目录路径
|
588
730
|
config_file: 配置文件路径
|
589
731
|
"""
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
732
|
+
from jarvis.jarvis_utils.input import user_confirm as get_yes_no
|
733
|
+
|
734
|
+
try:
|
735
|
+
content, config_data = _load_config_file(config_file)
|
736
|
+
_ensure_schema_declaration(jarvis_dir, config_file, content, config_data)
|
737
|
+
set_global_env_data(config_data)
|
738
|
+
_process_env_variables(config_data)
|
739
|
+
except Exception:
|
740
|
+
PrettyOutput.print("加载配置文件失败", OutputType.ERROR)
|
741
|
+
if get_yes_no("配置文件格式错误,是否删除并重新配置?"):
|
742
|
+
try:
|
743
|
+
os.remove(config_file)
|
744
|
+
PrettyOutput.print(
|
745
|
+
"已删除损坏的配置文件,请重启Jarvis以重新配置。", OutputType.SUCCESS
|
746
|
+
)
|
747
|
+
except Exception:
|
748
|
+
PrettyOutput.print("删除配置文件失败", OutputType.ERROR)
|
749
|
+
sys.exit(1)
|
594
750
|
|
595
751
|
|
596
752
|
def generate_default_config(schema_path: str, output_path: str) -> None:
|
@@ -804,91 +960,6 @@ def get_loc_stats() -> str:
|
|
804
960
|
return ""
|
805
961
|
|
806
962
|
|
807
|
-
def copy_to_clipboard(text: str) -> None:
|
808
|
-
"""将文本复制到剪贴板,支持Windows、macOS和Linux
|
809
|
-
|
810
|
-
参数:
|
811
|
-
text: 要复制的文本
|
812
|
-
"""
|
813
|
-
print("--- 剪贴板内容开始 ---")
|
814
|
-
print(text)
|
815
|
-
print("--- 剪贴板内容结束 ---")
|
816
|
-
|
817
|
-
system = platform.system()
|
818
|
-
|
819
|
-
# Windows系统
|
820
|
-
if system == "Windows":
|
821
|
-
try:
|
822
|
-
# 使用Windows的clip命令
|
823
|
-
process = subprocess.Popen(
|
824
|
-
["clip"],
|
825
|
-
stdin=subprocess.PIPE,
|
826
|
-
stdout=subprocess.DEVNULL,
|
827
|
-
stderr=subprocess.DEVNULL,
|
828
|
-
shell=True,
|
829
|
-
)
|
830
|
-
if process.stdin:
|
831
|
-
process.stdin.write(text.encode("utf-8"))
|
832
|
-
process.stdin.close()
|
833
|
-
return
|
834
|
-
except Exception as e:
|
835
|
-
PrettyOutput.print(f"使用Windows clip命令时出错: {e}", OutputType.WARNING)
|
836
|
-
|
837
|
-
# macOS系统
|
838
|
-
elif system == "Darwin":
|
839
|
-
try:
|
840
|
-
process = subprocess.Popen(
|
841
|
-
["pbcopy"],
|
842
|
-
stdin=subprocess.PIPE,
|
843
|
-
stdout=subprocess.DEVNULL,
|
844
|
-
stderr=subprocess.DEVNULL,
|
845
|
-
)
|
846
|
-
if process.stdin:
|
847
|
-
process.stdin.write(text.encode("utf-8"))
|
848
|
-
process.stdin.close()
|
849
|
-
return
|
850
|
-
except Exception as e:
|
851
|
-
PrettyOutput.print(f"使用macOS pbcopy命令时出错: {e}", OutputType.WARNING)
|
852
|
-
|
853
|
-
# Linux系统
|
854
|
-
else:
|
855
|
-
# 尝试使用 xsel
|
856
|
-
try:
|
857
|
-
process = subprocess.Popen(
|
858
|
-
["xsel", "-b", "-i"],
|
859
|
-
stdin=subprocess.PIPE,
|
860
|
-
stdout=subprocess.DEVNULL,
|
861
|
-
stderr=subprocess.DEVNULL,
|
862
|
-
)
|
863
|
-
if process.stdin:
|
864
|
-
process.stdin.write(text.encode("utf-8"))
|
865
|
-
process.stdin.close()
|
866
|
-
return
|
867
|
-
except FileNotFoundError:
|
868
|
-
pass # xsel 未安装,继续尝试下一个
|
869
|
-
except Exception as e:
|
870
|
-
PrettyOutput.print(f"使用xsel时出错: {e}", OutputType.WARNING)
|
871
|
-
|
872
|
-
# 尝试使用 xclip
|
873
|
-
try:
|
874
|
-
process = subprocess.Popen(
|
875
|
-
["xclip", "-selection", "clipboard"],
|
876
|
-
stdin=subprocess.PIPE,
|
877
|
-
stdout=subprocess.DEVNULL,
|
878
|
-
stderr=subprocess.DEVNULL,
|
879
|
-
)
|
880
|
-
if process.stdin:
|
881
|
-
process.stdin.write(text.encode("utf-8"))
|
882
|
-
process.stdin.close()
|
883
|
-
return
|
884
|
-
except FileNotFoundError:
|
885
|
-
PrettyOutput.print(
|
886
|
-
"xsel 和 xclip 均未安装, 无法复制到剪贴板", OutputType.WARNING
|
887
|
-
)
|
888
|
-
except Exception as e:
|
889
|
-
PrettyOutput.print(f"使用xclip时出错: {e}", OutputType.WARNING)
|
890
|
-
|
891
|
-
|
892
963
|
def _pull_git_repo(repo_path: Path, repo_type: str):
|
893
964
|
"""对指定的git仓库执行git pull操作,并根据commit hash判断是否有更新。"""
|
894
965
|
git_dir = repo_path / ".git"
|
@@ -946,6 +1017,25 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
|
|
946
1017
|
)
|
947
1018
|
before_hash = before_hash_result.stdout.strip()
|
948
1019
|
|
1020
|
+
# 检查是否是空仓库
|
1021
|
+
ls_remote_result = subprocess.run(
|
1022
|
+
["git", "ls-remote", "--heads", "origin"],
|
1023
|
+
cwd=repo_path,
|
1024
|
+
capture_output=True,
|
1025
|
+
text=True,
|
1026
|
+
encoding="utf-8",
|
1027
|
+
errors="replace",
|
1028
|
+
check=True,
|
1029
|
+
timeout=10,
|
1030
|
+
)
|
1031
|
+
|
1032
|
+
if not ls_remote_result.stdout.strip():
|
1033
|
+
PrettyOutput.print(
|
1034
|
+
f"{repo_type}库 '{repo_path.name}' 的远程仓库是空的,跳过更新。",
|
1035
|
+
OutputType.INFO,
|
1036
|
+
)
|
1037
|
+
return
|
1038
|
+
|
949
1039
|
# 执行 git pull
|
950
1040
|
pull_result = subprocess.run(
|
951
1041
|
["git", "pull"],
|