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.
Files changed (49) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +21 -10
  3. jarvis/jarvis_agent/edit_file_handler.py +8 -13
  4. jarvis/jarvis_agent/memory_manager.py +4 -4
  5. jarvis/jarvis_agent/task_analyzer.py +4 -3
  6. jarvis/jarvis_agent/task_manager.py +6 -6
  7. jarvis/jarvis_agent/tool_executor.py +2 -2
  8. jarvis/jarvis_code_agent/code_agent.py +21 -29
  9. jarvis/jarvis_code_analysis/code_review.py +2 -4
  10. jarvis/jarvis_git_utils/git_commiter.py +17 -18
  11. jarvis/jarvis_methodology/main.py +12 -12
  12. jarvis/jarvis_platform/base.py +14 -13
  13. jarvis/jarvis_platform/kimi.py +13 -13
  14. jarvis/jarvis_platform/tongyi.py +17 -15
  15. jarvis/jarvis_platform/yuanbao.py +11 -11
  16. jarvis/jarvis_rag/cli.py +36 -32
  17. jarvis/jarvis_rag/embedding_manager.py +11 -6
  18. jarvis/jarvis_rag/llm_interface.py +6 -5
  19. jarvis/jarvis_rag/rag_pipeline.py +9 -8
  20. jarvis/jarvis_rag/reranker.py +3 -2
  21. jarvis/jarvis_rag/retriever.py +18 -8
  22. jarvis/jarvis_smart_shell/main.py +306 -46
  23. jarvis/jarvis_stats/stats.py +40 -0
  24. jarvis/jarvis_stats/storage.py +220 -9
  25. jarvis/jarvis_tools/clear_memory.py +0 -11
  26. jarvis/jarvis_tools/cli/main.py +18 -17
  27. jarvis/jarvis_tools/edit_file.py +4 -4
  28. jarvis/jarvis_tools/execute_script.py +5 -1
  29. jarvis/jarvis_tools/file_analyzer.py +6 -6
  30. jarvis/jarvis_tools/generate_new_tool.py +6 -17
  31. jarvis/jarvis_tools/read_code.py +3 -6
  32. jarvis/jarvis_tools/read_webpage.py +4 -4
  33. jarvis/jarvis_tools/registry.py +8 -28
  34. jarvis/jarvis_tools/retrieve_memory.py +5 -16
  35. jarvis/jarvis_tools/rewrite_file.py +0 -4
  36. jarvis/jarvis_tools/save_memory.py +2 -10
  37. jarvis/jarvis_tools/search_web.py +5 -8
  38. jarvis/jarvis_tools/virtual_tty.py +22 -40
  39. jarvis/jarvis_utils/clipboard.py +3 -3
  40. jarvis/jarvis_utils/input.py +67 -27
  41. jarvis/jarvis_utils/methodology.py +3 -3
  42. jarvis/jarvis_utils/output.py +1 -7
  43. jarvis/jarvis_utils/utils.py +35 -58
  44. {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/METADATA +1 -1
  45. {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/RECORD +49 -49
  46. {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/WHEEL +0 -0
  47. {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/entry_points.txt +0 -0
  48. {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/licenses/LICENSE +0 -0
  49. {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/top_level.txt +0 -0
@@ -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
- PrettyOutput.print(
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
- PrettyOutput.print(
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
- PrettyOutput.print(
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
- PrettyOutput.print(
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
- PrettyOutput.print(
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
- PrettyOutput.print(
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
- PrettyOutput.print(
669
- f"检测到缺失的 {ct('TOOL_CALL')} 标签,已自动修复。修复后的内容如下:",
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
- PrettyOutput.print(
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
- PrettyOutput.print(
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
- PrettyOutput.print(f"检索到 {len(all_memories)} 条记忆", OutputType.INFO)
168
+
174
169
 
175
170
  if tags:
176
- PrettyOutput.print(f"使用标签过滤: {', '.join(tags)}", OutputType.INFO)
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
- PrettyOutput.print(f"记忆较多,仅显示前5条摘要:", OutputType.INFO)
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
- PrettyOutput.print(
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
- PrettyOutput.print(
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
- PrettyOutput.print("▶️ 使用 DuckDuckGo 开始网页搜索...", OutputType.INFO)
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
- PrettyOutput.print("ℹ️ 已成功获取10个网页,停止抓取。", OutputType.INFO)
53
+
54
54
  break
55
55
 
56
56
  url = r["href"]
57
57
  title = r.get("title", url)
58
58
 
59
59
  try:
60
- PrettyOutput.print(
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
- PrettyOutput.print("🧠 正在总结内容...", OutputType.INFO)
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(f"🚫 启动虚拟终端时,不能同时指定keys参数")
130
+ PrettyOutput.print("启动虚拟终端时,不能同时指定 keys 参数", OutputType.ERROR)
130
131
  return {
131
132
  "success": False,
132
133
  "stdout": "",
133
134
  "stderr": "启动虚拟终端时,不能同时指定keys参数",
134
135
  }
135
- print(f"🚀 正在启动虚拟终端 [{tty_id}]...")
136
+
136
137
  result = self._launch_tty(agent, tty_id)
137
- if result["success"]:
138
- print(f"启动虚拟终端 [{tty_id}] 成功")
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
- print(f"⌨️ 正在向终端 [{tty_id}] 发送按键序列: {keys}...")
145
+
147
146
  result = self._input_command(agent, tty_id, keys, timeout, add_enter)
148
- if result["success"]:
149
- print(f"发送按键序列到终端 [{tty_id}] 成功")
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
- print(f"📥 正在获取终端 [{tty_id}] 输出...")
152
+
156
153
  result = self._get_output(agent, tty_id, timeout)
157
- if result["success"]:
158
- print(f"获取终端 [{tty_id}] 输出成功")
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
- print(f"🔒 正在关闭虚拟终端 [{tty_id}]...")
158
+
164
159
  result = self._close_tty(agent, tty_id)
165
- if result["success"]:
166
- print(f"关闭虚拟终端 [{tty_id}] 成功")
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
- print(f"🖥️ 正在获取终端 [{tty_id}] 屏幕内容...")
164
+
172
165
  result = self._get_screen(agent, tty_id)
173
- if result["success"]:
174
- print(f"获取终端 [{tty_id}] 屏幕内容成功")
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
- print("📋 正在获取所有虚拟终端列表...")
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
- if output:
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
- if output:
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:
@@ -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
 
@@ -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
- return session.prompt(f"{tip}", default=default, style=style)
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 = False
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
- f"{Fore.YELLOW}提示:当前支持多行输入。输入完成请使用 Ctrl+J 确认;Enter 仅用于换行。{ColoramaStyle.RESET_ALL}"
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({"prompt": "ansicyan"})
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(f"{Fore.GREEN}{tip}{ColoramaStyle.RESET_ALL}")
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(prompt, style=style, pre_run=lambda: None).strip()
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(f"📁 加载方法论文件...")
206
+ PrettyOutput.print("📁 加载方法论文件...", OutputType.INFO)
207
207
  methodologies = _load_all_methodologies()
208
208
  if not methodologies:
209
- print(f"没有找到方法论文件")
209
+ PrettyOutput.print("没有找到方法论文件", OutputType.WARNING)
210
210
  return ""
211
- print(f"加载方法论文件完成 (共 {len(methodologies)} 个)")
211
+ PrettyOutput.print(f"加载方法论文件完成 (共 {len(methodologies)} 个)", OutputType.SUCCESS)
212
212
 
213
213
  if platform_name:
214
214
  platform = PlatformRegistry().create_platform(platform_name)
@@ -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
- if len(text.strip().splitlines()) > 1:
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()