Fix: F3 tool details toggle, sidebar width, and sync docs with code

- 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
This commit is contained in:
root
2026-04-12 16:01:21 +08:00
parent b7a6a3c1a9
commit b4f509de50
8 changed files with 398 additions and 399 deletions

View File

@ -1,25 +1,28 @@
# TrulyMEM 文档 # TrulyMEM 文档
欢迎来到TrulyMEM项目文档。 欢迎来到 TrulyMEM 项目文档。
## 文档目录 ## 文档目录
- [架构设计](架构.md) - 系统架构和技术设计 - [架构设计](架构.md) - 系统架构和技术设计
- [快速开始](一键启动指南.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) - [问题反馈](https://github.com/yourusername/trulymem/issues)

View File

@ -1,6 +1,6 @@
# 一键启动指南 # TrulyMEM 一键启动指南
## 🚀 快速开始 ## 快速开始
### Windows ### Windows
@ -12,72 +12,69 @@
start.bat start.bat
# 方法3: Python # 方法3: Python
python start.py python trulymem_entry.py
``` ```
### Linux / macOS ### Linux / macOS
```bash ```bash
# 方法1: Shell脚本 # 方法1: Python直接运行
chmod +x start.sh python3 trulymem_entry.py
./start.sh
# 方法2: Python # 方法2: 打包后的可执行文件
python3 start.py chmod +x TrulyMEM
./TrulyMEM
``` ```
## 📋 启动流程 ## 启动流程
``` ```
[Step 1/3] 检查 Python [Step 1/3] 检查 Python
[Step 2/3] 设置虚拟环境 [Step 2/3] 安装依赖
- 创建venv如果不存在 - 安装 requirements.txt 中的依赖
- 安装依赖
[Step 3/3] 启动应用 [Step 3/3] 启动应用
✅ 系统初始化成功! ✅ 系统初始化成功!
``` ```
## ⏱️ 启动时间 ## 启动时间
- **首次启动**: 约1-2分钟安装依赖 - **首次启动**: 约1-2分钟安装依赖
- **后续启动**: 约30秒 - **后续启动**: 约30秒
## 🔧 系统要求 ## 系统要求
### 必需 ### 必需
- **Python 3.8+** - **Python 3.8+**
- **DeepSeek API Key** - **DeepSeek API Key** (或 OpenAI 兼容 API Key)
### 可选 ### 可选
- **Git** - 用于克隆仓库 - **Git** - 用于克隆仓库
## 📝 首次使用 ## 首次使用
### 1. 启动应用 ### 1. 启动应用
```bash ```bash
start.bat # Windows python trulymem_entry.py
./start.sh # Linux/macOS
``` ```
### 2. 配置 API Key ### 2. 配置 API Key
1.**F2** 展开侧边栏 1.**F2** 展开侧边栏
2. 点击 **"配置"** 2. 在 API Key 输入框输入密钥
3. 输入 **DeepSeek API Key** 3. **Enter** 保存
4.**Enter** 保存
### 3. 开始对话 ### 3. 开始对话
- 输入消息 - 在底部输入框输入消息
-**Enter** 发送 -**Enter** 发送
## 🌐 获取 API Key ## 获取 API Key
### DeepSeek ### DeepSeek
@ -89,9 +86,9 @@ start.bat # Windows
### 其他兼容API ### 其他兼容API
- OpenAI - OpenAI
- 其他兼容OpenAI格式的API - 其他兼容OpenAI格式的API(如 Azure OpenAI
## ⚠️ 常见问题 ## 常见问题
### Q: Python 未找到 ### Q: Python 未找到
@ -105,60 +102,98 @@ start.bat # Windows
**A:** 手动安装 **A:** 手动安装
```bash ```bash
python -m venv venv python -m venv venv
venv\Scripts\activate # Windows
source venv/bin/activate # Linux/macOS source venv/bin/activate # Linux/macOS
venv\Scripts\activate # Windows
pip install -r requirements.txt pip install -r requirements.txt
``` ```
### Q: API Key 无效 ### Q: API Key 无效
**A:** 检查API Key格式 **A:** 检查API Key格式
- 应以 `sk-` 开头 - 应以 `sk-` 开头DeepSeek/OpenAI
- 无多余空格 - 无多余空格
- 正确复制 - 正确复制
## 🎯 快捷键 ### Q: 提示"请先配置API Key"
**A:** 按 F2 展开侧边栏,在 API Key 输入框中输入密钥后按 Enter
## 快捷键
| 快捷键 | 功能 | | 快捷键 | 功能 |
|--------|------| |--------|------|
| F1 | 帮助 | | F1 | 帮助 |
| F2 | 侧边栏 | | F2 | 切换侧边栏 |
| F3 | 工具详情 | | F3 | 工具详情 |
| F4 | 聚焦查询框 |
| F5 | 清屏 | | F5 | 清屏 |
| F6 | 退出 | | F6 | 退出 |
## 📊 数据存储 ## 数据存储
- **数据库**: `graph_memory.db` - **数据库**: `graph_memory.db`
- **位置**: 应用目录 - **位置**: 应用目录
- **格式**: SQLite - **格式**: SQLite(内嵌,无需外部数据库)
- **可备份**: 是 - **可备份**: 是,直接复制文件即可
## 🔄 更新应用 ## 更新应用
```bash ```bash
# 拉取最新代码 # 拉取最新代码
git pull git pull
# 重新安装依赖(如有更新)
pip install -r requirements.txt
# 重新启动 # 重新启动
start.bat python trulymem_entry.py
``` ```
## 🗑️ 清理数据 ## 清理数据
```bash ```bash
# 删除数据库 # 删除数据库
rm graph_memory.db 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
```
开始使用吧! 开始使用吧!

View File

@ -1,11 +1,15 @@
# 工作记忆链机制说明 # 工作记忆链机制说明
## 问题背景 ## 概述
当前工程的AI助手在处理连续性任务时存在以下问题: TrulyMEM 通过工作记忆链机制维持对话连贯性。由于系统没有传统的消息历史数组,图数据库是唯一的记忆载体,工作记忆链是维持对话上下文的关键机制。
1. **没有工作记忆链**: AI无法记住当前正在进行的任务状态 ## 核心问题
2. **任务上下文丢失**: 当话题被打断后,AI无法恢复之前的任务
传统 AI 对话系统在处理连续性任务时存在以下问题:
1. **没有工作记忆链**: AI 无法记住当前正在进行的任务状态
2. **任务上下文丢失**: 当话题被打断后AI 无法恢复之前的任务
3. **缺乏任务状态管理**: 没有明确标注任务的完成状态 3. **缺乏任务状态管理**: 没有明确标注任务的完成状态
### 问题示例 ### 问题示例
@ -18,171 +22,50 @@ AI: 好的喵!我接:为虎作伥喵!
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. **查询人设图** (最高优先级)
1. **NEXT_TASK**: 连接任务节点,形成时间链
``` ```
(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**: 任务节点指向普通记忆节点 3. **根据上下文生成回复**
```
(Task_N) -[CONTAINS_INFO]-> (普通记忆节点)
```
例如: `(Task_001) -[CONTAINS_INFO]-> (成语接龙_当前成语)`
4. **SUB_TASK**: 任务节点指向子任务节点 4. **更新工作记忆链** (如有必要)
```
(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_进行中"}
]
}
```
---
## 完整示例: 成语接龙游戏 ## 完整示例: 成语接龙游戏
@ -192,35 +75,24 @@ AI: [猜测] 看起来我们之前应该没有进行过成语接龙游戏,因
用户: 咱来玩成语接龙吧,我先开始,为所欲为 用户: 咱来玩成语接龙吧,我先开始,为所欲为
AI操作: AI操作:
1. 查询工作记忆链 → 无进行中任务 1. 查询人设图 → 获取当前人设(如:猫娘)
2. 创建任务节点: 2. 查询工作记忆链 → 无进行中任务
{ 3. 记录游戏状态:
"triplets": [ 调用 memory_commit
{"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):
{
"triplets": [ "triplets": [
{"subject": "成语接龙_当前成语", "relation": "内容", "object": "为所欲为"}, {"subject": "成语接龙_当前成语", "relation": "内容", "object": "为所欲为"},
{"subject": "成语接龙_当前成语", "relation": "游戏", "object": "成语接龙"} {"subject": "成语接龙_当前成语", "relation": "游戏", "object": "成语接龙"}
] ]
} }
5. 连接任务节点到记忆节点: 4. 创建任务节点:
{ 调用 task_create
"triplets": [ 参数: {
{"subject": "Task_001", "relation": "CONTAINS_INFO", "object": "成语接龙_当前成语"} "task_id": "Task_成语接龙",
] "description": "成语接龙游戏,当前成语:为所欲为",
"info_nodes": ["成语接龙_当前成语"]
} }
6. AI回复: "好的喵!我接:为虎作伥喵!" 5. 回复: "好的喵!我接:为虎作伥喵!"
``` ```
### 第二轮: 话题被打断 ### 第二轮: 话题被打断
@ -229,15 +101,15 @@ AI操作:
用户: 长门有希 用户: 长门有希
AI操作: AI操作:
1. 查询工作记忆链 → 发现Task_001(成语接龙)进行中 1. 查询人设图 → 获取当前人设(猫娘)
2. 暂停Task_001: 2. 查询工作记忆链 → 发现 Task_成语接龙 状态为"进行中"
{ 3. 暂停任务:
"triplets": [ 调用 task_set_state
{"subject": "Task_001", "relation": "HAS_STATE", "object": "State_已暂停"} 参数: {"task_id": "Task_成语接龙", "state": "已暂停"}
] 4. 创建新任务:
} 调用 task_create
3. 创建新任务节点Task_002(讨论长门有希) 参数: {"task_id": "Task_长门有希", "description": "讨论长门有希"}
4. AI回复关于长门有希的内容 5. 回复关于长门有希的内容
``` ```
### 第三轮: 用户要求继续游戏 ### 第三轮: 用户要求继续游戏
@ -246,62 +118,65 @@ AI操作:
用户: 关于刚才的成语接龙,我并不知道应该怎么接你的成语,请帮我接一下 用户: 关于刚才的成语接龙,我并不知道应该怎么接你的成语,请帮我接一下
AI操作: AI操作:
1. 查询工作记忆链 → 发现Task_001(成语接龙)已暂停 1. 查询人设图 → 获取当前人设(猫娘)
2. 恢复Task_001: 2. 查询工作记忆链 → 发现 Task_成语接龙 状态为"已暂停"
{ 3. 恢复任务:
"triplets": [ 调用 task_set_state
{"subject": "Task_001", "relation": "HAS_STATE", "object": "State_进行中"} 参数: {"task_id": "Task_成语接龙", "state": "进行中"}
] 4. 查询信息节点 → 获取当前成语"为虎作伥"
} 5. 回复: "好的喵!上一个成语是'为虎作伥',我帮你接:伥鬼害人喵!"
3. 查询Task_001的信息节点 → 获取当前成语"为虎作伥"
4. AI回复: "好的喵!上一个成语是'为虎作伥',我帮你接:伥鬼害人喵!"
``` ```
--- ## API 参考
## 修改的文件 ### task_create
1. **graph_memory_tui/core/optimized_operations.py** 创建任务节点,用于跟踪连续性任务。
- 添加了工作记忆链机制的完整说明
- 添加了图数据库结构定义
- 添加了强制执行规则
- 添加了连续性任务处理逻辑
- 添加了完整的示例说明
2. **graph_memory_demo.py** ```json
- 同步添加了工作记忆链机制 {
- 保持了人设图机制的最高优先级 "task_id": "Task_成语接龙",
- 确保了与optimized_operations.py的一致性 "description": "任务概述",
"info_nodes": ["关联的信息节点名称"]
}
```
--- ### task_set_state
## 验证结果 设置任务状态。
所有关键机制已成功添加到提示词中: ```json
{
"task_id": "Task_成语接龙",
"state": "进行中" // 进行中/已完成/已暂停/已取消
}
```
- ✅ 工作记忆链机制 ### task_delete
- ✅ TaskNode节点定义
- ✅ 强制执行规则
- ✅ 连续性任务处理
- ✅ 任务状态转换
- ✅ 完整示例说明
--- 删除任务节点。
```json
{
"task_id": "Task_成语接龙",
"delete_info_nodes": true // 是否删除关联的信息节点
}
```
### task_link_info
关联信息节点到任务。
```json
{
"task_id": "Task_成语接龙",
"info_node_names": ["成语接龙_当前成语", "成语接龙_上一个成语"]
}
```
## 注意事项 ## 注意事项
1. **人设图优先级**: 工作记忆链机制与人设图机制并存,人设图仍然保持最高优先级 1. **人设图优先级最高**: 每轮必须首先查询人设图
2. **强制执行**: 每轮对话必须维护工作记忆链,这是强制要求 2. **工作记忆链是唯一上下文载体**: 没有传统消息历史
3. **任务概述精简**: 任务节点的description应该是精简的概述,不要包含过多细节 3. **任务状态必须及时更新**: 确保状态转换正确
4. **信息节点**: 具体的任务信息应该存储在InfoNode中,通过CONTAINS_INFO边连接 4. **使用专用工具**: 优先使用 task_* 工具而非 memory_commit 处理任务相关操作
5. **状态管理**: 任务状态必须及时更新,确保状态转换的正确性
---
## 未来优化方向
1. **任务优先级**: 可以为任务节点添加优先级属性
2. **任务依赖**: 可以通过SUB_TASK边实现任务依赖关系
3. **任务超时**: 可以为任务添加超时机制,自动暂停长时间未更新的任务
4. **任务归档**: 可以实现任务归档机制,将已完成的任务移到归档区
5. **任务查询优化**: 可以优化任务查询算法,提高查询效率

View File

@ -1,43 +1,103 @@
--- # TrulyMEM 架构设计
第一部分:完整架构设计文档
将此内容保存为 架构.md。
```markdown
# Graph Memory TUI 架构设计
## 核心原则 ## 核心原则
- 键盘驱动,零鼠标依赖 - 键盘驱动,零鼠标依赖
- 极简视觉,信息密度优先 - 极简视觉,信息密度优先
- 工具痕迹默认隐藏,需要时可展开 - 工具痕迹默认隐藏,需要时可展开
## 项目结构
```
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:隐藏侧边栏 │ ┌─────────────────────────────────────┬─────────────────────┐
┌─────────────────────────────┐ │ ───────────── │ F2:隐藏侧边栏
│ 🟠 用户: 量子力学是什么? │ │ API Key: *** ┌─────────────────────────────┐ │ ────────────────────
└─────────────────────────────┘ │ 模型: deepseek▼ │ 🟠 用户: 量子力学是什么? │ │ API Key: ***
│ Base URL: ... └─────────────────────────────┘ │ 模型: deepseek-chat ▼
┌─────────────────────────────┐ │ ───────────── │ Base URL: ...
│ 🔵 模型: 根据记忆... │ │ [图操作日志] ┌─────────────────────────────┐ │ ────────────────────
│ │ │ │ ▼ recall │ │ 🔵 模型: 根据记忆... │ │ [图操作日志]
│ │ 是的!根据记忆... │ │ 实体:... │ │ │ │ ▼ 14:32:15 recall
│ │ │ │ 关系:... │ │ 是的!根据记忆... │ │ 实体: 量子力学...
│ │ [工具:2次] (F3展开) │ │ ───────────── │ │ │ │ 关系: 3条
└─────────────────────────────┘ │ >[查询图...] │ [工具:2次] (F3展开) │ │ ───────────────────│
│ F4:执行Cypher └─────────────────────────────┘ │ >[查询图...]
┌─────────────────────────────┐ └───────────────┘ │ F4:执行 │
│ ┌─────────────────────────────┐ └─────────────────────┘
│ │ 🟠 [输入框...] │ │ │ 🟠 [输入框...] │
│ └─────────────────────────────┘ │ └─────────────────────────────┘
│ F1:帮助 F2:隐藏侧边栏 F5:清屏 F6:退出 │ F1:帮助 F2:隐藏侧边栏 F5:清屏 F6:退出│
└─────────────────────────────────────┴───────────────┘ └─────────────────────────────────────┴─────────────────────
```
### F2后右侧折叠 ### F2后右侧折叠
```
┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ │ │ │
│ ┌─────────────────────────────┐ │ │ ┌─────────────────────────────┐ │
@ -49,7 +109,7 @@
│ │ │ │ │ │ │ │
│ │ 是的!根据记忆... │ │ │ │ 是的!根据记忆... │ │
│ │ │ │ │ │ │ │
│ │ [工具:2次] (F3展开) │ │ │ │ [工具:2次] (F3展开) │ │
│ └─────────────────────────────┘ │ │ └─────────────────────────────┘ │
│ │ │ │
│ ┌─────────────────────────────┐ │ │ ┌─────────────────────────────┐ │
@ -58,6 +118,7 @@
│ F1:帮助 F2:展开侧边栏 F5:清屏 F6:退出│ │ F1:帮助 F2:展开侧边栏 F5:清屏 F6:退出│
│ │ │ │
└─────────────────────────────────────┘ └─────────────────────────────────────┘
```
## 快捷键映射 ## 快捷键映射
@ -69,103 +130,88 @@
| `F4` | 右侧 Cypher 查询框获得焦点(若侧边栏折叠则自动展开) | | `F4` | 右侧 Cypher 查询框获得焦点(若侧边栏折叠则自动展开) |
| `F5` | 清屏(保留记忆,清空显示) | | `F5` | 清屏(保留记忆,清空显示) |
| `F6` / `Ctrl+C` | 退出程序 | | `F6` / `Ctrl+C` | 退出程序 |
| `Tab` | 在左侧输入框和右侧可聚焦控件间循环切换焦点 |
| `↑/↓` | 浏览历史消息(仅在输入框聚焦时生效) |
| `Enter` | 发送消息(输入框聚焦时) |
| `Shift+Enter` | 输入框换行 |
## 焦点管理 ## 焦点管理
- **默认焦点**启动后焦点自动位于左侧底部输入框 - **默认焦点**: 启动后焦点自动位于底部输入框
- **焦点切换**`Tab` 键在输入框、配置区控件(输入框/下拉框)、Cypher 查询框之间循环切换。 - **F4 特殊行为**: 按下 F4 时,若侧边栏处于折叠状态,先自动展开侧边栏,再将焦点移至 Cypher 查询框
- **焦点指示**:当前聚焦的输入框边框高亮(例如亮橙色或加亮背景),状态栏最左侧显示当前焦点位置标识(如 `[Input]``[Config]``[Cypher]`)。
- **F4 特殊行为**:按下 `F4` 时,若侧边栏处于折叠状态,先自动展开侧边栏,再将焦点移至右侧的 Cypher 查询输入框。
- **非输入组件按键处理**:当焦点位于消息历史等非输入区域时,按下字母键可自动将焦点切回输入框并插入字符(可选实现,提升体验)。
## 视觉样式 ## 视觉样式
| 元素 | 样式 | | 元素 | 样式 |
|------|------| |------|------|
| 用户消息 | 橙色边框 (`border: solid orange`) | | 用户消息 | 🟠 橙色头部标识 |
| 模型消息 | 蓝色边框 (`border: solid blue`) | | 模型消息 | 🔵 蓝色头部标识 |
| 底部输入框 | 橙色边框,聚焦时加亮显示 | | 底部输入框 | 橙色边框,聚焦时高亮 |
| 工具调用摘要 | 灰色括号 `[工具:N次]` | | 工具调用摘要 | 灰色括号 `[工具:N次]` |
| 右侧配置区 | 可折叠,默认折叠 | | 右侧边栏 | 深色背景,宽度 70 字符 |
| 右侧日志区 | 格式化文本,最新在上 | | 右侧日志区 | 格式化文本,最新在上 |
## 组件职责 ## 组件职责
### 左侧区域(主对话区) ### 左侧区域(主对话区)
- **消息历史**:滚动容器,支持 Markdown 渲染。 - **MessageHistory**: 滚动容器,显示消息历史
- **输入框**:固定底部,橙色边框,支持焦点切换与高亮指示。 - **InputBox**: 固定底部,消息输入
- **状态栏**:底部显示当前快捷键提示,以及焦点位置标识(如 `[Input]`)。 - **LeftPanel**: 左上面板容器
### 右侧区域(侧边栏) ### 右侧区域(侧边栏)
- **标题栏**:显示 `F2:隐藏侧边栏` - **RightPanel**: 侧边栏容器,可折叠(宽度 70
- **配置区**:可折叠,包含 API Key、模型选择、Base URL 输入框。 - **ConfigSection**: 配置区(API Key、模型选择、Base URL
- **图操作日志**:只读,显示最近 N 次工具调用结果(格式化文本),支持滚动。 - **OperationLog**: 图操作日志,只读
- **快捷查询**:输入框 + 执行按钮,支持直接执行 Cypher 语句。 - **CypherQueryBox**: 直接执行 Cypher 查询
## 交互流程
1. 用户输入 → 左侧底部输入框 → `Enter` 发送。
2. 模型回复 → 左侧消息区,蓝色边框包裹。
3. 工具调用 → 后台异步执行,左侧显示 `[工具:N次]` 摘要。
4. 工具详情 → 按 `F3` 展开/折叠,显示完整调用参数和结果。
5. 右侧日志 → 实时更新,显示格式化后的图操作记录,最新记录置顶。
6. 侧边栏切换 → `F2` 折叠后完全消失,左侧全屏。
7. 焦点切换 → `Tab` 循环移动焦点,`F4` 快速定位到 Cypher 查询框。
## 数据流 ## 数据流
``` ```
用户输入 → InputBox.on_input_box_send_message
用户输入 → ChatInput → GraphMemoryClient.send_message()
GraphMemoryClient.send_message_with_history()
模型响应 ← OpenAI API
OpenAI API / DeepSeek API
有 tool_calls? → execute_tool() → Neo4j
检查 tool_calls → execute_tool() → EmbeddedGraphDB / Neo4jGraph
循环调用 API 直到无 tool_calls
最终回复 → MessageHistory (左侧) 最终回复 → MessageHistory (左侧)
工具记录 → GraphOpsLog (右侧) 工具记录 → OperationLog (右侧)
``` ```
## 技术实现 ## 技术实现
- **框架**textualPython TUI 框架) - **框架**: textualPython TUI 框架)
- **布局**:响应式 CSS-like右侧固定宽度 40 - **布局**: CSS-like 样式,右侧固定宽度 70 字符
- **状态管理**:全局状态对象,包含当前会话、消息历史、工具调用记录 - **状态管理**: App 实例持有配置、数据库连接、API 客户端
- **Neo4j 操作**:复用现有 `Neo4jGraph` 类,异步执行 - **数据库**: SQLite默认内嵌或 Neo4j可选
- **LLM 接口**:复用现有 `GraphMemoryClient`,支持流式响应 - **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`。 ### 任务工具 (4个)
- 移除:命令行 `input`/`print` 交互,改为 TUI 组件。 - `task_create` - 创建任务
- 新增:异步事件循环、组件状态管理、键盘快捷键处理、焦点管理系统。 - `task_set_state` - 设置状态
- 适配:`execute_tool` 返回结果同时更新右侧日志和左侧消息。 - `task_delete` - 删除任务
- `task_link_info` - 关联信息

View File

@ -22,7 +22,7 @@ LeftPanel {
} }
RightPanel { RightPanel {
width: 40; width: 70;
dock: right; dock: right;
background: $panel; background: $panel;
overflow-y: auto; overflow-y: auto;

View File

@ -57,6 +57,18 @@ OperationLog {
height: 1fr; height: 1fr;
margin: 1; margin: 1;
overflow-y: auto; 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 { CypherQueryBox {

View File

@ -3,6 +3,7 @@
from textual.containers import Container, Vertical from textual.containers import Container, Vertical
from textual.widgets import Static from textual.widgets import Static
from textual.message import Message from textual.message import Message
from textual.css.query import NoMatches
from ..models.message import Message as MessageModel from ..models.message import Message as MessageModel
@ -14,6 +15,7 @@ class MessageWidget(Container):
self._message = message self._message = message
self._show_tool_details = False self._show_tool_details = False
self._content_widget = None # 保存内容组件的引用 self._content_widget = None # 保存内容组件的引用
self._tool_details_container = None # 保存工具详情容器引用
def compose(self): def compose(self):
"""构建消息组件""" """构建消息组件"""
@ -35,37 +37,42 @@ class MessageWidget(Container):
# 工具调用指示器 # 工具调用指示器
if self._message.tool_calls: if self._message.tool_calls:
tool_count = len(self._message.tool_calls) tool_count = len(self._message.tool_calls)
toggle_hint = "(F3折叠)" if self._show_tool_details else "(F3展开)"
yield Static( yield Static(
f"[工具:{tool_count}次] (F3展开)", f"[工具:{tool_count}次] {toggle_hint}",
classes="tool-indicator" classes="tool-indicator"
) )
# 工具调用详情(默认折叠) # 工具调用详情容器 - 始终创建,但根据状态显示/隐藏
if self._show_tool_details: self._tool_details_container = Vertical(classes="tool-details")
with Vertical(classes="tool-details"): with self._tool_details_container:
for i, tool_call in enumerate(self._message.tool_calls, 1): for i, tool_call in enumerate(self._message.tool_calls, 1):
yield Static( yield Static(
f"工具 {i}: {tool_call.name}", f"工具 {i}: {tool_call.name}",
classes="tool-name" classes="tool-name"
) )
yield Static( yield Static(
f"参数: {tool_call.arguments}", f"参数: {tool_call.arguments}",
classes="tool-args" classes="tool-args"
) )
# 显示执行结果 # 显示执行结果
if self._message.tool_results: if self._message.tool_results:
for result in self._message.tool_results: for result in self._message.tool_results:
if result.tool_call_id == tool_call.id: if result.tool_call_id == tool_call.id:
# 显示完整结果,不截断 # 显示完整结果,不截断
result_text = result.result result_text = result.result
# 如果结果太长只显示前1000字符但提供完整信息 # 如果结果太长只显示前1000字符但提供完整信息
if len(result_text) > 1000: if len(result_text) > 1000:
result_text = result_text[:1000] + f"\n... (共{len(result.result)}字符按F3查看完整内容)" result_text = result_text[:1000] + f"\n... (共{len(result.result)}字符按F3查看完整内容)"
yield Static( yield Static(
f"结果: {result_text}", f"结果: {result_text}",
classes="tool-result" classes="tool-result"
) )
# 根据状态设置初始显示/隐藏
if not self._show_tool_details:
self._tool_details_container.styles.display = "none"
def update_content(self, new_content: str) -> None: def update_content(self, new_content: str) -> None:
"""更新消息内容""" """更新消息内容"""
@ -75,6 +82,27 @@ class MessageWidget(Container):
def toggle_tool_details(self) -> None: 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._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

View File

@ -34,7 +34,7 @@ class RightPanel(Container):
self.styles.width = 0 self.styles.width = 0
self.styles.display = "none" self.styles.display = "none"
else: else:
self.styles.width = 40 self.styles.width = 70
self.styles.display = "block" self.styles.display = "block"
def is_collapsed(self) -> bool: def is_collapsed(self) -> bool: