From b5f638cffcf552f5a2a173934409d3fb8feabd33 Mon Sep 17 00:00:00 2001 From: shuiyu1123 Date: Fri, 26 Jun 2026 22:26:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DTUI=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E5=A4=9A=E4=B8=AA=E9=97=AE=E9=A2=98=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AE=B0=E5=BF=86=E6=A8=A1=E5=9D=97=E6=A8=A1=E5=BC=8F=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复ToolResult对象的get方法错误 - 修复TUI版本号显示错误(v5.4.1 -> v6.0) - 修复Shift+Enter换行功能 - 修复AI回复颜色显示问题 - 添加记忆模块模式选择功能(陪伴模式/工作模式) - 清理项目文件,准备上传到Gitee --- plugins_config.py | 40 ++++++++-------- requirements.txt | 40 +++++----------- xcli_core/cli_display.py | 9 ++-- xcli_core/cli_tools.py | 9 +++- xcli_core/memory.py | 100 +++++++++++++++++++++++++++++++++++++++ xcli_core/tui.py | 31 +++++------- 6 files changed, 153 insertions(+), 76 deletions(-) diff --git a/plugins_config.py b/plugins_config.py index bc614dc..3aa8786 100644 --- a/plugins_config.py +++ b/plugins_config.py @@ -1,27 +1,25 @@ -# 小狸插件配置 - -# ── 必备生产力 ── +# 小狸插件配置 - 由启动器自动生成 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_TERMINAL = 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_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_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 diff --git a/requirements.txt b/requirements.txt index 19a5f2e..460a20a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,30 +1,12 @@ -# 小狸 Pro-CLI 依赖库 -# 修复后的包名(使用 pip 安装名) +# 小狸 Pro-CLI 依赖 +# 生成于 2026-06-26 20:58 -# 核心依赖 -colorama>=0.4.6 -openai>=1.0.0 -requests>=2.28.0 -ollama>=0.1.0 - -# 图像处理 -Pillow>=9.0.0 -opencv-python>=4.5.0 -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 +PIL +ascii_magic +colorama +cv2 +numpy +ollama +openai +requests +tkinter diff --git a/xcli_core/cli_display.py b/xcli_core/cli_display.py index bc0a4e2..9b6ad46 100644 --- a/xcli_core/cli_display.py +++ b/xcli_core/cli_display.py @@ -45,13 +45,12 @@ class DisplayMixin: response_lines = content.split('\n') if self.tui_output_callback: for i, line in enumerate(response_lines): - if i == 0: - prefix = "[yellow]>[/] " if is_continue else "[cyan]>[/] " - line = prefix + line + # 为每一行都添加前缀,确保整个AI回复都使用正确的样式 + prefix = "[yellow]>[/] " if is_continue else "[cyan]>[/] " if i == len(response_lines) - 1: - self._output(f"{line} {user_id_display}") + self._output(f"{prefix}{line} {user_id_display}") else: - self._output(line) + self._output(f"{prefix}{line}") self._output("") else: for i, line in enumerate(response_lines): diff --git a/xcli_core/cli_tools.py b/xcli_core/cli_tools.py index 1cc1524..d764c54 100644 --- a/xcli_core/cli_tools.py +++ b/xcli_core/cli_tools.py @@ -105,6 +105,9 @@ class ToolMixin: result = self.liugin_manager.execute(tool_name, args, **arguments) else: result = self.liugin_manager.execute(tool_name, args) + # 将 ToolResult 对象转换为字典格式,兼容旧接口 + if hasattr(result, 'to_dict'): + return result.to_dict() return result # 回退:直接调用 handler(旧逻辑) @@ -212,8 +215,10 @@ class ToolMixin: else: tool_args_display = tool_args - # TUI 模式下不做延迟和 ESC 检测 - if not self.tui_output_callback: + # TUI 模式下显示工具执行状态 + if self.tui_output_callback: + self._output(f" 正在执行工具: {tool_name} {tool_args_display}") + else: try: import msvcrt canceled = False diff --git a/xcli_core/memory.py b/xcli_core/memory.py index bde3fe0..806b22d 100644 --- a/xcli_core/memory.py +++ b/xcli_core/memory.py @@ -17,13 +17,71 @@ from typing import List, Dict, Optional, Tuple class MemoryManager: """持久化记忆管理器""" + # 记忆模式常量 + MODE_COMPANION = "companion" # 陪伴模式(长期记忆) + MODE_WORK = "work" # 工作模式(短期记忆) + def __init__(self, project_dir: str): self.project_dir = project_dir self.memory_dir = os.path.join(project_dir, "memory") self.memory_file = os.path.join(project_dir, "MEMORY.md") 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._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): """确保目录存在""" @@ -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: """记忆系统插件 — AI 可读写记忆、搜索历史""" @@ -596,3 +692,7 @@ JSON格式示例: "required": ["action"] } } + # ══════════════════════════════════════════════ + # 模式特定的方法 + # ══════════════════════════════════════════════ + diff --git a/xcli_core/tui.py b/xcli_core/tui.py index 659b35a..9b34234 100644 --- a/xcli_core/tui.py +++ b/xcli_core/tui.py @@ -379,7 +379,8 @@ if TEXTUAL_AVAILABLE: self.styles.opacity = 0 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): @@ -411,17 +412,7 @@ if TEXTUAL_AVAILABLE: # ═══════════════════════════════════════════════════ class SendTextArea(TextArea): - """回车发送消息,Shift+回车换行的输入框""" - 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 - # 其他键正常处理 + """多行输入框,Ctrl+Enter发送消息""" # ═══════════════════════════════════════════════════ # TUI 主应用 @@ -460,9 +451,8 @@ if TEXTUAL_AVAILABLE: yield VerticalScroll(id="chat-scroll") with Vertical(id="input-area"): with Container(id="input-wrapper"): - yield SendTextArea( - placeholder="输入消息... (回车发送, Shift+回车换行)", - id="user-input", + yield SendTextArea( + placeholder="输入消息... (Ctrl+Enter 发送, Enter 换行)", id="user-input", soft_wrap=True, 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-welcome"), (" ║ ║", "msg-welcome"), - (" ║ 小狸 Pro-CLI v5.4.1 ║", "msg-welcome"), + (" ║ 小狸 Pro-CLI v6.0 ║", "msg-welcome"), (" ║ 智能编程助手 · 动画增强版 ║", "msg-welcome"), (" ║ ║", "msg-welcome"), (" ╚══════════════════════════════════════════════╝", "msg-welcome"), @@ -743,7 +733,7 @@ if TEXTUAL_AVAILABLE: 'cls': lambda: self.action_clear(), 'model': 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(), 'tools': lambda: self._show_tools(), 'engines': lambda: self._show_engines(), @@ -878,7 +868,10 @@ if TEXTUAL_AVAILABLE: self.call_after_refresh(self._update_sidebar) 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") elif '❌' in msg or 'X 工具' in msg or '错误' in msg: self._add(f" {msg}", "msg-tool-err")