fix: 修复TUI界面多个问题,添加记忆模块模式选择功能
- 修复ToolResult对象的get方法错误 - 修复TUI版本号显示错误(v5.4.1 -> v6.0) - 修复Shift+Enter换行功能 - 修复AI回复颜色显示问题 - 添加记忆模块模式选择功能(陪伴模式/工作模式) - 清理项目文件,准备上传到Gitee
This commit is contained in:
1 parent
329854866b
commit
b5f638cffc
6 files changed
+153
-76
No files matched your search
+19
-21
@@ -1,27 +1,25 @@
|
|||||||
# 小狸插件配置
|
# 小狸插件配置 - 由启动器自动生成
|
||||||
|
|
||||||
# ── 必备生产力 ──
|
|
||||||
ENABLE_AI_SEARCH = True
|
ENABLE_AI_SEARCH = True
|
||||||
ENABLE_CMD_EXECUTOR = True
|
|
||||||
ENABLE_FILE_MANAGER = True
|
|
||||||
ENABLE_CODE_EDITOR = True
|
|
||||||
ENABLE_GIT_TOOLS = True
|
|
||||||
ENABLE_FRONTEND_TESTER = True
|
|
||||||
ENABLE_NETWORK_TOOLS = True
|
|
||||||
ENABLE_SCHEDULER = True
|
|
||||||
ENABLE_SPEECH_RECOGNITION = True
|
|
||||||
ENABLE_TASK_MANAGER = True
|
|
||||||
ENABLE_TOOL_SEARCH = True
|
|
||||||
ENABLE_AUTO_ENGINEER = True
|
ENABLE_AUTO_ENGINEER = True
|
||||||
ENABLE_TERMINAL = True
|
|
||||||
ENABLE_BROWSER_AUTO = True
|
ENABLE_BROWSER_AUTO = True
|
||||||
|
ENABLE_CMD_EXECUTOR = True
|
||||||
|
ENABLE_CODE_EDITOR = True
|
||||||
|
ENABLE_FILE_MANAGER = True
|
||||||
|
ENABLE_FRONTEND_TESTER = True
|
||||||
|
ENABLE_GIT_TOOLS = True
|
||||||
ENABLE_GUI_AUTO = True
|
ENABLE_GUI_AUTO = True
|
||||||
ENABLE_SUB_AGENT = True
|
|
||||||
ENABLE_WORKFLOW = True
|
|
||||||
ENABLE_MEMORY_PLUGIN = True
|
|
||||||
ENABLE_JSON_TOOLKIT = True
|
|
||||||
ENABLE_REGEX_TESTER = True
|
|
||||||
ENABLE_SNIPPET_MANAGER = True
|
|
||||||
ENABLE_MARKDOWN_TOOL = True
|
|
||||||
ENABLE_ID_GENERATOR = True
|
ENABLE_ID_GENERATOR = True
|
||||||
ENABLE_IMAGE_READER = True
|
ENABLE_IMAGE_READER = True
|
||||||
|
ENABLE_JSON_TOOLKIT = True
|
||||||
|
ENABLE_MARKDOWN_TOOL = True
|
||||||
|
ENABLE_MEMORY_PLUGIN = True
|
||||||
|
ENABLE_NETWORK_TOOLS = True
|
||||||
|
ENABLE_REGEX_TESTER = True
|
||||||
|
ENABLE_SCHEDULER = True
|
||||||
|
ENABLE_SNIPPET_MANAGER = True
|
||||||
|
ENABLE_SPEECH_RECOGNITION = True
|
||||||
|
ENABLE_SUB_AGENT = True
|
||||||
|
ENABLE_TASK_MANAGER = True
|
||||||
|
ENABLE_TERMINAL = True
|
||||||
|
ENABLE_TOOL_SEARCH = True
|
||||||
|
ENABLE_WORKFLOW = True
|
||||||
+11
-29
@@ -1,30 +1,12 @@
|
|||||||
# 小狸 Pro-CLI 依赖库
|
# 小狸 Pro-CLI 依赖
|
||||||
# 修复后的包名(使用 pip 安装名)
|
# 生成于 2026-06-26 20:58
|
||||||
|
|
||||||
# 核心依赖
|
PIL
|
||||||
colorama>=0.4.6
|
ascii_magic
|
||||||
openai>=1.0.0
|
colorama
|
||||||
requests>=2.28.0
|
cv2
|
||||||
ollama>=0.1.0
|
numpy
|
||||||
|
ollama
|
||||||
# 图像处理
|
openai
|
||||||
Pillow>=9.0.0
|
requests
|
||||||
opencv-python>=4.5.0
|
tkinter
|
||||||
numpy>=1.21.0
|
|
||||||
ascii-magic>=2.0.0
|
|
||||||
|
|
||||||
# 数据处理
|
|
||||||
pandas>=1.3.0
|
|
||||||
scipy>=1.7.0
|
|
||||||
|
|
||||||
# 音频
|
|
||||||
pygame>=2.0.0
|
|
||||||
|
|
||||||
# TUI 模式
|
|
||||||
textual>=0.40.0
|
|
||||||
|
|
||||||
# WebSocket 远程
|
|
||||||
websockets>=11.0
|
|
||||||
|
|
||||||
# 类型提示
|
|
||||||
typing-extensions>=4.0.0
|
|
||||||
@@ -45,13 +45,12 @@ class DisplayMixin:
|
|||||||
response_lines = content.split('\n')
|
response_lines = content.split('\n')
|
||||||
if self.tui_output_callback:
|
if self.tui_output_callback:
|
||||||
for i, line in enumerate(response_lines):
|
for i, line in enumerate(response_lines):
|
||||||
if i == 0:
|
# 为每一行都添加前缀,确保整个AI回复都使用正确的样式
|
||||||
prefix = "[yellow]>[/] " if is_continue else "[cyan]>[/] "
|
prefix = "[yellow]>[/] " if is_continue else "[cyan]>[/] "
|
||||||
line = prefix + line
|
|
||||||
if i == len(response_lines) - 1:
|
if i == len(response_lines) - 1:
|
||||||
self._output(f"{line} {user_id_display}")
|
self._output(f"{prefix}{line} {user_id_display}")
|
||||||
else:
|
else:
|
||||||
self._output(line)
|
self._output(f"{prefix}{line}")
|
||||||
self._output("")
|
self._output("")
|
||||||
else:
|
else:
|
||||||
for i, line in enumerate(response_lines):
|
for i, line in enumerate(response_lines):
|
||||||
|
|||||||
@@ -105,6 +105,9 @@ class ToolMixin:
|
|||||||
result = self.liugin_manager.execute(tool_name, args, **arguments)
|
result = self.liugin_manager.execute(tool_name, args, **arguments)
|
||||||
else:
|
else:
|
||||||
result = self.liugin_manager.execute(tool_name, args)
|
result = self.liugin_manager.execute(tool_name, args)
|
||||||
|
# 将 ToolResult 对象转换为字典格式,兼容旧接口
|
||||||
|
if hasattr(result, 'to_dict'):
|
||||||
|
return result.to_dict()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# 回退:直接调用 handler(旧逻辑)
|
# 回退:直接调用 handler(旧逻辑)
|
||||||
@@ -212,8 +215,10 @@ class ToolMixin:
|
|||||||
else:
|
else:
|
||||||
tool_args_display = tool_args
|
tool_args_display = tool_args
|
||||||
|
|
||||||
# TUI 模式下不做延迟和 ESC 检测
|
# TUI 模式下显示工具执行状态
|
||||||
if not self.tui_output_callback:
|
if self.tui_output_callback:
|
||||||
|
self._output(f" 正在执行工具: {tool_name} {tool_args_display}")
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
import msvcrt
|
import msvcrt
|
||||||
canceled = False
|
canceled = False
|
||||||
|
|||||||
@@ -17,13 +17,71 @@ from typing import List, Dict, Optional, Tuple
|
|||||||
class MemoryManager:
|
class MemoryManager:
|
||||||
"""持久化记忆管理器"""
|
"""持久化记忆管理器"""
|
||||||
|
|
||||||
|
# 记忆模式常量
|
||||||
|
MODE_COMPANION = "companion" # 陪伴模式(长期记忆)
|
||||||
|
MODE_WORK = "work" # 工作模式(短期记忆)
|
||||||
|
|
||||||
def __init__(self, project_dir: str):
|
def __init__(self, project_dir: str):
|
||||||
self.project_dir = project_dir
|
self.project_dir = project_dir
|
||||||
self.memory_dir = os.path.join(project_dir, "memory")
|
self.memory_dir = os.path.join(project_dir, "memory")
|
||||||
self.memory_file = os.path.join(project_dir, "MEMORY.md")
|
self.memory_file = os.path.join(project_dir, "MEMORY.md")
|
||||||
self.chat_dir = os.path.join(project_dir, "chat_history")
|
self.chat_dir = os.path.join(project_dir, "chat_history")
|
||||||
|
self.config_file = os.path.join(project_dir, "config.json")
|
||||||
self.compress_threshold = 50 # 超过 50 条消息触发压缩
|
self.compress_threshold = 50 # 超过 50 条消息触发压缩
|
||||||
self._ensure_dirs()
|
self._ensure_dirs()
|
||||||
|
self._memory_mode = self._load_memory_mode()
|
||||||
|
|
||||||
|
def _load_memory_mode(self) -> str:
|
||||||
|
"""加载记忆模式配置"""
|
||||||
|
try:
|
||||||
|
if os.path.exists(self.config_file):
|
||||||
|
with open(self.config_file, 'r', encoding='utf-8') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
return config.get("system", {}).get("memory_mode", "")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _save_memory_mode(self, mode: str):
|
||||||
|
"""保存记忆模式配置"""
|
||||||
|
try:
|
||||||
|
config = {}
|
||||||
|
if os.path.exists(self.config_file):
|
||||||
|
with open(self.config_file, 'r', encoding='utf-8') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
if "system" not in config:
|
||||||
|
config["system"] = {}
|
||||||
|
config["system"]["memory_mode"] = mode
|
||||||
|
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(config, f, ensure_ascii=False, indent=2)
|
||||||
|
self._memory_mode = mode
|
||||||
|
except Exception as e:
|
||||||
|
print(f"保存记忆模式配置失败: {e}")
|
||||||
|
|
||||||
|
def get_memory_mode(self) -> str:
|
||||||
|
"""获取当前记忆模式"""
|
||||||
|
return self._memory_mode
|
||||||
|
|
||||||
|
def is_mode_selected(self) -> bool:
|
||||||
|
"""检查是否已选择记忆模式"""
|
||||||
|
return self._memory_mode in [self.MODE_COMPANION, self.MODE_WORK]
|
||||||
|
|
||||||
|
def select_mode(self, mode: str) -> bool:
|
||||||
|
"""选择记忆模式(只能选择一次)"""
|
||||||
|
if self.is_mode_selected():
|
||||||
|
return False # 已经选择过,不能更改
|
||||||
|
if mode not in [self.MODE_COMPANION, self.MODE_WORK]:
|
||||||
|
return False # 无效的模式
|
||||||
|
self._save_memory_mode(mode)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_mode_description(self) -> str:
|
||||||
|
"""获取当前模式的描述"""
|
||||||
|
if self._memory_mode == self.MODE_COMPANION:
|
||||||
|
return "陪伴模式(长期记忆):拥有成熟的记忆功能,记住用户的偏好、习惯、历史对话等"
|
||||||
|
elif self._memory_mode == self.MODE_WORK:
|
||||||
|
return "工作模式(短期记忆):每次对话是独立的短期记忆,可通过指令手动存储聊天记录"
|
||||||
|
return "未选择模式"
|
||||||
|
|
||||||
def _ensure_dirs(self):
|
def _ensure_dirs(self):
|
||||||
"""确保目录存在"""
|
"""确保目录存在"""
|
||||||
@@ -522,6 +580,44 @@ class MemoryManager:
|
|||||||
# 记忆插件(注册到插件系统)
|
# 记忆插件(注册到插件系统)
|
||||||
# ══════════════════════════════════════════════
|
# ══════════════════════════════════════════════
|
||||||
|
|
||||||
|
def save_conversation_by_mode(self, conversation: list, label: str = ""):
|
||||||
|
"""根据模式保存对话"""
|
||||||
|
if self._memory_mode == self.MODE_COMPANION:
|
||||||
|
# 陪伴模式:自动保存到长期记忆
|
||||||
|
self.auto_save_chat(conversation, label)
|
||||||
|
# 提取关键信息保存到MEMORY.md
|
||||||
|
self._extract_and_save_key_info(conversation)
|
||||||
|
elif self._memory_mode == self.MODE_WORK:
|
||||||
|
# 工作模式:自动保存聊天记录
|
||||||
|
self.auto_save_chat(conversation, label)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_and_save_key_info(self, conversation: list):
|
||||||
|
"""从对话中提取关键信息保存到MEMORY.md(陪伴模式专用)"""
|
||||||
|
# 这里可以添加AI提取关键信息的逻辑
|
||||||
|
# 暂时简单实现:保存最后一条助手消息
|
||||||
|
for msg in reversed(conversation):
|
||||||
|
if msg.get("role") == "assistant":
|
||||||
|
content = msg.get("content", "")
|
||||||
|
if content and len(content) > 20:
|
||||||
|
self.append_memory("对话记录", f"- {content[:200]}")
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def load_context_by_mode(self, conversation: list) -> list:
|
||||||
|
"""根据模式加载上下文"""
|
||||||
|
if self._memory_mode == self.MODE_COMPANION:
|
||||||
|
# 陪伴模式:加载长期记忆作为上下文
|
||||||
|
memory_content = self.load_memory()
|
||||||
|
if memory_content:
|
||||||
|
context_msg = {"role": "system", "content": f"长期记忆:{memory_content}"}
|
||||||
|
return [context_msg] + conversation
|
||||||
|
return conversation
|
||||||
|
elif self._memory_mode == self.MODE_WORK:
|
||||||
|
# 工作模式:只返回当前对话
|
||||||
|
return conversation
|
||||||
|
return conversation
|
||||||
|
|
||||||
class Liugin:
|
class Liugin:
|
||||||
"""记忆系统插件 — AI 可读写记忆、搜索历史"""
|
"""记忆系统插件 — AI 可读写记忆、搜索历史"""
|
||||||
|
|
||||||
@@ -596,3 +692,7 @@ JSON格式示例:
|
|||||||
"required": ["action"]
|
"required": ["action"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# ══════════════════════════════════════════════
|
||||||
|
# 模式特定的方法
|
||||||
|
# ══════════════════════════════════════════════
|
||||||
|
|
||||||
+12
-19
@@ -379,7 +379,8 @@ if TEXTUAL_AVAILABLE:
|
|||||||
self.styles.opacity = 0
|
self.styles.opacity = 0
|
||||||
|
|
||||||
def on_mount(self):
|
def on_mount(self):
|
||||||
self.animate("opacity", value=1.0, duration=0.3, easing="out_cubic")
|
# 使用 styles.animate 来动画化样式属性
|
||||||
|
self.styles.animate("opacity", value=1.0, duration=0.3, easing="out_cubic")
|
||||||
|
|
||||||
|
|
||||||
class TypingIndicator(Static):
|
class TypingIndicator(Static):
|
||||||
@@ -411,17 +412,7 @@ if TEXTUAL_AVAILABLE:
|
|||||||
# ═══════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════
|
||||||
|
|
||||||
class SendTextArea(TextArea):
|
class SendTextArea(TextArea):
|
||||||
"""回车发送消息,Shift+回车换行的输入框"""
|
"""多行输入框,Ctrl+Enter发送消息"""
|
||||||
async def _on_key(self, event):
|
|
||||||
if event.key in ("enter", "\r", "\n"):
|
|
||||||
# prevent_default 阻止父类 TextArea._on_key 执行
|
|
||||||
event.prevent_default()
|
|
||||||
try:
|
|
||||||
self.app.action_send_message()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return
|
|
||||||
# 其他键正常处理
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════
|
||||||
# TUI 主应用
|
# TUI 主应用
|
||||||
@@ -460,9 +451,8 @@ if TEXTUAL_AVAILABLE:
|
|||||||
yield VerticalScroll(id="chat-scroll")
|
yield VerticalScroll(id="chat-scroll")
|
||||||
with Vertical(id="input-area"):
|
with Vertical(id="input-area"):
|
||||||
with Container(id="input-wrapper"):
|
with Container(id="input-wrapper"):
|
||||||
yield SendTextArea(
|
yield SendTextArea(
|
||||||
placeholder="输入消息... (回车发送, Shift+回车换行)",
|
placeholder="输入消息... (Ctrl+Enter 发送, Enter 换行)", id="user-input",
|
||||||
id="user-input",
|
|
||||||
soft_wrap=True,
|
soft_wrap=True,
|
||||||
tab_behavior="indent",
|
tab_behavior="indent",
|
||||||
)
|
)
|
||||||
@@ -498,7 +488,7 @@ if TEXTUAL_AVAILABLE:
|
|||||||
"",
|
"",
|
||||||
" ╔══════════════════════════════════════════════╗",
|
" ╔══════════════════════════════════════════════╗",
|
||||||
" ║ ║",
|
" ║ ║",
|
||||||
" ║ 小狸 Pro-CLI v5.4.1 ║",
|
" ║ 小狸 Pro-CLI v6.0 ║",
|
||||||
" ║ 智能编程助手 · 动画增强版 ║",
|
" ║ 智能编程助手 · 动画增强版 ║",
|
||||||
" ║ ║",
|
" ║ ║",
|
||||||
" ╚══════════════════════════════════════════════╝",
|
" ╚══════════════════════════════════════════════╝",
|
||||||
@@ -542,7 +532,7 @@ if TEXTUAL_AVAILABLE:
|
|||||||
("", "msg-dim"),
|
("", "msg-dim"),
|
||||||
(" ╔══════════════════════════════════════════════╗", "msg-welcome"),
|
(" ╔══════════════════════════════════════════════╗", "msg-welcome"),
|
||||||
(" ║ ║", "msg-welcome"),
|
(" ║ ║", "msg-welcome"),
|
||||||
(" ║ 小狸 Pro-CLI v5.4.1 ║", "msg-welcome"),
|
(" ║ 小狸 Pro-CLI v6.0 ║", "msg-welcome"),
|
||||||
(" ║ 智能编程助手 · 动画增强版 ║", "msg-welcome"),
|
(" ║ 智能编程助手 · 动画增强版 ║", "msg-welcome"),
|
||||||
(" ║ ║", "msg-welcome"),
|
(" ║ ║", "msg-welcome"),
|
||||||
(" ╚══════════════════════════════════════════════╝", "msg-welcome"),
|
(" ╚══════════════════════════════════════════════╝", "msg-welcome"),
|
||||||
@@ -743,7 +733,7 @@ if TEXTUAL_AVAILABLE:
|
|||||||
'cls': lambda: self.action_clear(),
|
'cls': lambda: self.action_clear(),
|
||||||
'model': lambda: self._switch_model(args),
|
'model': lambda: self._switch_model(args),
|
||||||
'engine': lambda: self._switch_model(args),
|
'engine': lambda: self._switch_model(args),
|
||||||
'about': lambda: self._system(" 小狸 Pro-CLI v5.4.1 - 智能编程助手"),
|
'about': lambda: self._system(" 小狸 Pro-CLI v6.0 - 智能编程助手"),
|
||||||
'status': lambda: self._show_status(),
|
'status': lambda: self._show_status(),
|
||||||
'tools': lambda: self._show_tools(),
|
'tools': lambda: self._show_tools(),
|
||||||
'engines': lambda: self._show_engines(),
|
'engines': lambda: self._show_engines(),
|
||||||
@@ -878,7 +868,10 @@ if TEXTUAL_AVAILABLE:
|
|||||||
self.call_after_refresh(self._update_sidebar)
|
self.call_after_refresh(self._update_sidebar)
|
||||||
|
|
||||||
def _write_raw(self, msg):
|
def _write_raw(self, msg):
|
||||||
if '✅' in msg or 'OK 工具' in msg:
|
# 检查是否是AI回复(包含Rich标记前缀)
|
||||||
|
if msg.startswith('[cyan]>[/]') or msg.startswith('[yellow]>[/]'):
|
||||||
|
self._add(f" {msg}", "msg-ai")
|
||||||
|
elif '✅' in msg or 'OK 工具' in msg:
|
||||||
self._add(f" {msg}", "msg-tool-ok")
|
self._add(f" {msg}", "msg-tool-ok")
|
||||||
elif '❌' in msg or 'X 工具' in msg or '错误' in msg:
|
elif '❌' in msg or 'X 工具' in msg or '错误' in msg:
|
||||||
self._add(f" {msg}", "msg-tool-err")
|
self._add(f" {msg}", "msg-tool-err")
|
||||||
|
|||||||
Reference in new issue
Block a user