jarvis-ai-assistant 0.2.2__py3-none-any.whl → 0.2.4__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/edit_file_handler.py +5 -0
- jarvis/jarvis_agent/jarvis.py +22 -25
- jarvis/jarvis_agent/main.py +6 -6
- jarvis/jarvis_agent/prompts.py +26 -4
- jarvis/jarvis_code_agent/code_agent.py +279 -11
- jarvis/jarvis_code_analysis/code_review.py +21 -19
- jarvis/jarvis_data/config_schema.json +86 -18
- jarvis/jarvis_git_squash/main.py +3 -3
- jarvis/jarvis_git_utils/git_commiter.py +32 -11
- jarvis/jarvis_mcp/sse_mcp_client.py +4 -6
- jarvis/jarvis_mcp/streamable_mcp_client.py +5 -9
- jarvis/jarvis_platform/tongyi.py +9 -9
- jarvis/jarvis_rag/cli.py +79 -23
- jarvis/jarvis_rag/query_rewriter.py +61 -12
- jarvis/jarvis_rag/rag_pipeline.py +143 -34
- jarvis/jarvis_rag/retriever.py +6 -6
- jarvis/jarvis_smart_shell/main.py +2 -2
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +337 -0
- jarvis/jarvis_stats/stats.py +433 -0
- jarvis/jarvis_stats/storage.py +329 -0
- jarvis/jarvis_stats/visualizer.py +443 -0
- jarvis/jarvis_tools/cli/main.py +84 -15
- jarvis/jarvis_tools/generate_new_tool.py +22 -1
- jarvis/jarvis_tools/registry.py +35 -16
- jarvis/jarvis_tools/search_web.py +3 -3
- jarvis/jarvis_tools/virtual_tty.py +315 -26
- jarvis/jarvis_utils/config.py +98 -11
- jarvis/jarvis_utils/git_utils.py +8 -16
- jarvis/jarvis_utils/globals.py +29 -8
- jarvis/jarvis_utils/input.py +114 -121
- jarvis/jarvis_utils/utils.py +213 -37
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/METADATA +99 -9
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/RECORD +39 -34
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/entry_points.txt +2 -0
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/top_level.txt +0 -0
jarvis/jarvis_utils/globals.py
CHANGED
@@ -9,9 +9,11 @@
|
|
9
9
|
"""
|
10
10
|
import os
|
11
11
|
|
12
|
-
#
|
13
|
-
|
14
|
-
|
12
|
+
# 全局变量:保存消息历史
|
13
|
+
from typing import Any, Dict, Set, List
|
14
|
+
|
15
|
+
message_history: List[str] = []
|
16
|
+
MAX_HISTORY_SIZE = 50
|
15
17
|
|
16
18
|
import colorama
|
17
19
|
from rich.console import Console
|
@@ -169,13 +171,18 @@ def get_interrupt() -> int:
|
|
169
171
|
|
170
172
|
def set_last_message(message: str) -> None:
|
171
173
|
"""
|
172
|
-
|
174
|
+
将消息添加到历史记录中。
|
173
175
|
|
174
176
|
参数:
|
175
177
|
message: 要保存的消息
|
176
178
|
"""
|
177
|
-
global
|
178
|
-
|
179
|
+
global message_history
|
180
|
+
if message:
|
181
|
+
# 避免重复添加
|
182
|
+
if not message_history or message_history[-1] != message:
|
183
|
+
message_history.append(message)
|
184
|
+
if len(message_history) > MAX_HISTORY_SIZE:
|
185
|
+
message_history.pop(0)
|
179
186
|
|
180
187
|
|
181
188
|
def get_last_message() -> str:
|
@@ -183,6 +190,20 @@ def get_last_message() -> str:
|
|
183
190
|
获取最后一条消息。
|
184
191
|
|
185
192
|
返回:
|
186
|
-
str:
|
193
|
+
str: 最后一条消息,如果历史记录为空则返回空字符串
|
194
|
+
"""
|
195
|
+
global message_history
|
196
|
+
if message_history:
|
197
|
+
return message_history[-1]
|
198
|
+
return ""
|
199
|
+
|
200
|
+
|
201
|
+
def get_message_history() -> List[str]:
|
202
|
+
"""
|
203
|
+
获取完整的消息历史记录。
|
204
|
+
|
205
|
+
返回:
|
206
|
+
List[str]: 消息历史列表
|
187
207
|
"""
|
188
|
-
|
208
|
+
global message_history
|
209
|
+
return message_history
|
jarvis/jarvis_utils/input.py
CHANGED
@@ -8,99 +8,73 @@
|
|
8
8
|
- 带有模糊匹配的文件路径补全
|
9
9
|
- 用于输入控制的自定义键绑定
|
10
10
|
"""
|
11
|
-
|
11
|
+
import os
|
12
|
+
from typing import Iterable
|
13
|
+
|
14
|
+
from colorama import Fore
|
12
15
|
from colorama import Style as ColoramaStyle # type: ignore
|
13
16
|
from fuzzywuzzy import process # type: ignore
|
14
17
|
from prompt_toolkit import PromptSession # type: ignore
|
18
|
+
from prompt_toolkit.completion import CompleteEvent # type: ignore
|
15
19
|
from prompt_toolkit.completion import (
|
16
|
-
CompleteEvent,
|
17
20
|
Completer,
|
18
|
-
Completion,
|
21
|
+
Completion,
|
19
22
|
PathCompleter,
|
20
|
-
)
|
23
|
+
)
|
21
24
|
from prompt_toolkit.document import Document # type: ignore
|
22
25
|
from prompt_toolkit.formatted_text import FormattedText # type: ignore
|
26
|
+
from prompt_toolkit.history import FileHistory # type: ignore
|
23
27
|
from prompt_toolkit.key_binding import KeyBindings # type: ignore
|
24
28
|
from prompt_toolkit.styles import Style as PromptStyle # type: ignore
|
25
29
|
|
26
|
-
from jarvis.jarvis_utils.config import get_replace_map
|
30
|
+
from jarvis.jarvis_utils.config import get_data_dir, get_replace_map
|
27
31
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
28
32
|
from jarvis.jarvis_utils.tag import ot
|
29
33
|
from jarvis.jarvis_utils.utils import copy_to_clipboard
|
30
34
|
|
35
|
+
# Sentinel value to indicate that Ctrl+O was pressed
|
36
|
+
CTRL_O_SENTINEL = "__CTRL_O_PRESSED__"
|
37
|
+
|
31
38
|
|
32
39
|
def get_single_line_input(tip: str) -> str:
|
33
40
|
"""
|
34
41
|
获取支持历史记录的单行输入。
|
35
|
-
|
36
|
-
参数:
|
37
|
-
tip: 要显示的提示信息
|
38
|
-
|
39
|
-
返回:
|
40
|
-
str: 用户的输入
|
41
42
|
"""
|
42
43
|
session: PromptSession = PromptSession(history=None)
|
43
|
-
style = PromptStyle.from_dict(
|
44
|
-
{
|
45
|
-
"prompt": "ansicyan",
|
46
|
-
}
|
47
|
-
)
|
44
|
+
style = PromptStyle.from_dict({"prompt": "ansicyan"})
|
48
45
|
return session.prompt(f"{tip}", style=style)
|
49
46
|
|
50
47
|
|
51
48
|
class FileCompleter(Completer):
|
52
49
|
"""
|
53
50
|
带有模糊匹配的文件路径自定义补全器。
|
54
|
-
|
55
|
-
属性:
|
56
|
-
path_completer: 基础路径补全器
|
57
|
-
max_suggestions: 显示的最大建议数量
|
58
|
-
min_score: 建议的最小匹配分数
|
59
51
|
"""
|
60
52
|
|
61
53
|
def __init__(self):
|
62
|
-
"""使用默认设置初始化文件补全器。"""
|
63
54
|
self.path_completer = PathCompleter()
|
64
55
|
self.max_suggestions = 10
|
65
56
|
self.min_score = 10
|
66
57
|
self.replace_map = get_replace_map()
|
67
58
|
|
68
|
-
def get_completions(
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
参数:
|
73
|
-
document: 当前正在编辑的文档
|
74
|
-
complete_event: 补全事件
|
75
|
-
|
76
|
-
生成:
|
77
|
-
Completion: 建议的补全项
|
78
|
-
"""
|
59
|
+
def get_completions(
|
60
|
+
self, document: Document, _: CompleteEvent
|
61
|
+
) -> Iterable[Completion]:
|
79
62
|
text = document.text_before_cursor
|
80
63
|
cursor_pos = document.cursor_position
|
81
|
-
# 查找文本中的所有@位置
|
82
64
|
at_positions = [i for i, char in enumerate(text) if char == "@"]
|
83
65
|
if not at_positions:
|
84
66
|
return
|
85
|
-
# 获取最后一个@位置
|
86
67
|
current_at_pos = at_positions[-1]
|
87
|
-
# 如果光标不在最后一个@之后,则不补全
|
88
68
|
if cursor_pos <= current_at_pos:
|
89
69
|
return
|
90
|
-
# 检查@之后是否有空格
|
91
70
|
text_after_at = text[current_at_pos + 1 : cursor_pos]
|
92
71
|
if " " in text_after_at:
|
93
72
|
return
|
94
73
|
|
95
|
-
# 获取当前@之后的文本
|
96
74
|
file_path = text_after_at.strip()
|
97
|
-
# 计算替换长度
|
98
75
|
replace_length = len(text_after_at) + 1
|
99
76
|
|
100
|
-
# 获取所有可能的补全项
|
101
77
|
all_completions = []
|
102
|
-
|
103
|
-
# 1. 添加特殊标记
|
104
78
|
all_completions.extend(
|
105
79
|
[(ot(tag), self._get_description(tag)) for tag in self.replace_map.keys()]
|
106
80
|
)
|
@@ -114,7 +88,6 @@ class FileCompleter(Completer):
|
|
114
88
|
]
|
115
89
|
)
|
116
90
|
|
117
|
-
# 2. 添加文件列表
|
118
91
|
try:
|
119
92
|
import subprocess
|
120
93
|
|
@@ -135,9 +108,7 @@ class FileCompleter(Completer):
|
|
135
108
|
except Exception:
|
136
109
|
pass
|
137
110
|
|
138
|
-
# 统一过滤和排序
|
139
111
|
if file_path:
|
140
|
-
# 使用模糊匹配过滤
|
141
112
|
scored_items = process.extract(
|
142
113
|
file_path,
|
143
114
|
[item[0] for item in all_completions],
|
@@ -146,31 +117,25 @@ class FileCompleter(Completer):
|
|
146
117
|
scored_items = [
|
147
118
|
(item[0], item[1]) for item in scored_items if item[1] > self.min_score
|
148
119
|
]
|
149
|
-
# 创建映射以便查找描述
|
150
120
|
completion_map = {item[0]: item[1] for item in all_completions}
|
151
|
-
# 生成补全项
|
152
121
|
for text, score in scored_items:
|
153
|
-
display_text = text
|
154
|
-
if score < 100:
|
155
|
-
display_text = f"{text} ({score}%)"
|
122
|
+
display_text = f"{text} ({score}%)" if score < 100 else text
|
156
123
|
yield Completion(
|
157
124
|
text=f"'{text}'",
|
158
125
|
start_position=-replace_length,
|
159
126
|
display=display_text,
|
160
127
|
display_meta=completion_map.get(text, ""),
|
161
|
-
)
|
128
|
+
)
|
162
129
|
else:
|
163
|
-
# 没有输入时返回前max_suggestions个建议
|
164
130
|
for text, desc in all_completions[: self.max_suggestions]:
|
165
131
|
yield Completion(
|
166
132
|
text=f"'{text}'",
|
167
133
|
start_position=-replace_length,
|
168
134
|
display=text,
|
169
135
|
display_meta=desc,
|
170
|
-
)
|
136
|
+
)
|
171
137
|
|
172
138
|
def _get_description(self, tag: str) -> str:
|
173
|
-
"""获取标记的描述信息"""
|
174
139
|
if tag in self.replace_map:
|
175
140
|
return (
|
176
141
|
self.replace_map[tag].get("description", tag) + "(Append)"
|
@@ -181,15 +146,7 @@ class FileCompleter(Completer):
|
|
181
146
|
|
182
147
|
|
183
148
|
def user_confirm(tip: str, default: bool = True) -> bool:
|
184
|
-
"""提示用户确认是/否问题
|
185
|
-
|
186
|
-
参数:
|
187
|
-
tip: 显示给用户的消息
|
188
|
-
default: 用户直接回车时的默认响应
|
189
|
-
|
190
|
-
返回:
|
191
|
-
bool: 用户确认返回True,否则返回False
|
192
|
-
"""
|
149
|
+
"""提示用户确认是/否问题"""
|
193
150
|
try:
|
194
151
|
suffix = "[Y/n]" if default else "[y/N]"
|
195
152
|
ret = get_single_line_input(f"{tip} {suffix}: ")
|
@@ -198,28 +155,66 @@ def user_confirm(tip: str, default: bool = True) -> bool:
|
|
198
155
|
return False
|
199
156
|
|
200
157
|
|
201
|
-
def
|
158
|
+
def _show_history_and_copy():
|
202
159
|
"""
|
203
|
-
|
160
|
+
Displays message history and handles copying to clipboard.
|
161
|
+
This function uses standard I/O and is safe to call outside a prompt session.
|
162
|
+
"""
|
163
|
+
from jarvis.jarvis_utils.globals import get_message_history
|
164
|
+
|
165
|
+
history = get_message_history()
|
166
|
+
if not history:
|
167
|
+
PrettyOutput.print("没有可复制的消息", OutputType.INFO)
|
168
|
+
return
|
204
169
|
|
205
|
-
|
206
|
-
|
170
|
+
print("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20)
|
171
|
+
for i, msg in enumerate(history):
|
172
|
+
cleaned_msg = msg.replace("\n", r"\n")
|
173
|
+
display_msg = (cleaned_msg[:70] + "...") if len(cleaned_msg) > 70 else cleaned_msg
|
174
|
+
print(f" {i + 1}: {display_msg.strip()}")
|
175
|
+
print("=" * 58 + "\n")
|
207
176
|
|
208
|
-
|
209
|
-
|
177
|
+
while True:
|
178
|
+
try:
|
179
|
+
prompt_text = f"{Fore.CYAN}请输入要复制的条目序号 (或输入c取消, 直接回车选择最后一条): {ColoramaStyle.RESET_ALL}"
|
180
|
+
choice_str = input(prompt_text)
|
181
|
+
|
182
|
+
if not choice_str: # User pressed Enter
|
183
|
+
if not history:
|
184
|
+
print("没有历史记录可供选择。")
|
185
|
+
break
|
186
|
+
choice = len(history) - 1
|
187
|
+
elif choice_str.lower() == "c":
|
188
|
+
print("已取消")
|
189
|
+
break
|
190
|
+
else:
|
191
|
+
choice = int(choice_str) - 1
|
192
|
+
|
193
|
+
if 0 <= choice < len(history):
|
194
|
+
selected_msg = history[choice]
|
195
|
+
copy_to_clipboard(selected_msg)
|
196
|
+
PrettyOutput.print(
|
197
|
+
f"已复制消息: {selected_msg[:70]}...", OutputType.SUCCESS
|
198
|
+
)
|
199
|
+
break
|
200
|
+
else:
|
201
|
+
print("无效的序号,请重试。")
|
202
|
+
except ValueError:
|
203
|
+
print("无效的输入,请输入数字。")
|
204
|
+
except (KeyboardInterrupt, EOFError):
|
205
|
+
print("\n操作取消")
|
206
|
+
break
|
207
|
+
|
208
|
+
|
209
|
+
def _get_multiline_input_internal(tip: str) -> str:
|
210
|
+
"""
|
211
|
+
Internal function to get multiline input using prompt_toolkit.
|
212
|
+
Returns a sentinel value if Ctrl+O is pressed.
|
210
213
|
"""
|
211
|
-
# 显示输入说明
|
212
|
-
PrettyOutput.section(
|
213
|
-
"用户输入 - 使用 @ 触发文件补全,Tab 选择补全项,Ctrl+J 提交,Ctrl+O 复制最后一条消息,按 Ctrl+C 取消输入",
|
214
|
-
OutputType.USER,
|
215
|
-
)
|
216
|
-
print(f"{Fore.GREEN}{tip}{ColoramaStyle.RESET_ALL}")
|
217
|
-
# 配置键绑定
|
218
214
|
bindings = KeyBindings()
|
219
215
|
|
220
216
|
@bindings.add("enter")
|
221
217
|
def _(event):
|
222
|
-
"""处理回车键以进行补全或换行。"""
|
223
218
|
if event.current_buffer.complete_state:
|
224
219
|
event.current_buffer.apply_completion(
|
225
220
|
event.current_buffer.complete_state.current_completion
|
@@ -229,55 +224,53 @@ def get_multiline_input(tip: str) -> str:
|
|
229
224
|
|
230
225
|
@bindings.add("c-j")
|
231
226
|
def _(event):
|
232
|
-
"""处理Ctrl+J以提交输入。"""
|
233
227
|
event.current_buffer.validate_and_handle()
|
234
228
|
|
235
229
|
@bindings.add("c-o")
|
236
230
|
def _(event):
|
237
|
-
"""
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
231
|
+
"""Handle Ctrl+O by exiting the prompt and returning the sentinel value."""
|
232
|
+
event.app.exit(result=CTRL_O_SENTINEL)
|
233
|
+
|
234
|
+
style = PromptStyle.from_dict({"prompt": "ansicyan"})
|
235
|
+
|
236
|
+
history_dir = get_data_dir()
|
237
|
+
session: PromptSession = PromptSession(
|
238
|
+
history=FileHistory(os.path.join(history_dir, "multiline_input_history")),
|
239
|
+
completer=FileCompleter(),
|
240
|
+
key_bindings=bindings,
|
241
|
+
complete_while_typing=True,
|
242
|
+
multiline=True,
|
243
|
+
vi_mode=False,
|
244
|
+
mouse_support=False,
|
245
|
+
)
|
246
246
|
|
247
|
-
|
247
|
+
print(f"{Fore.GREEN}{tip}{ColoramaStyle.RESET_ALL}")
|
248
|
+
prompt = FormattedText([("class:prompt", ">>> ")])
|
248
249
|
|
249
|
-
# 配置提示会话
|
250
|
-
style = PromptStyle.from_dict(
|
251
|
-
{
|
252
|
-
"prompt": "ansicyan",
|
253
|
-
}
|
254
|
-
)
|
255
250
|
try:
|
256
|
-
|
257
|
-
|
258
|
-
from prompt_toolkit.history import FileHistory # type: ignore
|
259
|
-
|
260
|
-
from jarvis.jarvis_utils.config import get_data_dir
|
261
|
-
|
262
|
-
# 获取数据目录路径
|
263
|
-
history_dir = get_data_dir()
|
264
|
-
# 初始化带历史记录的会话
|
265
|
-
session: PromptSession = PromptSession(
|
266
|
-
history=FileHistory(os.path.join(history_dir, "multiline_input_history")),
|
267
|
-
completer=FileCompleter(),
|
268
|
-
key_bindings=bindings,
|
269
|
-
complete_while_typing=True,
|
270
|
-
multiline=True,
|
271
|
-
vi_mode=False,
|
272
|
-
mouse_support=False,
|
273
|
-
)
|
274
|
-
prompt = FormattedText([("class:prompt", ">>> ")])
|
275
|
-
# 获取输入
|
276
|
-
text = session.prompt(
|
277
|
-
prompt,
|
278
|
-
style=style,
|
279
|
-
).strip()
|
280
|
-
return text
|
281
|
-
except KeyboardInterrupt:
|
282
|
-
PrettyOutput.print("输入已取消", OutputType.INFO)
|
251
|
+
return session.prompt(prompt, style=style, pre_run=lambda: None).strip()
|
252
|
+
except (KeyboardInterrupt, EOFError):
|
283
253
|
return ""
|
254
|
+
|
255
|
+
|
256
|
+
def get_multiline_input(tip: str) -> str:
|
257
|
+
"""
|
258
|
+
获取带有增强补全和确认功能的多行输入。
|
259
|
+
此函数处理控制流,允许在不破坏终端状态的情况下处理历史记录复制。
|
260
|
+
"""
|
261
|
+
PrettyOutput.section(
|
262
|
+
"用户输入 - 使用 @ 触发文件补全,Tab 选择补全项,Ctrl+J 提交,Ctrl+O 从历史记录中选择消息复制,按 Ctrl+C/D 取消输入",
|
263
|
+
OutputType.USER,
|
264
|
+
)
|
265
|
+
|
266
|
+
while True:
|
267
|
+
user_input = _get_multiline_input_internal(tip)
|
268
|
+
|
269
|
+
if user_input == CTRL_O_SENTINEL:
|
270
|
+
_show_history_and_copy()
|
271
|
+
tip = "请继续输入(或按Ctrl+J提交):"
|
272
|
+
continue
|
273
|
+
else:
|
274
|
+
if not user_input:
|
275
|
+
PrettyOutput.print("\n输入已取消", OutputType.INFO)
|
276
|
+
return user_input
|