From b4f509de50042a4efbe2e699c66bd9c0018daba0 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 12 Apr 2026 16:01:21 +0800 Subject: [PATCH] Fix: F3 tool details toggle, sidebar width, and sync docs with code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix F3 tool details: use display:none/block instead of re-compose - Increase sidebar width from 40 to 70 for readability - Sync 一键启动指南.md with actual entry points (trulymem_entry.py) - Rewrite 工作记忆链机制说明.md with actual task_* tool APIs - Rewrite 架构.md with actual file structure (38 Python files) - Update docs/README.md with complete feature list --- docs/README.md | 19 +- docs/一键启动指南.md | 113 ++++--- docs/工作记忆链机制说明.md | 331 +++++++-------------- docs/架构.md | 234 +++++++++------ graph_memory_tui/styles/app.css | 2 +- graph_memory_tui/styles/components.css | 12 + graph_memory_tui/widgets/message_widget.py | 84 ++++-- graph_memory_tui/widgets/right_panel.py | 2 +- 8 files changed, 398 insertions(+), 399 deletions(-) diff --git a/docs/README.md b/docs/README.md index 4066bc6..3043fa0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,25 +1,28 @@ # TrulyMEM 文档 -欢迎来到TrulyMEM项目文档。 +欢迎来到 TrulyMEM 项目文档。 ## 文档目录 - [架构设计](架构.md) - 系统架构和技术设计 - [快速开始](一键启动指南.md) - 一键启动指南 +- [工作记忆链机制说明](工作记忆链机制说明.md) - 连续性任务处理 ## 项目简介 -TrulyMEM是一个让AI拥有长期记忆能力的图记忆系统,通过图数据库存储实体关系,让AI能够像人类一样记忆、回忆和管理信息。 +TrulyMEM (TrueHumanMEM) 是一个让 AI 拥有长期记忆能力的图记忆系统,通过图数据库存储实体关系,让 AI 能够像人类一样记忆、回忆和管理信息。 ## 核心特性 -- 长期记忆存储 -- 人设图机制 -- 流式消息显示 -- 跨平台支持 -- 独立部署 +- **长期记忆存储**: 基于 SQLite 内嵌图数据库,开箱即用 +- **人设图机制**: 支持角色扮演和性格设定 +- **工作记忆链**: 维持对话连贯性的任务跟踪机制 +- **流式消息显示**: 实时显示 AI 响应 +- **键盘驱动 TUI**: 无需鼠标,全键盘操作 +- **跨平台支持**: Windows / Linux / macOS +- **独立部署**: 支持打包为可执行文件 ## 快速链接 -- [GitHub仓库](https://github.com/yourusername/trulymem) +- [GitHub 仓库](https://github.com/yourusername/trulymem) - [问题反馈](https://github.com/yourusername/trulymem/issues) diff --git a/docs/一键启动指南.md b/docs/一键启动指南.md index f916edb..3f4bf81 100644 --- a/docs/一键启动指南.md +++ b/docs/一键启动指南.md @@ -1,6 +1,6 @@ -# 一键启动指南 +# TrulyMEM 一键启动指南 -## 🚀 快速开始 +## 快速开始 ### Windows @@ -12,72 +12,69 @@ start.bat # 方法3: Python -python start.py +python trulymem_entry.py ``` ### Linux / macOS ```bash -# 方法1: Shell脚本 -chmod +x start.sh -./start.sh +# 方法1: Python直接运行 +python3 trulymem_entry.py -# 方法2: Python -python3 start.py +# 方法2: 打包后的可执行文件 +chmod +x TrulyMEM +./TrulyMEM ``` -## 📋 启动流程 +## 启动流程 ``` [Step 1/3] 检查 Python ↓ -[Step 2/3] 设置虚拟环境 - - 创建venv(如果不存在) - - 安装依赖 +[Step 2/3] 安装依赖 + - 安装 requirements.txt 中的依赖 ↓ [Step 3/3] 启动应用 ↓ ✅ 系统初始化成功! ``` -## ⏱️ 启动时间 +## 启动时间 - **首次启动**: 约1-2分钟(安装依赖) - **后续启动**: 约30秒 -## 🔧 系统要求 +## 系统要求 ### 必需 - **Python 3.8+** -- **DeepSeek API Key** +- **DeepSeek API Key** (或 OpenAI 兼容 API Key) ### 可选 - **Git** - 用于克隆仓库 -## 📝 首次使用 +## 首次使用 ### 1. 启动应用 ```bash -start.bat # Windows -./start.sh # Linux/macOS +python trulymem_entry.py ``` ### 2. 配置 API Key 1. 按 **F2** 展开侧边栏 -2. 点击 **"配置"** -3. 输入 **DeepSeek API Key** -4. 按 **Enter** 保存 +2. 在 API Key 输入框输入密钥 +3. 按 **Enter** 保存 ### 3. 开始对话 -- 输入消息 +- 在底部输入框输入消息 - 按 **Enter** 发送 -## 🌐 获取 API Key +## 获取 API Key ### DeepSeek @@ -89,9 +86,9 @@ start.bat # Windows ### 其他兼容API - OpenAI -- 其他兼容OpenAI格式的API +- 其他兼容OpenAI格式的API(如 Azure OpenAI) -## ⚠️ 常见问题 +## 常见问题 ### Q: Python 未找到 @@ -105,60 +102,98 @@ start.bat # Windows **A:** 手动安装 ```bash python -m venv venv -venv\Scripts\activate # Windows source venv/bin/activate # Linux/macOS +venv\Scripts\activate # Windows pip install -r requirements.txt ``` ### Q: API Key 无效 **A:** 检查API Key格式 -- 应以 `sk-` 开头 +- 应以 `sk-` 开头(DeepSeek/OpenAI) - 无多余空格 - 正确复制 -## 🎯 快捷键 +### Q: 提示"请先配置API Key" + +**A:** 按 F2 展开侧边栏,在 API Key 输入框中输入密钥后按 Enter + +## 快捷键 | 快捷键 | 功能 | |--------|------| | F1 | 帮助 | -| F2 | 侧边栏 | +| F2 | 切换侧边栏 | | F3 | 工具详情 | +| F4 | 聚焦查询框 | | F5 | 清屏 | | F6 | 退出 | -## 📊 数据存储 +## 数据存储 - **数据库**: `graph_memory.db` - **位置**: 应用目录 -- **格式**: SQLite -- **可备份**: 是 +- **格式**: SQLite(内嵌,无需外部数据库) +- **可备份**: 是,直接复制文件即可 -## 🔄 更新应用 +## 更新应用 ```bash # 拉取最新代码 git pull +# 重新安装依赖(如有更新) +pip install -r requirements.txt + # 重新启动 -start.bat +python trulymem_entry.py ``` -## 🗑️ 清理数据 +## 清理数据 ```bash # 删除数据库 rm graph_memory.db # 重新启动会创建新数据库 -start.bat +python trulymem_entry.py ``` -## 💡 提示 +## 提示 - 首次启动需要安装依赖,请耐心等待 -- API Key 只需配置一次 +- API Key 只需配置一次(自动保存) - 数据自动保存,无需手动操作 -- 可以随时按F6退出 +- 可以随时按 F6 退出 + +## 从源码运行 + +```bash +# 克隆仓库 +git clone https://github.com/yourusername/trulymem.git +cd trulymem + +# 安装依赖 +pip install -r requirements.txt + +# 运行 +python -m graph_memory_tui.main +# 或 +python trulymem_entry.py +``` + +## 打包应用 + +### Windows + +```bash +python build_windows.bat +``` + +### Linux + +```bash +bash build_linux.sh +``` 开始使用吧! diff --git a/docs/工作记忆链机制说明.md b/docs/工作记忆链机制说明.md index 2e22eae..2f85773 100644 --- a/docs/工作记忆链机制说明.md +++ b/docs/工作记忆链机制说明.md @@ -1,11 +1,15 @@ # 工作记忆链机制说明 -## 问题背景 +## 概述 -当前工程的AI助手在处理连续性任务时存在以下问题: +TrulyMEM 通过工作记忆链机制维持对话连贯性。由于系统没有传统的消息历史数组,图数据库是唯一的记忆载体,工作记忆链是维持对话上下文的关键机制。 -1. **没有工作记忆链**: AI无法记住当前正在进行的任务状态 -2. **任务上下文丢失**: 当话题被打断后,AI无法恢复之前的任务 +## 核心问题 + +传统 AI 对话系统在处理连续性任务时存在以下问题: + +1. **没有工作记忆链**: AI 无法记住当前正在进行的任务状态 +2. **任务上下文丢失**: 当话题被打断后,AI 无法恢复之前的任务 3. **缺乏任务状态管理**: 没有明确标注任务的完成状态 ### 问题示例 @@ -18,171 +22,50 @@ AI: 好的喵!我接:为虎作伥喵! AI: (讨论长门有希的内容) 用户: 关于刚才的成语接龙,我并不知道应该怎么接你的成语,请帮我接一下 -AI: [猜测] 看起来我们之前应该没有进行过成语接龙游戏,因为记忆中没有找到相关记录。 +AI: [猜测] 看起来我们之前应该没有进行过成语接龙游戏... ``` -**问题**: AI完全忘记了之前进行的成语接龙游戏,无法恢复任务上下文。 - ---- +**问题**: AI 完全忘记了之前的成语接龙游戏。 ## 解决方案 -### 工作记忆链机制 +### 专用工具 -通过图数据库实现一个**时间序列的任务链**,用于跟踪连续性任务的状态和上下文。 +系统提供 4 个专用任务工具: -### 图数据库结构 +| 工具 | 功能 | 使用场景 | +|------|------|----------| +| `task_create` | 创建任务节点 | 开始新任务 | +| `task_set_state` | 设置任务状态 | 更新进行中/已完成/已暂停/已取消 | +| `task_delete` | 删除任务 | 清理完成任务 | +| `task_link_info` | 关联信息节点 | 连接任务与具体信息 | -#### 节点类型 +### 任务状态 -1. **TaskNode (任务节点)**: 存储任务概述 - - 实体名称: `Task_当前轮次ID` - - 类型: `TaskNode` - - 属性: - - `description`: 任务概述(精简) - - `created_at`: 创建时间 - - `turn_id`: 对话轮次 +- **进行中**: 任务正在执行 +- **已完成**: 任务成功完成 +- **已暂停**: 任务被中断,可恢复 +- **已取消**: 任务被取消 -2. **StateNode (状态节点)**: 存储任务状态 - - 实体名称: `State_进行中` / `State_已完成` / `State_已暂停` / `State_已取消` - - 类型: `StateNode` +## 使用流程 -3. **普通记忆节点**: 通过memory_commit正常插入的记忆节点 - - 就是普通的实体节点,不需要特殊类型 - - 例如: `成语接龙_当前成语`、`成语接龙_上一个成语`等 - - 通过CONTAINS_INFO边与任务节点关联 +### 每轮对话必须执行 -#### 边类型 - -1. **NEXT_TASK**: 连接任务节点,形成时间链 +1. **查询人设图** (最高优先级) ``` - (Task_N) -[NEXT_TASK]-> (Task_N+1) + 调用 memory_recall + 参数: {"query_intent": "AI,人设,角色,性格,语气,说话风格", "depth": 2} ``` -2. **HAS_STATE**: 任务节点指向状态节点 +2. **查询工作记忆链** ``` - (Task_N) -[HAS_STATE]-> (State_进行中) + 调用 memory_recall + 参数: {"query_intent": "TaskNode,工作记忆,任务链", "depth": 2} ``` -3. **CONTAINS_INFO**: 任务节点指向普通记忆节点 - ``` - (Task_N) -[CONTAINS_INFO]-> (普通记忆节点) - ``` - 例如: `(Task_001) -[CONTAINS_INFO]-> (成语接龙_当前成语)` +3. **根据上下文生成回复** -4. **SUB_TASK**: 任务节点指向子任务节点 - ``` - (Task_N) -[SUB_TASK]-> (SubTask_M) - ``` - ---- - -## 强制执行规则 - -### 每轮对话开始时 - -**必须**执行以下操作: - -1. **查询工作记忆链**: - ```json - { - "query_intent": "TaskNode,工作记忆,任务链", - "depth": 2 - } - ``` - -2. **检查是否有进行中的任务**: - - 如果有进行中的任务,检查是否与当前对话相关 - - 如果相关,恢复任务上下文并继续 - - 如果不相关,询问用户是否要暂停当前任务 - -### 每轮对话结束时 - -**必须**执行以下操作: - -1. **创建任务节点**: - ```json - { - "triplets": [ - {"subject": "Task_当前轮次ID", "relation": "is_type", "object": "TaskNode"}, - {"subject": "Task_当前轮次ID", "relation": "has_description", "object": "任务概述(精简)"}, - {"subject": "Task_当前轮次ID", "relation": "created_at", "object": "当前时间"} - ] - } - ``` - -2. **连接到时间链**: - ```json - { - "triplets": [ - {"subject": "上一个Task节点", "relation": "NEXT_TASK", "object": "Task_当前轮次ID"} - ] - } - ``` - -3. **设置任务状态**: - ```json - { - "triplets": [ - {"subject": "Task_当前轮次ID", "relation": "HAS_STATE", "object": "State_进行中"} - ] - } - ``` - -4. **如果任务包含具体信息,通过memory_commit创建普通记忆节点,并用CONTAINS_INFO边连接**: - - 先用memory_commit正常写入记忆(如成语接龙的当前成语) - - 再用CONTAINS_INFO边将任务节点指向这些记忆节点 - ```json - { - "triplets": [ - {"subject": "Task_当前轮次ID", "relation": "CONTAINS_INFO", "object": "记忆节点名称"} - ] - } - ``` - ---- - -## 连续性任务处理 - -### 识别连续性任务 - -以下情况属于连续性任务,**必须**维护工作记忆链: - -- 游戏(成语接龙、猜谜等) -- 多步骤任务(项目开发、学习计划等) -- 需要上下文的对话(故事创作、问题讨论等) -- 被打断的对话(需要恢复上下文) - -### 任务状态转换 - -1. **进行中 → 已完成**: 任务完成时 - ```json - { - "triplets": [ - {"subject": "Task_N", "relation": "HAS_STATE", "object": "State_已完成"} - ] - } - ``` - -2. **进行中 → 已暂停**: 任务被打断时 - ```json - { - "triplets": [ - {"subject": "Task_N", "relation": "HAS_STATE", "object": "State_已暂停"} - ] - } - ``` - -3. **已暂停 → 进行中**: 任务恢复时 - ```json - { - "triplets": [ - {"subject": "Task_N", "relation": "HAS_STATE", "object": "State_进行中"} - ] - } - ``` - ---- +4. **更新工作记忆链** (如有必要) ## 完整示例: 成语接龙游戏 @@ -192,35 +75,24 @@ AI: [猜测] 看起来我们之前应该没有进行过成语接龙游戏,因 用户: 咱来玩成语接龙吧,我先开始,为所欲为 AI操作: -1. 查询工作记忆链 → 无进行中任务 -2. 创建任务节点: - { - "triplets": [ - {"subject": "Task_001", "relation": "is_type", "object": "TaskNode"}, - {"subject": "Task_001", "relation": "has_description", "object": "成语接龙游戏"}, - {"subject": "Task_001", "relation": "created_at", "object": "2024-01-01 11:02:13"} - ] - } -3. 设置状态: - { - "triplets": [ - {"subject": "Task_001", "relation": "HAS_STATE", "object": "State_进行中"} - ] - } -4. 创建普通记忆节点(通过memory_commit): - { +1. 查询人设图 → 获取当前人设(如:猫娘) +2. 查询工作记忆链 → 无进行中任务 +3. 记录游戏状态: + 调用 memory_commit + 参数: { "triplets": [ {"subject": "成语接龙_当前成语", "relation": "内容", "object": "为所欲为"}, {"subject": "成语接龙_当前成语", "relation": "游戏", "object": "成语接龙"} ] } -5. 连接任务节点到记忆节点: - { - "triplets": [ - {"subject": "Task_001", "relation": "CONTAINS_INFO", "object": "成语接龙_当前成语"} - ] +4. 创建任务节点: + 调用 task_create + 参数: { + "task_id": "Task_成语接龙", + "description": "成语接龙游戏,当前成语:为所欲为", + "info_nodes": ["成语接龙_当前成语"] } -6. AI回复: "好的喵!我接:为虎作伥喵!" +5. 回复: "好的喵!我接:为虎作伥喵!" ``` ### 第二轮: 话题被打断 @@ -229,15 +101,15 @@ AI操作: 用户: 长门有希 AI操作: -1. 查询工作记忆链 → 发现Task_001(成语接龙)进行中 -2. 暂停Task_001: - { - "triplets": [ - {"subject": "Task_001", "relation": "HAS_STATE", "object": "State_已暂停"} - ] - } -3. 创建新任务节点Task_002(讨论长门有希) -4. AI回复关于长门有希的内容 +1. 查询人设图 → 获取当前人设(猫娘) +2. 查询工作记忆链 → 发现 Task_成语接龙 状态为"进行中" +3. 暂停任务: + 调用 task_set_state + 参数: {"task_id": "Task_成语接龙", "state": "已暂停"} +4. 创建新任务: + 调用 task_create + 参数: {"task_id": "Task_长门有希", "description": "讨论长门有希"} +5. 回复关于长门有希的内容 ``` ### 第三轮: 用户要求继续游戏 @@ -246,62 +118,65 @@ AI操作: 用户: 关于刚才的成语接龙,我并不知道应该怎么接你的成语,请帮我接一下 AI操作: -1. 查询工作记忆链 → 发现Task_001(成语接龙)已暂停 -2. 恢复Task_001: - { - "triplets": [ - {"subject": "Task_001", "relation": "HAS_STATE", "object": "State_进行中"} - ] - } -3. 查询Task_001的信息节点 → 获取当前成语"为虎作伥" -4. AI回复: "好的喵!上一个成语是'为虎作伥',我帮你接:伥鬼害人喵!" +1. 查询人设图 → 获取当前人设(猫娘) +2. 查询工作记忆链 → 发现 Task_成语接龙 状态为"已暂停" +3. 恢复任务: + 调用 task_set_state + 参数: {"task_id": "Task_成语接龙", "state": "进行中"} +4. 查询信息节点 → 获取当前成语"为虎作伥" +5. 回复: "好的喵!上一个成语是'为虎作伥',我帮你接:伥鬼害人喵!" ``` ---- +## API 参考 -## 修改的文件 +### task_create -1. **graph_memory_tui/core/optimized_operations.py** - - 添加了工作记忆链机制的完整说明 - - 添加了图数据库结构定义 - - 添加了强制执行规则 - - 添加了连续性任务处理逻辑 - - 添加了完整的示例说明 +创建任务节点,用于跟踪连续性任务。 -2. **graph_memory_demo.py** - - 同步添加了工作记忆链机制 - - 保持了人设图机制的最高优先级 - - 确保了与optimized_operations.py的一致性 +```json +{ + "task_id": "Task_成语接龙", + "description": "任务概述", + "info_nodes": ["关联的信息节点名称"] +} +``` ---- +### task_set_state -## 验证结果 +设置任务状态。 -所有关键机制已成功添加到提示词中: +```json +{ + "task_id": "Task_成语接龙", + "state": "进行中" // 进行中/已完成/已暂停/已取消 +} +``` -- ✅ 工作记忆链机制 -- ✅ TaskNode节点定义 -- ✅ 强制执行规则 -- ✅ 连续性任务处理 -- ✅ 任务状态转换 -- ✅ 完整示例说明 +### task_delete ---- +删除任务节点。 + +```json +{ + "task_id": "Task_成语接龙", + "delete_info_nodes": true // 是否删除关联的信息节点 +} +``` + +### task_link_info + +关联信息节点到任务。 + +```json +{ + "task_id": "Task_成语接龙", + "info_node_names": ["成语接龙_当前成语", "成语接龙_上一个成语"] +} +``` ## 注意事项 -1. **人设图优先级**: 工作记忆链机制与人设图机制并存,人设图仍然保持最高优先级 -2. **强制执行**: 每轮对话必须维护工作记忆链,这是强制要求 -3. **任务概述精简**: 任务节点的description应该是精简的概述,不要包含过多细节 -4. **信息节点**: 具体的任务信息应该存储在InfoNode中,通过CONTAINS_INFO边连接 -5. **状态管理**: 任务状态必须及时更新,确保状态转换的正确性 - ---- - -## 未来优化方向 - -1. **任务优先级**: 可以为任务节点添加优先级属性 -2. **任务依赖**: 可以通过SUB_TASK边实现任务依赖关系 -3. **任务超时**: 可以为任务添加超时机制,自动暂停长时间未更新的任务 -4. **任务归档**: 可以实现任务归档机制,将已完成的任务移到归档区 -5. **任务查询优化**: 可以优化任务查询算法,提高查询效率 +1. **人设图优先级最高**: 每轮必须首先查询人设图 +2. **工作记忆链是唯一上下文载体**: 没有传统消息历史 +3. **任务状态必须及时更新**: 确保状态转换正确 +4. **使用专用工具**: 优先使用 task_* 工具而非 memory_commit 处理任务相关操作 diff --git a/docs/架构.md b/docs/架构.md index c195f86..1319f85 100644 --- a/docs/架构.md +++ b/docs/架构.md @@ -1,43 +1,103 @@ ---- - -第一部分:完整架构设计文档 - -将此内容保存为 架构.md。 - -```markdown -# Graph Memory TUI 架构设计 +# TrulyMEM 架构设计 ## 核心原则 + - 键盘驱动,零鼠标依赖 - 极简视觉,信息密度优先 - 工具痕迹默认隐藏,需要时可展开 +## 项目结构 + +``` +TrulyMEM-TrueHumanMEM/ +├── trulymem_entry.py # 打包入口(独立可执行文件) +├── graph_memory_tui/ # 核心应用包 +│ ├── app.py # TUI 主应用 (GraphMemoryApp) +│ ├── main.py # 模块入口 +│ ├── __init__.py +│ ├── core/ # 核心逻辑 +│ │ ├── __init__.py +│ │ ├── imports.py # 动态导入(内嵌DB vs Neo4j) +│ │ ├── embedded_db.py # SQLite 图数据库实现 +│ │ ├── graph_client.py # Neo4j 客户端(可选) +│ │ ├── optimized_operations.py +│ │ ├── prompts/ # 提示词管理 +│ │ │ ├── __init__.py +│ │ │ ├── prompt_manager.py +│ │ │ └── templates/ +│ │ │ └── system_prompt.md +│ │ └── tools/ # 工具定义与执行 +│ │ ├── __init__.py +│ │ ├── memory_tools.py # 6个记忆工具 + 人设/任务工具 +│ │ ├── tool_executor.py # 工具执行器 +│ │ └── tool_limiter.py # 调用限制器 +│ ├── models/ # 数据模型 +│ │ ├── __init__.py +│ │ ├── message.py # Message, ToolCall, ToolResult +│ │ ├── config.py # AppConfig +│ │ └── log_entry.py # LogEntry +│ ├── services/ # 服务层 +│ │ ├── __init__.py +│ │ ├── config_manager.py # 配置持久化 +│ │ ├── config_service.py +│ │ ├── chat_service.py +│ │ └── tool_service.py +│ ├── handlers/ # 事件处理 +│ │ ├── __init__.py +│ │ ├── focus_handler.py +│ │ ├── key_handler.py +│ │ └── message_handler.py +│ ├── widgets/ # TUI 组件 +│ │ ├── __init__.py +│ │ ├── left_panel.py # 左侧主对话区 +│ │ ├── right_panel.py # 右侧边栏 +│ │ ├── message_history.py # 消息历史列表 +│ │ ├── message_widget.py # 单条消息组件 +│ │ ├── input_box.py # 底部输入框 +│ │ ├── config_section.py # 配置区 +│ │ ├── operation_log.py # 图操作日志 +│ │ ├── cypher_query_box.py # Cypher 查询 +│ │ └── status_bar.py # 状态栏 +│ └── styles/ # 样式文件 +│ ├── __init__.py +│ ├── app.css +│ ├── components.css +│ └── messages.css +├── tests/ # 测试 +├── docs/ # 文档 +├── requirements.txt # 依赖 +└── build_*.{bat,sh} # 打包脚本 +``` + ## 布局结构 ### 默认视图(右侧展开) -┌─────────────────────────────────────┬───────────────┐ -│ │ F2:隐藏侧边栏 │ -│ ┌─────────────────────────────┐ │ ───────────── │ -│ │ 🟠 用户: 量子力学是什么? │ │ API Key: *** │ -│ └─────────────────────────────┘ │ 模型: deepseek▼│ -│ │ Base URL: ... │ -│ ┌─────────────────────────────┐ │ ───────────── │ -│ │ 🔵 模型: 根据记忆... │ │ [图操作日志] │ -│ │ │ │ ▼ recall │ -│ │ 是的!根据记忆... │ │ 实体:... │ -│ │ │ │ 关系:... │ -│ │ [工具:2次] (F3展开) │ │ ───────────── │ -│ └─────────────────────────────┘ │ >[查询图...] │ -│ │ F4:执行Cypher │ -│ ┌─────────────────────────────┐ └───────────────┘ +``` +┌─────────────────────────────────────┬─────────────────────┐ +│ │ F2:隐藏侧边栏 │ +│ ┌─────────────────────────────┐ │ ────────────────────│ +│ │ 🟠 用户: 量子力学是什么? │ │ API Key: *** │ +│ └─────────────────────────────┘ │ 模型: deepseek-chat ▼│ +│ │ Base URL: ... │ +│ ┌─────────────────────────────┐ │ ────────────────────│ +│ │ 🔵 模型: 根据记忆... │ │ [图操作日志] │ +│ │ │ │ ▼ 14:32:15 recall │ +│ │ 是的!根据记忆... │ │ 实体: 量子力学... │ +│ │ │ │ 关系: 3条 │ +│ │ [工具:2次] (F3展开) │ │ ───────────────────│ +│ └─────────────────────────────┘ │ >[查询图...] │ +│ │ F4:执行 │ +│ ┌─────────────────────────────┐ └─────────────────────┘ │ │ 🟠 [输入框...] │ │ └─────────────────────────────┘ -│ F1:帮助 F2:隐藏侧边栏 F5:清屏 F6:退出 │ -└─────────────────────────────────────┴───────────────┘ +│ F1:帮助 F2:隐藏侧边栏 F5:清屏 F6:退出│ +└─────────────────────────────────────┴─────────────────────┘ +``` ### F2后(右侧折叠) +``` ┌─────────────────────────────────────┐ │ │ │ ┌─────────────────────────────┐ │ @@ -49,7 +109,7 @@ │ │ │ │ │ │ 是的!根据记忆... │ │ │ │ │ │ -│ │ [工具:2次] (F3展开) │ │ +│ │ [工具:2次] (F3展开) │ │ │ └─────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ │ @@ -58,6 +118,7 @@ │ F1:帮助 F2:展开侧边栏 F5:清屏 F6:退出│ │ │ └─────────────────────────────────────┘ +``` ## 快捷键映射 @@ -69,103 +130,88 @@ | `F4` | 右侧 Cypher 查询框获得焦点(若侧边栏折叠则自动展开) | | `F5` | 清屏(保留记忆,清空显示) | | `F6` / `Ctrl+C` | 退出程序 | -| `Tab` | 在左侧输入框和右侧可聚焦控件间循环切换焦点 | -| `↑/↓` | 浏览历史消息(仅在输入框聚焦时生效) | -| `Enter` | 发送消息(输入框聚焦时) | -| `Shift+Enter` | 输入框换行 | ## 焦点管理 -- **默认焦点**:启动后焦点自动位于左侧底部输入框。 -- **焦点切换**:`Tab` 键在输入框、配置区控件(输入框/下拉框)、Cypher 查询框之间循环切换。 -- **焦点指示**:当前聚焦的输入框边框高亮(例如亮橙色或加亮背景),状态栏最左侧显示当前焦点位置标识(如 `[Input]`、`[Config]`、`[Cypher]`)。 -- **F4 特殊行为**:按下 `F4` 时,若侧边栏处于折叠状态,先自动展开侧边栏,再将焦点移至右侧的 Cypher 查询输入框。 -- **非输入组件按键处理**:当焦点位于消息历史等非输入区域时,按下字母键可自动将焦点切回输入框并插入字符(可选实现,提升体验)。 +- **默认焦点**: 启动后焦点自动位于底部输入框 +- **F4 特殊行为**: 按下 F4 时,若侧边栏处于折叠状态,先自动展开侧边栏,再将焦点移至 Cypher 查询框 ## 视觉样式 | 元素 | 样式 | |------|------| -| 用户消息 | 橙色边框 (`border: solid orange`) | -| 模型消息 | 蓝色边框 (`border: solid blue`) | -| 底部输入框 | 橙色边框,聚焦时加亮显示 | +| 用户消息 | 🟠 橙色头部标识 | +| 模型消息 | 🔵 蓝色头部标识 | +| 底部输入框 | 橙色边框,聚焦时高亮 | | 工具调用摘要 | 灰色括号 `[工具:N次]` | -| 右侧配置区 | 可折叠,默认折叠 | +| 右侧边栏 | 深色背景,宽度 70 字符 | | 右侧日志区 | 格式化文本,最新在上 | ## 组件职责 ### 左侧区域(主对话区) -- **消息历史**:滚动容器,支持 Markdown 渲染。 -- **输入框**:固定底部,橙色边框,支持焦点切换与高亮指示。 -- **状态栏**:底部显示当前快捷键提示,以及焦点位置标识(如 `[Input]`)。 +- **MessageHistory**: 滚动容器,显示消息历史 +- **InputBox**: 固定底部,消息输入 +- **LeftPanel**: 左上面板容器 ### 右侧区域(侧边栏) -- **标题栏**:显示 `F2:隐藏侧边栏`。 -- **配置区**:可折叠,包含 API Key、模型选择、Base URL 输入框。 -- **图操作日志**:只读,显示最近 N 次工具调用结果(格式化文本),支持滚动。 -- **快捷查询**:输入框 + 执行按钮,支持直接执行 Cypher 语句。 - -## 交互流程 - -1. 用户输入 → 左侧底部输入框 → `Enter` 发送。 -2. 模型回复 → 左侧消息区,蓝色边框包裹。 -3. 工具调用 → 后台异步执行,左侧显示 `[工具:N次]` 摘要。 -4. 工具详情 → 按 `F3` 展开/折叠,显示完整调用参数和结果。 -5. 右侧日志 → 实时更新,显示格式化后的图操作记录,最新记录置顶。 -6. 侧边栏切换 → `F2` 折叠后完全消失,左侧全屏。 -7. 焦点切换 → `Tab` 循环移动焦点,`F4` 快速定位到 Cypher 查询框。 +- **RightPanel**: 侧边栏容器,可折叠(宽度 70) +- **ConfigSection**: 配置区(API Key、模型选择、Base URL) +- **OperationLog**: 图操作日志,只读 +- **CypherQueryBox**: 直接执行 Cypher 查询 ## 数据流 ``` - -用户输入 → ChatInput → GraphMemoryClient.send_message() -↓ -模型响应 ← OpenAI API -↓ -有 tool_calls? → execute_tool() → Neo4j -↓ +用户输入 → InputBox.on_input_box_send_message + ↓ +GraphMemoryClient.send_message_with_history() + ↓ +OpenAI API / DeepSeek API + ↓ +检查 tool_calls → execute_tool() → EmbeddedGraphDB / Neo4jGraph + ↓ +循环调用 API 直到无 tool_calls + ↓ 最终回复 → MessageHistory (左侧) -↓ -工具记录 → GraphOpsLog (右侧) - + ↓ +工具记录 → OperationLog (右侧) ``` ## 技术实现 -- **框架**:textual(Python TUI 框架) -- **布局**:响应式 CSS-like,右侧固定宽度 40 列 -- **状态管理**:全局状态对象,包含当前会话、消息历史、工具调用记录 -- **Neo4j 操作**:复用现有 `Neo4jGraph` 类,异步执行 -- **LLM 接口**:复用现有 `GraphMemoryClient`,支持流式响应 +- **框架**: textual(Python TUI 框架) +- **布局**: CSS-like 样式,右侧固定宽度 70 字符 +- **状态管理**: App 实例持有配置、数据库连接、API 客户端 +- **数据库**: SQLite(默认内嵌)或 Neo4j(可选) +- **LLM 接口**: OpenAI SDK,兼容 DeepSeek 等 -## 文件结构 +## 数据库双模式 +```python +# core/imports.py +if USE_EMBEDDED_DB: + from .embedded_db import EmbeddedGraphDB as Neo4jGraph # SQLite +else: + from .graph_client import Neo4jGraph # Neo4j ``` -graph-memory-tui/ -├── main.py # 程序入口 -├── app.py # Textual App 主类 -├── widgets/ -│ ├── chat_history.py # 左侧消息历史 -│ ├── chat_input.py # 底部输入框 -│ ├── sidebar.py # 右侧边栏容器 -│ ├── config_panel.py # 配置区(可折叠) -│ ├── ops_log.py # 图操作日志 -│ ├── cypher_input.py # 快捷查询输入 -│ └── help_modal.py # 帮助弹窗 -├── core/ -│ ├── neo4j_graph.py # 现有 Neo4jGraph 类迁移 -│ ├── llm_client.py # 现有 GraphMemoryClient 迁移 -│ └── state.py # 全局状态管理 -└── styles.css # Textual 样式定义 +## 工具系统 -``` +### 记忆工具 (6个) +- `memory_recall` - 检索记忆 +- `memory_commit` - 写入记忆 +- `memory_purge` - 删除记忆 +- `memory_introspect` - 查看状态 +- `memory_archive` - 归档记忆 +- `memory_cleanup` - 清理数据 -## 与现有 Demo 的关系 +### 人设工具 (2个) +- `persona_update` - 更新人设 +- `persona_clear` - 清除人设 -- 保留所有核心逻辑:`Neo4jGraph`、`GraphMemoryClient`、`TOOLS` 定义、`system_prompt`。 -- 移除:命令行 `input`/`print` 交互,改为 TUI 组件。 -- 新增:异步事件循环、组件状态管理、键盘快捷键处理、焦点管理系统。 -- 适配:`execute_tool` 返回结果同时更新右侧日志和左侧消息。 \ No newline at end of file +### 任务工具 (4个) +- `task_create` - 创建任务 +- `task_set_state` - 设置状态 +- `task_delete` - 删除任务 +- `task_link_info` - 关联信息 diff --git a/graph_memory_tui/styles/app.css b/graph_memory_tui/styles/app.css index 20b29d7..6c04465 100644 --- a/graph_memory_tui/styles/app.css +++ b/graph_memory_tui/styles/app.css @@ -22,7 +22,7 @@ LeftPanel { } RightPanel { - width: 40; + width: 70; dock: right; background: $panel; overflow-y: auto; diff --git a/graph_memory_tui/styles/components.css b/graph_memory_tui/styles/components.css index 46c4adb..cf2bcd0 100644 --- a/graph_memory_tui/styles/components.css +++ b/graph_memory_tui/styles/components.css @@ -57,6 +57,18 @@ OperationLog { height: 1fr; margin: 1; overflow-y: auto; + padding: 1; +} + +OperationLog .log-entry { + color: $text; + margin: 0 0 1 0; + height: auto; +} + +OperationLog .log-empty { + color: $text-muted; + text-style: italic; } CypherQueryBox { diff --git a/graph_memory_tui/widgets/message_widget.py b/graph_memory_tui/widgets/message_widget.py index 8db4958..a6929de 100644 --- a/graph_memory_tui/widgets/message_widget.py +++ b/graph_memory_tui/widgets/message_widget.py @@ -3,6 +3,7 @@ from textual.containers import Container, Vertical from textual.widgets import Static from textual.message import Message +from textual.css.query import NoMatches from ..models.message import Message as MessageModel @@ -14,6 +15,7 @@ class MessageWidget(Container): self._message = message self._show_tool_details = False self._content_widget = None # 保存内容组件的引用 + self._tool_details_container = None # 保存工具详情容器引用 def compose(self): """构建消息组件""" @@ -35,37 +37,42 @@ class MessageWidget(Container): # 工具调用指示器 if self._message.tool_calls: tool_count = len(self._message.tool_calls) + toggle_hint = "(F3折叠)" if self._show_tool_details else "(F3展开)" yield Static( - f"[工具:{tool_count}次] (F3展开)", + f"[工具:{tool_count}次] {toggle_hint}", classes="tool-indicator" ) - # 工具调用详情(默认折叠) - if self._show_tool_details: - with Vertical(classes="tool-details"): - for i, tool_call in enumerate(self._message.tool_calls, 1): - yield Static( - f"工具 {i}: {tool_call.name}", - classes="tool-name" - ) - yield Static( - f"参数: {tool_call.arguments}", - classes="tool-args" - ) + # 工具调用详情容器 - 始终创建,但根据状态显示/隐藏 + self._tool_details_container = Vertical(classes="tool-details") + with self._tool_details_container: + for i, tool_call in enumerate(self._message.tool_calls, 1): + yield Static( + f"工具 {i}: {tool_call.name}", + classes="tool-name" + ) + yield Static( + f"参数: {tool_call.arguments}", + classes="tool-args" + ) - # 显示执行结果 - if self._message.tool_results: - for result in self._message.tool_results: - if result.tool_call_id == tool_call.id: - # 显示完整结果,不截断 - result_text = result.result - # 如果结果太长,只显示前1000字符,但提供完整信息 - if len(result_text) > 1000: - result_text = result_text[:1000] + f"\n... (共{len(result.result)}字符,按F3查看完整内容)" - yield Static( - f"结果: {result_text}", - classes="tool-result" - ) + # 显示执行结果 + if self._message.tool_results: + for result in self._message.tool_results: + if result.tool_call_id == tool_call.id: + # 显示完整结果,不截断 + result_text = result.result + # 如果结果太长,只显示前1000字符,但提供完整信息 + if len(result_text) > 1000: + result_text = result_text[:1000] + f"\n... (共{len(result.result)}字符,按F3查看完整内容)" + yield Static( + f"结果: {result_text}", + classes="tool-result" + ) + + # 根据状态设置初始显示/隐藏 + if not self._show_tool_details: + self._tool_details_container.styles.display = "none" def update_content(self, new_content: str) -> None: """更新消息内容""" @@ -75,6 +82,27 @@ class MessageWidget(Container): def toggle_tool_details(self) -> None: """切换工具详情显示状态""" - if self._message.tool_calls: + if self._message.tool_calls and self._tool_details_container: self._show_tool_details = not self._show_tool_details - self.refresh() + + # 切换显示/隐藏 + if self._show_tool_details: + self._tool_details_container.styles.display = "block" + else: + self._tool_details_container.styles.display = "none" + + # 更新指示器文字 + self._update_indicator() + + # 刷新布局 + self.refresh(layout=True) + + def _update_indicator(self) -> None: + """更新工具调用指示器文字""" + try: + indicator = self.query_one(".tool-indicator", Static) + tool_count = len(self._message.tool_calls) + toggle_hint = "(F3折叠)" if self._show_tool_details else "(F3展开)" + indicator.update(f"[工具:{tool_count}次] {toggle_hint}") + except NoMatches: + pass diff --git a/graph_memory_tui/widgets/right_panel.py b/graph_memory_tui/widgets/right_panel.py index 5ae0417..fc7664e 100644 --- a/graph_memory_tui/widgets/right_panel.py +++ b/graph_memory_tui/widgets/right_panel.py @@ -34,7 +34,7 @@ class RightPanel(Container): self.styles.width = 0 self.styles.display = "none" else: - self.styles.width = 40 + self.styles.width = 70 self.styles.display = "block" def is_collapsed(self) -> bool: