jarvis-ai-assistant 0.3.18__py3-none-any.whl → 0.3.19__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 +21 -10
- jarvis/jarvis_agent/edit_file_handler.py +8 -13
- jarvis/jarvis_agent/memory_manager.py +4 -4
- jarvis/jarvis_agent/task_analyzer.py +4 -3
- jarvis/jarvis_agent/task_manager.py +6 -6
- jarvis/jarvis_agent/tool_executor.py +2 -2
- jarvis/jarvis_code_agent/code_agent.py +21 -29
- jarvis/jarvis_code_analysis/code_review.py +2 -4
- jarvis/jarvis_git_utils/git_commiter.py +17 -18
- jarvis/jarvis_methodology/main.py +12 -12
- jarvis/jarvis_platform/base.py +14 -13
- jarvis/jarvis_platform/kimi.py +13 -13
- jarvis/jarvis_platform/tongyi.py +17 -15
- jarvis/jarvis_platform/yuanbao.py +11 -11
- jarvis/jarvis_rag/cli.py +36 -32
- jarvis/jarvis_rag/embedding_manager.py +11 -6
- jarvis/jarvis_rag/llm_interface.py +6 -5
- jarvis/jarvis_rag/rag_pipeline.py +9 -8
- jarvis/jarvis_rag/reranker.py +3 -2
- jarvis/jarvis_rag/retriever.py +18 -8
- jarvis/jarvis_smart_shell/main.py +306 -46
- jarvis/jarvis_stats/stats.py +40 -0
- jarvis/jarvis_stats/storage.py +220 -9
- jarvis/jarvis_tools/clear_memory.py +0 -11
- jarvis/jarvis_tools/cli/main.py +18 -17
- jarvis/jarvis_tools/edit_file.py +4 -4
- jarvis/jarvis_tools/execute_script.py +5 -1
- jarvis/jarvis_tools/file_analyzer.py +6 -6
- jarvis/jarvis_tools/generate_new_tool.py +6 -17
- jarvis/jarvis_tools/read_code.py +3 -6
- jarvis/jarvis_tools/read_webpage.py +4 -4
- jarvis/jarvis_tools/registry.py +8 -28
- jarvis/jarvis_tools/retrieve_memory.py +5 -16
- jarvis/jarvis_tools/rewrite_file.py +0 -4
- jarvis/jarvis_tools/save_memory.py +2 -10
- jarvis/jarvis_tools/search_web.py +5 -8
- jarvis/jarvis_tools/virtual_tty.py +22 -40
- jarvis/jarvis_utils/clipboard.py +3 -3
- jarvis/jarvis_utils/input.py +67 -27
- jarvis/jarvis_utils/methodology.py +3 -3
- jarvis/jarvis_utils/output.py +1 -7
- jarvis/jarvis_utils/utils.py +35 -58
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/RECORD +49 -49
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/top_level.txt +0 -0
jarvis/jarvis_tools/registry.py
CHANGED
@@ -287,10 +287,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
287
287
|
for tool_name in dont_use_list:
|
288
288
|
if tool_name in self.tools:
|
289
289
|
del self.tools[tool_name]
|
290
|
-
|
291
|
-
f"已排除工具: {tool_name}",
|
292
|
-
OutputType.INFO,
|
293
|
-
)
|
290
|
+
|
294
291
|
|
295
292
|
def _load_mcp_tools(self) -> None:
|
296
293
|
"""加载MCP工具,优先从配置获取,其次从目录扫描"""
|
@@ -354,9 +351,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
354
351
|
try:
|
355
352
|
import subprocess
|
356
353
|
|
357
|
-
|
358
|
-
f"正在克隆中心工具仓库: {central_repo}", OutputType.INFO
|
359
|
-
)
|
354
|
+
|
360
355
|
subprocess.run(
|
361
356
|
["git", "clone", central_repo, central_repo_path], check=True
|
362
357
|
)
|
@@ -399,10 +394,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
399
394
|
|
400
395
|
# 检查enable标志
|
401
396
|
if not config.get("enable", True):
|
402
|
-
|
403
|
-
f"MCP配置{config.get('name', '')}已禁用(enable=false),跳过注册",
|
404
|
-
OutputType.INFO,
|
405
|
-
)
|
397
|
+
|
406
398
|
return False
|
407
399
|
|
408
400
|
name = config.get("name", "mcp")
|
@@ -414,10 +406,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
414
406
|
args.pop("agent", None)
|
415
407
|
args.pop("want", None)
|
416
408
|
ret = client.get_resource_list()
|
417
|
-
|
418
|
-
f"MCP {name} 资源列表:\n{yaml.safe_dump(ret, allow_unicode=True)}",
|
419
|
-
OutputType.TOOL,
|
420
|
-
)
|
409
|
+
|
421
410
|
return {
|
422
411
|
"success": True,
|
423
412
|
"stdout": yaml.safe_dump(ret, allow_unicode=True),
|
@@ -438,10 +427,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
438
427
|
"stderr": "缺少必需的uri参数",
|
439
428
|
}
|
440
429
|
ret = client.get_resource(args["uri"])
|
441
|
-
|
442
|
-
f"MCP {name} 获取资源:\n{yaml.safe_dump(ret, allow_unicode=True)}",
|
443
|
-
OutputType.TOOL,
|
444
|
-
)
|
430
|
+
|
445
431
|
return ret
|
446
432
|
|
447
433
|
return execute
|
@@ -452,10 +438,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
452
438
|
args.pop("agent", None)
|
453
439
|
args.pop("want", None)
|
454
440
|
ret = client.execute(tool_name, args)
|
455
|
-
|
456
|
-
f"MCP {name} {tool_name} 执行结果:\n{yaml.safe_dump(ret, allow_unicode=True)}",
|
457
|
-
OutputType.TOOL,
|
458
|
-
)
|
441
|
+
|
459
442
|
return ret
|
460
443
|
|
461
444
|
return execute
|
@@ -665,11 +648,8 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
665
648
|
yaml.safe_load(temp_data[0]) # Check if valid YAML
|
666
649
|
|
667
650
|
# Ask user for confirmation
|
668
|
-
|
669
|
-
|
670
|
-
OutputType.INFO,
|
671
|
-
)
|
672
|
-
PrettyOutput.print(fixed_content, OutputType.TOOL)
|
651
|
+
|
652
|
+
|
673
653
|
data = temp_data
|
674
654
|
except (yaml.YAMLError, EOFError, KeyboardInterrupt):
|
675
655
|
# Even after fixing, it's not valid YAML, or user cancelled.
|
@@ -147,17 +147,12 @@ class RetrieveMemoryTool:
|
|
147
147
|
|
148
148
|
# 检查是否超过token限制
|
149
149
|
if total_tokens + memory_tokens > memory_token_limit:
|
150
|
-
|
151
|
-
f"达到token限制 ({total_tokens}/{memory_token_limit}),停止加载更多记忆",
|
152
|
-
OutputType.INFO,
|
153
|
-
)
|
150
|
+
|
154
151
|
break
|
155
152
|
|
156
153
|
# 检查是否超过50条限制
|
157
154
|
if len(filtered_memories) >= 50:
|
158
|
-
|
159
|
-
f"达到记忆条数限制 (50条),停止加载更多记忆", OutputType.INFO
|
160
|
-
)
|
155
|
+
|
161
156
|
break
|
162
157
|
|
163
158
|
filtered_memories.append(memory)
|
@@ -170,10 +165,10 @@ class RetrieveMemoryTool:
|
|
170
165
|
all_memories = all_memories[:limit]
|
171
166
|
|
172
167
|
# 打印结果摘要
|
173
|
-
|
168
|
+
|
174
169
|
|
175
170
|
if tags:
|
176
|
-
|
171
|
+
pass
|
177
172
|
|
178
173
|
# 格式化为Markdown输出
|
179
174
|
markdown_output = f"# 记忆检索结果\n\n"
|
@@ -216,17 +211,11 @@ class RetrieveMemoryTool:
|
|
216
211
|
|
217
212
|
# 如果记忆较多,在终端显示摘要
|
218
213
|
if len(all_memories) > 5:
|
219
|
-
|
214
|
+
# 静默模式下不再打印摘要,完整结果已包含在返回的markdown_output中
|
220
215
|
for i, memory in enumerate(all_memories[:5]):
|
221
216
|
content_preview = memory.get("content", "")[:100]
|
222
217
|
if len(memory.get("content", "")) > 100:
|
223
218
|
content_preview += "..."
|
224
|
-
PrettyOutput.print(
|
225
|
-
f"{i+1}. [{memory.get('type')}] {memory.get('id')}\n"
|
226
|
-
f" 标签: {', '.join(memory.get('tags', []))}\n"
|
227
|
-
f" 内容: {content_preview}",
|
228
|
-
OutputType.INFO,
|
229
|
-
)
|
230
219
|
|
231
220
|
return {
|
232
221
|
"success": True,
|
@@ -114,7 +114,6 @@ class FileRewriteTool:
|
|
114
114
|
action = "创建并写入" if not file_exists else "成功重写"
|
115
115
|
stdout_message = f"文件 {abs_path} {action}"
|
116
116
|
stdout_messages.append(stdout_message)
|
117
|
-
PrettyOutput.print(stdout_message, OutputType.SUCCESS)
|
118
117
|
|
119
118
|
except Exception as e:
|
120
119
|
stderr_message = f"处理文件 {file_path} 时出错: {str(e)}"
|
@@ -126,7 +125,6 @@ class FileRewriteTool:
|
|
126
125
|
if not success and processed:
|
127
126
|
rollback_message = "操作失败,正在回滚修改..."
|
128
127
|
stderr_messages.append(rollback_message)
|
129
|
-
PrettyOutput.print(rollback_message, OutputType.WARNING)
|
130
128
|
|
131
129
|
try:
|
132
130
|
if original_content is None:
|
@@ -141,7 +139,6 @@ class FileRewriteTool:
|
|
141
139
|
rollback_file_message = f"已回滚文件: {abs_path}"
|
142
140
|
|
143
141
|
stderr_messages.append(rollback_file_message)
|
144
|
-
PrettyOutput.print(rollback_file_message, OutputType.INFO)
|
145
142
|
except Exception as e:
|
146
143
|
rollback_error = f"回滚文件 {file_path} 失败: {str(e)}"
|
147
144
|
stderr_messages.append(rollback_error)
|
@@ -172,7 +169,6 @@ class FileRewriteTool:
|
|
172
169
|
if processed:
|
173
170
|
rollback_message = "操作失败,正在回滚修改..."
|
174
171
|
stderr_messages.append(rollback_message)
|
175
|
-
PrettyOutput.print(rollback_message, OutputType.WARNING)
|
176
172
|
|
177
173
|
try:
|
178
174
|
if original_content is None:
|
@@ -159,12 +159,7 @@ class SaveMemoryTool:
|
|
159
159
|
# 打印单条记忆保存信息
|
160
160
|
memory_type = memory_data["memory_type"]
|
161
161
|
tags = memory_data.get("tags", [])
|
162
|
-
|
163
|
-
f"[{i+1}/{len(memories)}] {memory_type} 记忆已保存\n"
|
164
|
-
f"ID: {result['memory_id']}\n"
|
165
|
-
f"标签: {', '.join(tags)}",
|
166
|
-
OutputType.SUCCESS,
|
167
|
-
)
|
162
|
+
|
168
163
|
except Exception as e:
|
169
164
|
failed_count += 1
|
170
165
|
error_msg = f"保存第 {i+1} 条记忆失败: {str(e)}"
|
@@ -178,10 +173,7 @@ class SaveMemoryTool:
|
|
178
173
|
)
|
179
174
|
|
180
175
|
# 生成总结报告
|
181
|
-
|
182
|
-
f"\n批量保存完成:成功 {success_count} 条,失败 {failed_count} 条",
|
183
|
-
OutputType.INFO,
|
184
|
-
)
|
176
|
+
|
185
177
|
|
186
178
|
# 构建返回结果
|
187
179
|
output = {
|
@@ -34,7 +34,7 @@ class SearchWebTool:
|
|
34
34
|
# pylint: disable=too-many-locals, broad-except
|
35
35
|
"""执行网络搜索、抓取内容并总结结果。"""
|
36
36
|
try:
|
37
|
-
|
37
|
+
|
38
38
|
results = list(DDGS().text(query, max_results=50, page=3))
|
39
39
|
|
40
40
|
if not results:
|
@@ -50,17 +50,14 @@ class SearchWebTool:
|
|
50
50
|
|
51
51
|
for r in results:
|
52
52
|
if visited_count >= 10:
|
53
|
-
|
53
|
+
|
54
54
|
break
|
55
55
|
|
56
56
|
url = r["href"]
|
57
57
|
title = r.get("title", url)
|
58
58
|
|
59
59
|
try:
|
60
|
-
|
61
|
-
f"📄 ({visited_count + 1}/10) 正在抓取: {title} ({url})",
|
62
|
-
OutputType.INFO,
|
63
|
-
)
|
60
|
+
|
64
61
|
response = http_get(url, timeout=10.0, allow_redirects=True)
|
65
62
|
content = md(response.text, strip=["script", "style"])
|
66
63
|
if content:
|
@@ -83,9 +80,9 @@ class SearchWebTool:
|
|
83
80
|
}
|
84
81
|
|
85
82
|
url_list_str = "\n".join(f" - {u}" for u in visited_urls)
|
86
|
-
PrettyOutput.print(f"🔍 已成功访问并处理以下URL:\n{url_list_str}", OutputType.INFO)
|
87
83
|
|
88
|
-
|
84
|
+
|
85
|
+
|
89
86
|
summary_prompt = f"请为查询“{query}”总结以下内容:\n\n{full_content}"
|
90
87
|
|
91
88
|
if not agent.model:
|
@@ -3,6 +3,7 @@ import os
|
|
3
3
|
import sys
|
4
4
|
import time
|
5
5
|
from typing import Any, Dict, TYPE_CHECKING
|
6
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
6
7
|
|
7
8
|
# 为了类型检查,总是导入这些模块
|
8
9
|
if TYPE_CHECKING:
|
@@ -126,62 +127,50 @@ class VirtualTTYTool:
|
|
126
127
|
try:
|
127
128
|
if action == "launch":
|
128
129
|
if args.get("keys", "") != "":
|
129
|
-
print(
|
130
|
+
PrettyOutput.print("启动虚拟终端时,不能同时指定 keys 参数", OutputType.ERROR)
|
130
131
|
return {
|
131
132
|
"success": False,
|
132
133
|
"stdout": "",
|
133
134
|
"stderr": "启动虚拟终端时,不能同时指定keys参数",
|
134
135
|
}
|
135
|
-
|
136
|
+
|
136
137
|
result = self._launch_tty(agent, tty_id)
|
137
|
-
if result["success"]:
|
138
|
-
print(f"
|
139
|
-
else:
|
140
|
-
print(f"❌ 启动虚拟终端 [{tty_id}] 失败")
|
138
|
+
if not result["success"]:
|
139
|
+
PrettyOutput.print(f"启动虚拟终端 [{tty_id}] 失败", OutputType.ERROR)
|
141
140
|
return result
|
142
141
|
elif action == "send_keys":
|
143
142
|
keys = args.get("keys", "").strip()
|
144
143
|
add_enter = args.get("add_enter", True)
|
145
144
|
timeout = args.get("timeout", 5.0) # 默认5秒超时
|
146
|
-
|
145
|
+
|
147
146
|
result = self._input_command(agent, tty_id, keys, timeout, add_enter)
|
148
|
-
if result["success"]:
|
149
|
-
print(f"
|
150
|
-
else:
|
151
|
-
print(f"❌ 发送按键序列到终端 [{tty_id}] 失败")
|
147
|
+
if not result["success"]:
|
148
|
+
PrettyOutput.print(f"发送按键序列到终端 [{tty_id}] 失败", OutputType.ERROR)
|
152
149
|
return result
|
153
150
|
elif action == "output":
|
154
151
|
timeout = args.get("timeout", 5.0) # 默认5秒超时
|
155
|
-
|
152
|
+
|
156
153
|
result = self._get_output(agent, tty_id, timeout)
|
157
|
-
if result["success"]:
|
158
|
-
print(f"
|
159
|
-
else:
|
160
|
-
print(f"❌ 获取终端 [{tty_id}] 输出失败")
|
154
|
+
if not result["success"]:
|
155
|
+
PrettyOutput.print(f"获取终端 [{tty_id}] 输出失败", OutputType.ERROR)
|
161
156
|
return result
|
162
157
|
elif action == "close":
|
163
|
-
|
158
|
+
|
164
159
|
result = self._close_tty(agent, tty_id)
|
165
|
-
if result["success"]:
|
166
|
-
print(f"
|
167
|
-
else:
|
168
|
-
print(f"❌ 关闭虚拟终端 [{tty_id}] 失败")
|
160
|
+
if not result["success"]:
|
161
|
+
PrettyOutput.print(f"关闭虚拟终端 [{tty_id}] 失败", OutputType.ERROR)
|
169
162
|
return result
|
170
163
|
elif action == "get_screen":
|
171
|
-
|
164
|
+
|
172
165
|
result = self._get_screen(agent, tty_id)
|
173
|
-
if result["success"]:
|
174
|
-
print(f"
|
175
|
-
else:
|
176
|
-
print(f"❌ 获取终端 [{tty_id}] 屏幕内容失败")
|
166
|
+
if not result["success"]:
|
167
|
+
PrettyOutput.print(f"获取终端 [{tty_id}] 屏幕内容失败", OutputType.ERROR)
|
177
168
|
return result
|
178
169
|
elif action == "list":
|
179
|
-
|
170
|
+
|
180
171
|
result = self._list_ttys(agent)
|
181
|
-
if result["success"]:
|
182
|
-
print("
|
183
|
-
else:
|
184
|
-
print("❌ 获取虚拟终端列表失败")
|
172
|
+
if not result["success"]:
|
173
|
+
PrettyOutput.print("获取虚拟终端列表失败", OutputType.ERROR)
|
185
174
|
return result
|
186
175
|
return {"success": False, "stdout": "", "stderr": "不支持的操作"}
|
187
176
|
|
@@ -241,8 +230,7 @@ class VirtualTTYTool:
|
|
241
230
|
except BlockingIOError:
|
242
231
|
continue
|
243
232
|
|
244
|
-
|
245
|
-
print(f"📤 终端 [{tty_id}]: {output}")
|
233
|
+
|
246
234
|
|
247
235
|
return {"success": True, "stdout": output, "stderr": ""}
|
248
236
|
|
@@ -309,8 +297,7 @@ class VirtualTTYTool:
|
|
309
297
|
except _queue.Empty:
|
310
298
|
continue
|
311
299
|
|
312
|
-
|
313
|
-
print(f"📤 终端 [{tty_id}]: {output}")
|
300
|
+
|
314
301
|
|
315
302
|
return {"success": True, "stdout": output, "stderr": ""}
|
316
303
|
|
@@ -388,7 +375,6 @@ class VirtualTTYTool:
|
|
388
375
|
output += data.decode()
|
389
376
|
except BlockingIOError:
|
390
377
|
continue
|
391
|
-
print(f"📤 终端 [{tty_id}]: {output}")
|
392
378
|
return {"success": True, "stdout": output, "stderr": ""}
|
393
379
|
|
394
380
|
except Exception as e:
|
@@ -437,7 +423,6 @@ class VirtualTTYTool:
|
|
437
423
|
except Exception: # queue.Empty
|
438
424
|
continue
|
439
425
|
|
440
|
-
print(f"📤 终端 [{tty_id}]: {output}")
|
441
426
|
return {"success": True, "stdout": output, "stderr": ""}
|
442
427
|
|
443
428
|
except Exception as e:
|
@@ -490,8 +475,6 @@ class VirtualTTYTool:
|
|
490
475
|
break
|
491
476
|
except BlockingIOError:
|
492
477
|
break
|
493
|
-
print(f"📤 终端 [{tty_id}]: {output}")
|
494
|
-
|
495
478
|
return {"success": True, "stdout": output, "stderr": ""}
|
496
479
|
|
497
480
|
except Exception as e:
|
@@ -523,7 +506,6 @@ class VirtualTTYTool:
|
|
523
506
|
except Exception: # queue.Empty
|
524
507
|
continue
|
525
508
|
|
526
|
-
print(f"📤 终端 [{tty_id}]: {output}")
|
527
509
|
return {"success": True, "stdout": output, "stderr": ""}
|
528
510
|
|
529
511
|
except Exception as e:
|
jarvis/jarvis_utils/clipboard.py
CHANGED
@@ -11,9 +11,9 @@ def copy_to_clipboard(text: str) -> None:
|
|
11
11
|
参数:
|
12
12
|
text: 要复制的文本
|
13
13
|
"""
|
14
|
-
print("--- 剪贴板内容开始 ---")
|
15
|
-
print(text)
|
16
|
-
print("--- 剪贴板内容结束 ---")
|
14
|
+
PrettyOutput.print("--- 剪贴板内容开始 ---", OutputType.INFO)
|
15
|
+
PrettyOutput.print(text, OutputType.CODE, lang="text")
|
16
|
+
PrettyOutput.print("--- 剪贴板内容结束 ---", OutputType.INFO)
|
17
17
|
|
18
18
|
system = platform.system()
|
19
19
|
|
jarvis/jarvis_utils/input.py
CHANGED
@@ -26,6 +26,8 @@ from prompt_toolkit.document import Document
|
|
26
26
|
from prompt_toolkit.formatted_text import FormattedText
|
27
27
|
from prompt_toolkit.history import FileHistory
|
28
28
|
from prompt_toolkit.key_binding import KeyBindings
|
29
|
+
from prompt_toolkit.enums import DEFAULT_BUFFER
|
30
|
+
from prompt_toolkit.filters import has_focus
|
29
31
|
from prompt_toolkit.layout.containers import Window
|
30
32
|
from prompt_toolkit.layout.controls import FormattedTextControl
|
31
33
|
from prompt_toolkit.layout.layout import Layout
|
@@ -68,8 +70,9 @@ def get_single_line_input(tip: str, default: str = "") -> str:
|
|
68
70
|
获取支持历史记录的单行输入。
|
69
71
|
"""
|
70
72
|
session: PromptSession = PromptSession(history=None)
|
71
|
-
style = PromptStyle.from_dict({"prompt": "ansicyan"})
|
72
|
-
|
73
|
+
style = PromptStyle.from_dict({"prompt": "ansicyan", "bottom-toolbar": "fg:#888888"})
|
74
|
+
prompt = FormattedText([("class:prompt", f"👤 ❯ {tip}")])
|
75
|
+
return session.prompt(prompt, default=default, style=style)
|
73
76
|
|
74
77
|
|
75
78
|
def get_choice(tip: str, choices: List[str]) -> str:
|
@@ -289,14 +292,14 @@ def _show_history_and_copy():
|
|
289
292
|
PrettyOutput.print("没有可复制的消息", OutputType.INFO)
|
290
293
|
return
|
291
294
|
|
292
|
-
print("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20)
|
295
|
+
PrettyOutput.print("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20, OutputType.INFO)
|
293
296
|
for i, msg in enumerate(history):
|
294
297
|
cleaned_msg = msg.replace("\n", r"\n")
|
295
298
|
display_msg = (
|
296
299
|
(cleaned_msg[:70] + "...") if len(cleaned_msg) > 70 else cleaned_msg
|
297
300
|
)
|
298
|
-
print(f" {i + 1}: {display_msg.strip()}")
|
299
|
-
print("=" * 58 + "\n")
|
301
|
+
PrettyOutput.print(f" {i + 1}: {display_msg.strip()}", OutputType.INFO)
|
302
|
+
PrettyOutput.print("=" * 58 + "\n", OutputType.INFO)
|
300
303
|
|
301
304
|
while True:
|
302
305
|
try:
|
@@ -305,11 +308,11 @@ def _show_history_and_copy():
|
|
305
308
|
|
306
309
|
if not choice_str: # User pressed Enter
|
307
310
|
if not history:
|
308
|
-
print("没有历史记录可供选择。")
|
311
|
+
PrettyOutput.print("没有历史记录可供选择。", OutputType.INFO)
|
309
312
|
break
|
310
313
|
choice = len(history) - 1
|
311
314
|
elif choice_str.lower() == "c":
|
312
|
-
print("已取消")
|
315
|
+
PrettyOutput.print("已取消", OutputType.INFO)
|
313
316
|
break
|
314
317
|
else:
|
315
318
|
choice = int(choice_str) - 1
|
@@ -322,11 +325,11 @@ def _show_history_and_copy():
|
|
322
325
|
)
|
323
326
|
break
|
324
327
|
else:
|
325
|
-
print("无效的序号,请重试。")
|
328
|
+
PrettyOutput.print("无效的序号,请重试。", OutputType.WARNING)
|
326
329
|
except ValueError:
|
327
|
-
print("无效的输入,请输入数字。")
|
330
|
+
PrettyOutput.print("无效的输入,请输入数字。", OutputType.WARNING)
|
328
331
|
except (KeyboardInterrupt, EOFError):
|
329
|
-
print("\n操作取消")
|
332
|
+
PrettyOutput.print("\n操作取消", OutputType.INFO)
|
330
333
|
break
|
331
334
|
|
332
335
|
|
@@ -337,8 +340,8 @@ def _get_multiline_input_internal(tip: str) -> str:
|
|
337
340
|
"""
|
338
341
|
bindings = KeyBindings()
|
339
342
|
|
340
|
-
# Show a one-time hint on the first Enter press in this invocation
|
341
|
-
first_enter_hint_shown =
|
343
|
+
# Show a one-time hint on the first Enter press in this invocation (disabled; using inlay toolbar instead)
|
344
|
+
first_enter_hint_shown = True
|
342
345
|
|
343
346
|
@bindings.add("enter")
|
344
347
|
def _(event):
|
@@ -347,8 +350,9 @@ def _get_multiline_input_internal(tip: str) -> str:
|
|
347
350
|
first_enter_hint_shown = True
|
348
351
|
|
349
352
|
def _show_notice():
|
350
|
-
print(
|
351
|
-
|
353
|
+
PrettyOutput.print(
|
354
|
+
"提示:当前支持多行输入。输入完成请使用 Ctrl+J 确认;Enter 仅用于换行。",
|
355
|
+
OutputType.INFO,
|
352
356
|
)
|
353
357
|
try:
|
354
358
|
input("按回车继续...")
|
@@ -372,17 +376,49 @@ def _get_multiline_input_internal(tip: str) -> str:
|
|
372
376
|
else:
|
373
377
|
event.current_buffer.insert_text("\n")
|
374
378
|
|
375
|
-
@bindings.add("c-j")
|
379
|
+
@bindings.add("c-j", filter=has_focus(DEFAULT_BUFFER))
|
376
380
|
def _(event):
|
377
381
|
event.current_buffer.validate_and_handle()
|
378
382
|
|
379
|
-
@bindings.add("c-o")
|
383
|
+
@bindings.add("c-o", filter=has_focus(DEFAULT_BUFFER))
|
380
384
|
def _(event):
|
381
385
|
"""Handle Ctrl+O by exiting the prompt and returning the sentinel value."""
|
382
386
|
event.app.exit(result=CTRL_O_SENTINEL)
|
383
387
|
|
384
|
-
style = PromptStyle.from_dict(
|
388
|
+
style = PromptStyle.from_dict(
|
389
|
+
{
|
390
|
+
"prompt": "ansibrightmagenta bold",
|
391
|
+
"bottom-toolbar": "bg:#4b145b #ffd6ff bold",
|
392
|
+
"bt.tip": "bold fg:#ff5f87",
|
393
|
+
"bt.sep": "fg:#ffb3de",
|
394
|
+
"bt.key": "bg:#d7005f #ffffff bold",
|
395
|
+
"bt.label": "fg:#ffd6ff",
|
396
|
+
}
|
397
|
+
)
|
385
398
|
|
399
|
+
def _bottom_toolbar():
|
400
|
+
return FormattedText(
|
401
|
+
[
|
402
|
+
("class:bt.tip", f" {tip} "),
|
403
|
+
("class:bt.sep", " • "),
|
404
|
+
("class:bt.label", "快捷键: "),
|
405
|
+
("class:bt.key", "@"),
|
406
|
+
("class:bt.label", " 文件补全 "),
|
407
|
+
("class:bt.sep", " • "),
|
408
|
+
("class:bt.key", "Tab"),
|
409
|
+
("class:bt.label", " 选择 "),
|
410
|
+
("class:bt.sep", " • "),
|
411
|
+
("class:bt.key", "Ctrl+J"),
|
412
|
+
("class:bt.label", " 确认 "),
|
413
|
+
("class:bt.sep", " • "),
|
414
|
+
("class:bt.key", "Ctrl+O"),
|
415
|
+
("class:bt.label", " 历史复制 "),
|
416
|
+
("class:bt.sep", " • "),
|
417
|
+
("class:bt.key", "Ctrl+C/D"),
|
418
|
+
("class:bt.label", " 取消 "),
|
419
|
+
]
|
420
|
+
)
|
421
|
+
|
386
422
|
history_dir = get_data_dir()
|
387
423
|
session: PromptSession = PromptSession(
|
388
424
|
history=FileHistory(os.path.join(history_dir, "multiline_input_history")),
|
@@ -394,25 +430,29 @@ def _get_multiline_input_internal(tip: str) -> str:
|
|
394
430
|
mouse_support=False,
|
395
431
|
)
|
396
432
|
|
397
|
-
print
|
398
|
-
prompt = FormattedText([("class:prompt", "
|
433
|
+
# Tip is shown in bottom toolbar; avoid extra print
|
434
|
+
prompt = FormattedText([("class:prompt", "👤 ❯ ")])
|
399
435
|
|
400
436
|
try:
|
401
|
-
return session.prompt(
|
437
|
+
return session.prompt(
|
438
|
+
prompt,
|
439
|
+
style=style,
|
440
|
+
pre_run=lambda: None,
|
441
|
+
bottom_toolbar=_bottom_toolbar,
|
442
|
+
).strip()
|
402
443
|
except (KeyboardInterrupt, EOFError):
|
403
444
|
return ""
|
404
445
|
|
405
446
|
|
406
|
-
def get_multiline_input(tip: str) -> str:
|
447
|
+
def get_multiline_input(tip: str, print_on_empty: bool = True) -> str:
|
407
448
|
"""
|
408
449
|
获取带有增强补全和确认功能的多行输入。
|
409
450
|
此函数处理控制流,允许在不破坏终端状态的情况下处理历史记录复制。
|
410
|
-
"""
|
411
|
-
PrettyOutput.section(
|
412
|
-
"用户输入 - 使用 @ 触发文件补全,Tab 选择补全项,Ctrl+J 确认,Ctrl+O 从历史记录中选择消息复制,按 Ctrl+C/D 取消输入",
|
413
|
-
OutputType.USER,
|
414
|
-
)
|
415
451
|
|
452
|
+
参数:
|
453
|
+
tip: 提示文本,将显示在底部工具栏中
|
454
|
+
print_on_empty: 当输入为空字符串时,是否打印“输入已取消”提示。默认打印。
|
455
|
+
"""
|
416
456
|
while True:
|
417
457
|
user_input = _get_multiline_input_internal(tip)
|
418
458
|
|
@@ -421,6 +461,6 @@ def get_multiline_input(tip: str) -> str:
|
|
421
461
|
tip = "请继续输入(或按Ctrl+J确认):"
|
422
462
|
continue
|
423
463
|
else:
|
424
|
-
if not user_input:
|
464
|
+
if not user_input and print_on_empty:
|
425
465
|
PrettyOutput.print("\n输入已取消", OutputType.INFO)
|
426
466
|
return user_input
|
@@ -203,12 +203,12 @@ def load_methodology(
|
|
203
203
|
|
204
204
|
try:
|
205
205
|
# 加载所有方法论
|
206
|
-
print(
|
206
|
+
PrettyOutput.print("📁 加载方法论文件...", OutputType.INFO)
|
207
207
|
methodologies = _load_all_methodologies()
|
208
208
|
if not methodologies:
|
209
|
-
print(
|
209
|
+
PrettyOutput.print("没有找到方法论文件", OutputType.WARNING)
|
210
210
|
return ""
|
211
|
-
print(f"
|
211
|
+
PrettyOutput.print(f"加载方法论文件完成 (共 {len(methodologies)} 个)", OutputType.SUCCESS)
|
212
212
|
|
213
213
|
if platform_name:
|
214
214
|
platform = PlatformRegistry().create_platform(platform_name)
|
jarvis/jarvis_utils/output.py
CHANGED
@@ -274,19 +274,13 @@ class PrettyOutput:
|
|
274
274
|
panel = Panel(
|
275
275
|
content,
|
276
276
|
border_style=header_styles[output_type],
|
277
|
-
title=header,
|
278
|
-
title_align="left",
|
279
277
|
padding=(0, 0),
|
280
278
|
highlight=True,
|
281
279
|
)
|
282
280
|
if get_pretty_output():
|
283
281
|
console.print(panel)
|
284
282
|
else:
|
285
|
-
|
286
|
-
console.print(header)
|
287
|
-
console.print(content)
|
288
|
-
else:
|
289
|
-
console.print(header, content)
|
283
|
+
console.print(content)
|
290
284
|
if traceback or output_type == OutputType.ERROR:
|
291
285
|
try:
|
292
286
|
console.print_exception()
|