jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.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 +1143 -245
- jarvis/jarvis_agent/agent_manager.py +97 -0
- jarvis/jarvis_agent/builtin_input_handler.py +12 -10
- jarvis/jarvis_agent/config_editor.py +57 -0
- jarvis/jarvis_agent/edit_file_handler.py +392 -99
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/events.py +157 -0
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/file_methodology_manager.py +117 -0
- jarvis/jarvis_agent/jarvis.py +1117 -147
- jarvis/jarvis_agent/main.py +78 -34
- jarvis/jarvis_agent/memory_manager.py +195 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/prompts.py +46 -9
- jarvis/jarvis_agent/protocols.py +4 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +146 -0
- jarvis/jarvis_agent/session_manager.py +9 -9
- jarvis/jarvis_agent/share_manager.py +228 -0
- jarvis/jarvis_agent/shell_input_handler.py +23 -3
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +212 -0
- jarvis/jarvis_agent/task_manager.py +154 -0
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/tool_executor.py +8 -4
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_agent/utils.py +54 -0
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1605 -178
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +275 -13
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
- jarvis/jarvis_code_analysis/code_review.py +583 -548
- jarvis/jarvis_data/config_schema.json +339 -28
- jarvis/jarvis_git_squash/main.py +22 -13
- jarvis/jarvis_git_utils/git_commiter.py +171 -55
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
- jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
- jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
- jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
- jarvis/jarvis_methodology/main.py +48 -63
- jarvis/jarvis_multi_agent/__init__.py +302 -43
- jarvis/jarvis_multi_agent/main.py +70 -24
- jarvis/jarvis_platform/ai8.py +40 -23
- jarvis/jarvis_platform/base.py +210 -49
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +82 -76
- jarvis/jarvis_platform/openai.py +73 -1
- jarvis/jarvis_platform/registry.py +8 -15
- jarvis/jarvis_platform/tongyi.py +115 -101
- jarvis/jarvis_platform/yuanbao.py +89 -63
- jarvis/jarvis_platform_manager/main.py +194 -132
- jarvis/jarvis_platform_manager/service.py +122 -86
- jarvis/jarvis_rag/cli.py +156 -53
- jarvis/jarvis_rag/embedding_manager.py +155 -12
- jarvis/jarvis_rag/llm_interface.py +10 -13
- jarvis/jarvis_rag/query_rewriter.py +63 -12
- jarvis/jarvis_rag/rag_pipeline.py +222 -40
- jarvis/jarvis_rag/reranker.py +26 -3
- jarvis/jarvis_rag/retriever.py +270 -14
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_smart_shell/main.py +405 -137
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +387 -0
- jarvis/jarvis_stats/stats.py +711 -0
- jarvis/jarvis_stats/storage.py +612 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/ask_user.py +1 -0
- jarvis/jarvis_tools/base.py +18 -2
- jarvis/jarvis_tools/clear_memory.py +239 -0
- jarvis/jarvis_tools/cli/main.py +220 -144
- jarvis/jarvis_tools/execute_script.py +52 -12
- jarvis/jarvis_tools/file_analyzer.py +17 -12
- jarvis/jarvis_tools/generate_new_tool.py +46 -24
- jarvis/jarvis_tools/read_code.py +277 -18
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +86 -13
- jarvis/jarvis_tools/registry.py +294 -90
- jarvis/jarvis_tools/retrieve_memory.py +227 -0
- jarvis/jarvis_tools/save_memory.py +194 -0
- jarvis/jarvis_tools/search_web.py +62 -28
- jarvis/jarvis_tools/sub_agent.py +205 -0
- jarvis/jarvis_tools/sub_code_agent.py +217 -0
- jarvis/jarvis_tools/virtual_tty.py +330 -62
- jarvis/jarvis_utils/builtin_replace_map.py +4 -5
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +607 -50
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/fzf.py +57 -0
- jarvis/jarvis_utils/git_utils.py +251 -29
- jarvis/jarvis_utils/globals.py +174 -17
- jarvis/jarvis_utils/http.py +58 -79
- jarvis/jarvis_utils/input.py +899 -153
- jarvis/jarvis_utils/methodology.py +210 -83
- jarvis/jarvis_utils/output.py +220 -137
- jarvis/jarvis_utils/utils.py +1906 -135
- jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
- jarvis/jarvis_git_details/main.py +0 -265
- jarvis/jarvis_platform/oyi.py +0 -357
- jarvis/jarvis_tools/edit_file.py +0 -255
- jarvis/jarvis_tools/rewrite_file.py +0 -195
- jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
- jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
- /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -1,27 +1,61 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
import
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
import yaml # type: ignore[import-untyped]
|
|
6
|
+
import os
|
|
3
7
|
|
|
4
8
|
from jarvis.jarvis_multi_agent import MultiAgent
|
|
5
9
|
from jarvis.jarvis_utils.input import get_multiline_input
|
|
6
10
|
from jarvis.jarvis_utils.utils import init_env
|
|
11
|
+
from jarvis.jarvis_utils.config import set_config
|
|
12
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
7
13
|
|
|
14
|
+
app = typer.Typer(help="多智能体系统启动器")
|
|
8
15
|
|
|
9
|
-
def main():
|
|
10
|
-
"""从YAML配置文件初始化并运行多智能体系统
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"""
|
|
17
|
+
@app.command()
|
|
18
|
+
def cli(
|
|
19
|
+
config: str = typer.Option(..., "--config", "-c", help="YAML配置文件路径"),
|
|
20
|
+
user_input: Optional[str] = typer.Option(
|
|
21
|
+
None, "--input", "-i", help="用户输入(可选)"
|
|
22
|
+
),
|
|
23
|
+
model_group: Optional[str] = typer.Option(
|
|
24
|
+
None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
|
|
25
|
+
),
|
|
26
|
+
non_interactive: bool = typer.Option(
|
|
27
|
+
False, "-n", "--non-interactive", help="启用非交互模式:用户无法与命令交互,脚本执行超时限制为5分钟"
|
|
28
|
+
),
|
|
29
|
+
):
|
|
30
|
+
"""从YAML配置文件初始化并运行多智能体系统"""
|
|
31
|
+
# CLI 标志:非交互模式(不依赖配置文件)
|
|
32
|
+
if non_interactive:
|
|
33
|
+
try:
|
|
34
|
+
os.environ["JARVIS_NON_INTERACTIVE"] = "true"
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
# 注意:全局配置同步在 init_env 之后执行,避免被覆盖
|
|
38
|
+
# 非交互模式要求从命令行传入任务
|
|
39
|
+
if non_interactive and not (user_input and str(user_input).strip()):
|
|
40
|
+
PrettyOutput.print(
|
|
41
|
+
"非交互模式已启用:必须使用 --input 传入任务内容,因多行输入不可用。",
|
|
42
|
+
OutputType.ERROR,
|
|
43
|
+
)
|
|
44
|
+
raise typer.Exit(code=2)
|
|
15
45
|
init_env("欢迎使用 Jarvis-MultiAgent,您的多智能体系统已准备就绪!")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
46
|
+
|
|
47
|
+
# 在初始化环境后同步 CLI 选项到全局配置,避免被 init_env 覆盖
|
|
48
|
+
try:
|
|
49
|
+
if non_interactive:
|
|
50
|
+
set_config("JARVIS_NON_INTERACTIVE", True)
|
|
51
|
+
if model_group:
|
|
52
|
+
set_config("JARVIS_LLM_GROUP", str(model_group))
|
|
53
|
+
except Exception:
|
|
54
|
+
# 静默忽略同步异常,不影响主流程
|
|
55
|
+
pass
|
|
22
56
|
|
|
23
57
|
try:
|
|
24
|
-
with open(
|
|
58
|
+
with open(config, "r", errors="ignore") as f:
|
|
25
59
|
config_data = yaml.safe_load(f)
|
|
26
60
|
|
|
27
61
|
# 获取agents配置
|
|
@@ -32,21 +66,33 @@ def main():
|
|
|
32
66
|
raise ValueError("必须指定main_agent作为主智能体")
|
|
33
67
|
|
|
34
68
|
# 创建并运行多智能体系统
|
|
35
|
-
multi_agent = MultiAgent(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
69
|
+
multi_agent = MultiAgent(
|
|
70
|
+
agents_config,
|
|
71
|
+
main_agent_name,
|
|
72
|
+
common_system_prompt=str(config_data.get("common_system_prompt", "") or "")
|
|
73
|
+
)
|
|
74
|
+
final_input = (
|
|
75
|
+
user_input
|
|
76
|
+
if user_input is not None
|
|
39
77
|
else get_multiline_input("请输入内容(输入空行结束):")
|
|
40
78
|
)
|
|
41
|
-
if
|
|
79
|
+
if not final_input:
|
|
42
80
|
return
|
|
43
|
-
|
|
81
|
+
multi_agent.run(final_input)
|
|
82
|
+
|
|
83
|
+
except KeyboardInterrupt:
|
|
84
|
+
return
|
|
85
|
+
except typer.Exit:
|
|
86
|
+
return
|
|
87
|
+
except (ValueError, RuntimeError, yaml.YAMLError) as e:
|
|
88
|
+
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
|
89
|
+
raise typer.Exit(code=1)
|
|
90
|
+
|
|
44
91
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
raise RuntimeError(f"多智能体系统初始化失败: {str(e)}")
|
|
92
|
+
def main() -> None:
|
|
93
|
+
"""Application entry point."""
|
|
94
|
+
app()
|
|
49
95
|
|
|
50
96
|
|
|
51
97
|
if __name__ == "__main__":
|
|
52
|
-
|
|
98
|
+
main()
|
jarvis/jarvis_platform/ai8.py
CHANGED
|
@@ -32,22 +32,24 @@ class AI8Model(BasePlatform):
|
|
|
32
32
|
|
|
33
33
|
self.headers = {
|
|
34
34
|
"Authorization": self.token,
|
|
35
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
36
|
+
"sec-ch-ua": '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"',
|
|
37
|
+
"sec-ch-ua-mobile": "?0",
|
|
35
38
|
"Content-Type": "application/json",
|
|
36
39
|
"Accept": "application/json, text/plain, */*",
|
|
37
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
38
|
-
"X-APP-VERSION": "2.
|
|
40
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
|
41
|
+
"X-APP-VERSION": "2.4.2",
|
|
39
42
|
"Origin": self.BASE_URL,
|
|
40
43
|
"Referer": f"{self.BASE_URL}/chat?_userMenuKey=chat",
|
|
41
44
|
"Sec-Fetch-Site": "same-origin",
|
|
42
45
|
"Sec-Fetch-Mode": "cors",
|
|
43
46
|
"Sec-Fetch-Dest": "empty",
|
|
47
|
+
"Accept-Language": "zh-CN,zh;q=0.9",
|
|
48
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
49
|
+
"Connection": "keep-alive",
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
self.model_name = os.getenv("JARVIS_MODEL") or "deepseek-chat"
|
|
47
|
-
if self.model_name not in self.get_available_models():
|
|
48
|
-
PrettyOutput.print(
|
|
49
|
-
f"警告: 选择的模型 {self.model_name} 不在可用列表中", OutputType.WARNING
|
|
50
|
-
)
|
|
51
53
|
|
|
52
54
|
def set_model_name(self, model_name: str):
|
|
53
55
|
"""Set model name"""
|
|
@@ -60,7 +62,14 @@ class AI8Model(BasePlatform):
|
|
|
60
62
|
# 1. 创建会话
|
|
61
63
|
response = while_success(
|
|
62
64
|
lambda: http.post(
|
|
63
|
-
f"{self.BASE_URL}/api/chat/session",
|
|
65
|
+
f"{self.BASE_URL}/api/chat/session",
|
|
66
|
+
headers=self.headers,
|
|
67
|
+
json={
|
|
68
|
+
"mcp": [],
|
|
69
|
+
"model": self.model_name,
|
|
70
|
+
"plugins": [],
|
|
71
|
+
"rags": [],
|
|
72
|
+
},
|
|
64
73
|
),
|
|
65
74
|
sleep_time=5,
|
|
66
75
|
)
|
|
@@ -83,6 +92,7 @@ class AI8Model(BasePlatform):
|
|
|
83
92
|
"plugins": [],
|
|
84
93
|
"localPlugins": None,
|
|
85
94
|
"useAppId": 0,
|
|
95
|
+
"temperature": 0,
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
response = while_success(
|
|
@@ -127,33 +137,30 @@ class AI8Model(BasePlatform):
|
|
|
127
137
|
"files": [],
|
|
128
138
|
}
|
|
129
139
|
|
|
140
|
+
# 为流式请求构造专用的请求头,避免 'Accept' 和 'accept' 键冲突
|
|
141
|
+
stream_headers = self.headers.copy()
|
|
142
|
+
stream_headers["Accept"] = "text/event-stream" # 添加流式专用的accept头
|
|
143
|
+
|
|
130
144
|
# 使用stream_post进行流式请求
|
|
131
145
|
response_stream = while_success(
|
|
132
146
|
lambda: http.stream_post(
|
|
133
147
|
f"{self.BASE_URL}/api/chat/completions",
|
|
134
|
-
headers=
|
|
148
|
+
headers=stream_headers,
|
|
135
149
|
json=payload,
|
|
136
150
|
),
|
|
137
151
|
sleep_time=5,
|
|
138
152
|
)
|
|
139
153
|
|
|
140
154
|
# 处理流式响应
|
|
141
|
-
for
|
|
142
|
-
if
|
|
155
|
+
for line in response_stream:
|
|
156
|
+
if line and line.startswith("data: "):
|
|
143
157
|
try:
|
|
144
|
-
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if chunk_data:
|
|
151
|
-
yield chunk_data
|
|
152
|
-
|
|
153
|
-
except json.JSONDecodeError:
|
|
154
|
-
continue
|
|
155
|
-
|
|
156
|
-
except UnicodeDecodeError:
|
|
158
|
+
data = json.loads(line[6:])
|
|
159
|
+
if data.get("type") == "string":
|
|
160
|
+
chunk_data = data.get("data", "")
|
|
161
|
+
if chunk_data:
|
|
162
|
+
yield chunk_data
|
|
163
|
+
except json.JSONDecodeError:
|
|
157
164
|
continue
|
|
158
165
|
|
|
159
166
|
return None
|
|
@@ -313,3 +320,13 @@ class AI8Model(BasePlatform):
|
|
|
313
320
|
|
|
314
321
|
def upload_files(self, file_list: List[str]) -> bool:
|
|
315
322
|
return False
|
|
323
|
+
|
|
324
|
+
@classmethod
|
|
325
|
+
def get_required_env_keys(cls) -> List[str]:
|
|
326
|
+
"""
|
|
327
|
+
获取AI8平台所需的环境变量键列表
|
|
328
|
+
|
|
329
|
+
返回:
|
|
330
|
+
List[str]: 环境变量键的列表
|
|
331
|
+
"""
|
|
332
|
+
return ["AI8_API_KEY"]
|
jarvis/jarvis_platform/base.py
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
import re
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime
|
|
3
5
|
from abc import ABC, abstractmethod
|
|
4
|
-
from
|
|
6
|
+
from types import TracebackType
|
|
7
|
+
from typing import Dict, Generator, List, Optional, Tuple, Type
|
|
8
|
+
|
|
9
|
+
from typing_extensions import Self
|
|
5
10
|
|
|
6
11
|
from rich import box # type: ignore
|
|
7
12
|
from rich.live import Live # type: ignore
|
|
8
13
|
from rich.panel import Panel # type: ignore
|
|
14
|
+
from rich.status import Status # type: ignore
|
|
9
15
|
from rich.text import Text # type: ignore
|
|
10
16
|
|
|
11
17
|
from jarvis.jarvis_utils.config import (
|
|
12
18
|
get_max_input_token_count,
|
|
13
19
|
get_pretty_output,
|
|
14
20
|
is_print_prompt,
|
|
21
|
+
is_immediate_abort,
|
|
22
|
+
is_save_session_history,
|
|
23
|
+
get_data_dir,
|
|
15
24
|
)
|
|
16
25
|
from jarvis.jarvis_utils.embedding import split_text_into_chunks
|
|
17
|
-
from jarvis.jarvis_utils.globals import set_in_chat
|
|
26
|
+
from jarvis.jarvis_utils.globals import set_in_chat, get_interrupt, console
|
|
27
|
+
import jarvis.jarvis_utils.globals as G
|
|
18
28
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
19
29
|
from jarvis.jarvis_utils.tag import ct, ot
|
|
20
30
|
from jarvis.jarvis_utils.utils import get_context_token_count, while_success, while_true
|
|
@@ -28,9 +38,20 @@ class BasePlatform(ABC):
|
|
|
28
38
|
self.suppress_output = True # 添加输出控制标志
|
|
29
39
|
self.web = False # 添加web属性,默认false
|
|
30
40
|
self._saved = False
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
self.model_group: Optional[str] = None
|
|
42
|
+
self._session_history_file: Optional[str] = None
|
|
43
|
+
|
|
44
|
+
def __enter__(self) -> Self:
|
|
45
|
+
"""Enter context manager"""
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def __exit__(
|
|
49
|
+
self,
|
|
50
|
+
exc_type: Optional[Type[BaseException]],
|
|
51
|
+
exc_val: Optional[BaseException],
|
|
52
|
+
exc_tb: Optional[TracebackType],
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Exit context manager"""
|
|
34
55
|
if not self._saved:
|
|
35
56
|
self.delete_chat()
|
|
36
57
|
|
|
@@ -42,6 +63,7 @@ class BasePlatform(ABC):
|
|
|
42
63
|
def reset(self):
|
|
43
64
|
"""Reset model"""
|
|
44
65
|
self.delete_chat()
|
|
66
|
+
self._session_history_file = None
|
|
45
67
|
|
|
46
68
|
@abstractmethod
|
|
47
69
|
def chat(self, message: str) -> Generator[str, None, None]:
|
|
@@ -62,31 +84,37 @@ class BasePlatform(ABC):
|
|
|
62
84
|
|
|
63
85
|
start_time = time.time()
|
|
64
86
|
|
|
87
|
+
# 当输入为空白字符串时,打印警告并直接返回空字符串
|
|
88
|
+
if message.strip() == "":
|
|
89
|
+
PrettyOutput.print("输入为空白字符串,已忽略本次请求", OutputType.WARNING)
|
|
90
|
+
return ""
|
|
91
|
+
|
|
65
92
|
input_token_count = get_context_token_count(message)
|
|
66
93
|
|
|
67
|
-
if input_token_count > get_max_input_token_count():
|
|
68
|
-
max_chunk_size =
|
|
69
|
-
|
|
94
|
+
if input_token_count > get_max_input_token_count(self.model_group):
|
|
95
|
+
max_chunk_size = (
|
|
96
|
+
get_max_input_token_count(self.model_group) - 1024
|
|
97
|
+
) # 留出一些余量
|
|
98
|
+
min_chunk_size = get_max_input_token_count(self.model_group) - 2048
|
|
70
99
|
inputs = split_text_into_chunks(message, max_chunk_size, min_chunk_size)
|
|
71
|
-
print(
|
|
72
|
-
|
|
100
|
+
PrettyOutput.print(
|
|
101
|
+
f"长上下文,分批提交,共{len(inputs)}部分...", OutputType.INFO
|
|
102
|
+
)
|
|
103
|
+
prefix_prompt = """
|
|
73
104
|
我将分多次提供大量内容,在我明确告诉你内容已经全部提供完毕之前,每次仅需要输出"已收到",明白请输出"开始接收输入"。
|
|
74
105
|
"""
|
|
75
|
-
while_true(lambda: while_success(lambda: self.
|
|
106
|
+
while_true(lambda: while_success(lambda: self._chat(prefix_prompt), 5), 5)
|
|
76
107
|
submit_count = 0
|
|
77
108
|
length = 0
|
|
78
109
|
response = ""
|
|
79
110
|
for input in inputs:
|
|
80
111
|
submit_count += 1
|
|
81
112
|
length += len(input)
|
|
82
|
-
print(
|
|
83
|
-
f"📤 正在提交第{submit_count}部分(共{len(inputs)}部分({length}/{len(message)}))"
|
|
84
|
-
)
|
|
85
113
|
|
|
86
114
|
response += "\n"
|
|
87
115
|
for trunk in while_true(
|
|
88
116
|
lambda: while_success(
|
|
89
|
-
lambda: self.
|
|
117
|
+
lambda: self._chat(
|
|
90
118
|
f"<part_content>{input}</part_content>\n\n请返回<已收到>,不需要返回其他任何内容"
|
|
91
119
|
),
|
|
92
120
|
5,
|
|
@@ -95,10 +123,7 @@ class BasePlatform(ABC):
|
|
|
95
123
|
):
|
|
96
124
|
response += trunk
|
|
97
125
|
|
|
98
|
-
|
|
99
|
-
f"📤 提交第{submit_count}部分完成,当前进度:{length}/{len(message)}"
|
|
100
|
-
)
|
|
101
|
-
print("✅ 提交完成")
|
|
126
|
+
PrettyOutput.print("提交完成", OutputType.SUCCESS)
|
|
102
127
|
response += "\n" + while_true(
|
|
103
128
|
lambda: while_success(
|
|
104
129
|
lambda: self._chat("内容已经全部提供完毕,请根据内容继续"), 5
|
|
@@ -108,48 +133,112 @@ class BasePlatform(ABC):
|
|
|
108
133
|
else:
|
|
109
134
|
response = ""
|
|
110
135
|
|
|
111
|
-
text_content = Text()
|
|
112
|
-
panel = Panel(
|
|
113
|
-
text_content,
|
|
114
|
-
title=f"[bold cyan]{self.name()}[/bold cyan]",
|
|
115
|
-
subtitle="[dim]思考中...[/dim]",
|
|
116
|
-
border_style="bright_blue",
|
|
117
|
-
box=box.ROUNDED,
|
|
118
|
-
)
|
|
119
|
-
|
|
120
136
|
if not self.suppress_output:
|
|
121
137
|
if get_pretty_output():
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
char_count = len(response)
|
|
131
|
-
# Calculate token count and tokens per second
|
|
138
|
+
chat_iterator = self.chat(message)
|
|
139
|
+
first_chunk = None
|
|
140
|
+
|
|
141
|
+
with Status(
|
|
142
|
+
f"🤔 {(G.current_agent_name + ' · ') if G.current_agent_name else ''}{self.name()} 正在思考中...",
|
|
143
|
+
spinner="dots",
|
|
144
|
+
console=console,
|
|
145
|
+
):
|
|
132
146
|
try:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
while True:
|
|
148
|
+
first_chunk = next(chat_iterator)
|
|
149
|
+
if first_chunk:
|
|
150
|
+
break
|
|
151
|
+
except StopIteration:
|
|
152
|
+
self._append_session_history(message, "")
|
|
153
|
+
return ""
|
|
154
|
+
|
|
155
|
+
text_content = Text(overflow="fold")
|
|
156
|
+
panel = Panel(
|
|
157
|
+
text_content,
|
|
158
|
+
title=f"[bold cyan]{(G.current_agent_name + ' · ') if G.current_agent_name else ''}{self.name()}[/bold cyan]",
|
|
159
|
+
subtitle="[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]",
|
|
160
|
+
border_style="bright_blue",
|
|
161
|
+
box=box.ROUNDED,
|
|
162
|
+
expand=True, # 允许面板自动调整大小
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
with Live(panel, refresh_per_second=4, transient=False) as live:
|
|
166
|
+
|
|
167
|
+
def _update_panel_content(content: str):
|
|
168
|
+
text_content.append(content, style="bright_white")
|
|
169
|
+
# --- Scrolling Logic ---
|
|
170
|
+
# Calculate available height in the panel
|
|
171
|
+
max_text_height = (
|
|
172
|
+
console.height - 5
|
|
173
|
+
) # Leave space for borders/titles
|
|
174
|
+
if max_text_height <= 0:
|
|
175
|
+
max_text_height = 1
|
|
176
|
+
|
|
177
|
+
# Get the actual number of lines the text will wrap to
|
|
178
|
+
lines = text_content.wrap(
|
|
179
|
+
console,
|
|
180
|
+
console.width - 4 if console.width > 4 else 1,
|
|
136
181
|
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
182
|
+
|
|
183
|
+
# If content overflows, truncate to show only the last few lines
|
|
184
|
+
if len(lines) > max_text_height:
|
|
185
|
+
# Rebuild the text from the wrapped lines to ensure visual consistency
|
|
186
|
+
# This correctly handles both wrapped long lines and explicit newlines
|
|
187
|
+
text_content.plain = "\n".join(
|
|
188
|
+
[line.plain for line in lines[-max_text_height:]]
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
panel.subtitle = (
|
|
192
|
+
"[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]"
|
|
140
193
|
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
194
|
+
live.update(panel)
|
|
195
|
+
|
|
196
|
+
# Process first chunk
|
|
197
|
+
response += first_chunk
|
|
198
|
+
if first_chunk:
|
|
199
|
+
_update_panel_content(first_chunk)
|
|
200
|
+
|
|
201
|
+
# Process rest of the chunks
|
|
202
|
+
for s in chat_iterator:
|
|
203
|
+
if not s:
|
|
204
|
+
continue
|
|
205
|
+
response += s # Accumulate the full response string
|
|
206
|
+
_update_panel_content(s)
|
|
207
|
+
|
|
208
|
+
if is_immediate_abort() and get_interrupt():
|
|
209
|
+
self._append_session_history(message, response)
|
|
210
|
+
return response # Return the partial response immediately
|
|
211
|
+
|
|
212
|
+
# At the end, display the entire response
|
|
213
|
+
text_content.plain = response
|
|
214
|
+
|
|
215
|
+
end_time = time.time()
|
|
216
|
+
duration = end_time - start_time
|
|
217
|
+
panel.subtitle = f"[bold green]✓ 对话完成耗时: {duration:.2f}秒[/bold green]"
|
|
144
218
|
live.update(panel)
|
|
219
|
+
console.print()
|
|
145
220
|
else:
|
|
221
|
+
# Print a clear prefix line before streaming model output (non-pretty mode)
|
|
222
|
+
console.print(
|
|
223
|
+
f"🤖 模型输出 - {(G.current_agent_name + ' · ') if G.current_agent_name else ''}{self.name()} (按 Ctrl+C 中断)",
|
|
224
|
+
soft_wrap=False,
|
|
225
|
+
)
|
|
146
226
|
for s in self.chat(message):
|
|
147
|
-
print(s, end=""
|
|
227
|
+
console.print(s, end="")
|
|
148
228
|
response += s
|
|
149
|
-
|
|
229
|
+
if is_immediate_abort() and get_interrupt():
|
|
230
|
+
self._append_session_history(message, response)
|
|
231
|
+
return response
|
|
232
|
+
console.print()
|
|
233
|
+
end_time = time.time()
|
|
234
|
+
duration = end_time - start_time
|
|
235
|
+
console.print(f"✓ 对话完成耗时: {duration:.2f}秒")
|
|
150
236
|
else:
|
|
151
237
|
for s in self.chat(message):
|
|
152
238
|
response += s
|
|
239
|
+
if is_immediate_abort() and get_interrupt():
|
|
240
|
+
self._append_session_history(message, response)
|
|
241
|
+
return response
|
|
153
242
|
# Keep original think tag handling
|
|
154
243
|
response = re.sub(
|
|
155
244
|
ot("think") + r".*?" + ct("think"), "", response, flags=re.DOTALL
|
|
@@ -157,6 +246,8 @@ class BasePlatform(ABC):
|
|
|
157
246
|
response = re.sub(
|
|
158
247
|
ot("thinking") + r".*?" + ct("thinking"), "", response, flags=re.DOTALL
|
|
159
248
|
)
|
|
249
|
+
# Save session history (input and full response)
|
|
250
|
+
self._append_session_history(message, response)
|
|
160
251
|
return response
|
|
161
252
|
|
|
162
253
|
def chat_until_success(self, message: str) -> str:
|
|
@@ -229,14 +320,84 @@ class BasePlatform(ABC):
|
|
|
229
320
|
"""Get model list"""
|
|
230
321
|
raise NotImplementedError("get_model_list is not implemented")
|
|
231
322
|
|
|
323
|
+
@classmethod
|
|
324
|
+
@abstractmethod
|
|
325
|
+
def get_required_env_keys(cls) -> List[str]:
|
|
326
|
+
"""Get required env keys"""
|
|
327
|
+
raise NotImplementedError("get_required_env_keys is not implemented")
|
|
328
|
+
|
|
329
|
+
@classmethod
|
|
330
|
+
def get_env_defaults(cls) -> Dict[str, str]:
|
|
331
|
+
"""Get env default values"""
|
|
332
|
+
return {}
|
|
333
|
+
|
|
334
|
+
@classmethod
|
|
335
|
+
def get_env_config_guide(cls) -> Dict[str, str]:
|
|
336
|
+
"""Get environment variable configuration guide
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Dict[str, str]: A dictionary mapping env key names to their configuration instructions
|
|
340
|
+
"""
|
|
341
|
+
return {}
|
|
342
|
+
|
|
232
343
|
def set_suppress_output(self, suppress: bool):
|
|
233
344
|
"""Set whether to suppress output"""
|
|
234
345
|
self.suppress_output = suppress
|
|
235
346
|
|
|
347
|
+
def set_model_group(self, model_group: Optional[str]):
|
|
348
|
+
"""Set model group"""
|
|
349
|
+
self.model_group = model_group
|
|
350
|
+
|
|
236
351
|
def set_web(self, web: bool):
|
|
237
352
|
"""Set web flag"""
|
|
238
353
|
self.web = web
|
|
239
354
|
|
|
355
|
+
def _append_session_history(self, user_input: str, model_output: str) -> None:
|
|
356
|
+
"""
|
|
357
|
+
Append the user input and model output to a session history file if enabled.
|
|
358
|
+
The file name is generated on first save and reused until reset.
|
|
359
|
+
"""
|
|
360
|
+
try:
|
|
361
|
+
if not is_save_session_history():
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
if self._session_history_file is None:
|
|
365
|
+
# Ensure session history directory exists under data directory
|
|
366
|
+
data_dir = get_data_dir()
|
|
367
|
+
session_dir = os.path.join(data_dir, "session_history")
|
|
368
|
+
os.makedirs(session_dir, exist_ok=True)
|
|
369
|
+
|
|
370
|
+
# Build a safe filename including platform, model and timestamp
|
|
371
|
+
try:
|
|
372
|
+
platform_name = type(self).platform_name()
|
|
373
|
+
except Exception:
|
|
374
|
+
platform_name = "unknown_platform"
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
model_name = self.name()
|
|
378
|
+
except Exception:
|
|
379
|
+
model_name = "unknown_model"
|
|
380
|
+
|
|
381
|
+
safe_platform = re.sub(r"[^\w\-\.]+", "_", str(platform_name))
|
|
382
|
+
safe_model = re.sub(r"[^\w\-\.]+", "_", str(model_name))
|
|
383
|
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
384
|
+
|
|
385
|
+
self._session_history_file = os.path.join(
|
|
386
|
+
session_dir, f"session_history_{safe_platform}_{safe_model}_{ts}.log"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Append record
|
|
390
|
+
with open(self._session_history_file, "a", encoding="utf-8", errors="ignore") as f:
|
|
391
|
+
ts_line = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
392
|
+
f.write(f"===== {ts_line} =====\n")
|
|
393
|
+
f.write("USER:\n")
|
|
394
|
+
f.write(f"{user_input}\n")
|
|
395
|
+
f.write("\nASSISTANT:\n")
|
|
396
|
+
f.write(f"{model_output}\n\n")
|
|
397
|
+
except Exception:
|
|
398
|
+
# Do not break chat flow if writing history fails
|
|
399
|
+
pass
|
|
400
|
+
|
|
240
401
|
@abstractmethod
|
|
241
402
|
def support_web(self) -> bool:
|
|
242
403
|
"""Check if platform supports web functionality"""
|
jarvis/jarvis_platform/human.py
CHANGED
|
@@ -10,9 +10,9 @@ import string
|
|
|
10
10
|
from typing import Generator, List, Tuple
|
|
11
11
|
|
|
12
12
|
from jarvis.jarvis_platform.base import BasePlatform
|
|
13
|
+
from jarvis.jarvis_utils.clipboard import copy_to_clipboard
|
|
13
14
|
from jarvis.jarvis_utils.input import get_multiline_input
|
|
14
15
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
15
|
-
from jarvis.jarvis_utils.utils import copy_to_clipboard
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class HumanPlatform(BasePlatform):
|
|
@@ -131,3 +131,13 @@ class HumanPlatform(BasePlatform):
|
|
|
131
131
|
def support_upload_files(self) -> bool:
|
|
132
132
|
"""是否支持文件上传功能"""
|
|
133
133
|
return False
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def get_required_env_keys(cls) -> List[str]:
|
|
137
|
+
"""
|
|
138
|
+
获取Human平台所需的环境变量键列表
|
|
139
|
+
|
|
140
|
+
返回:
|
|
141
|
+
List[str]: 环境变量键的列表
|
|
142
|
+
"""
|
|
143
|
+
return []
|