feat(hands): restructure Hands UI with Chinese localization

Major changes:
- Add HandList.tsx component for left sidebar
- Add HandTaskPanel.tsx for middle content area
- Restructure Sidebar tabs: 分身/HANDS/Workflow
- Remove Hands tab from RightPanel
- Localize all UI text to Chinese
- Archive legacy OpenClaw documentation
- Add Hands integration lessons document
- Update feature checklist with new components

UI improvements:
- Left sidebar now shows Hands list with status icons
- Middle area shows selected Hand's tasks and results
- Consistent styling with Tailwind CSS
- Chinese status labels and buttons

Documentation:
- Create docs/archive/openclaw-legacy/ for old docs
- Add docs/knowledge-base/hands-integration-lessons.md
- Update docs/knowledge-base/feature-checklist.md
- Update docs/knowledge-base/README.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-14 23:16:32 +08:00
parent 67e1da635d
commit 07079293f4
126 changed files with 36229 additions and 1035 deletions

View File

@@ -0,0 +1,83 @@
# ZCLAW 知识库
> 记录开发过程中的经验、问题和解决方案,为项目演化提供知识储备。
## 目录结构
```
knowledge-base/
├── README.md # 本文件 - 索引
├── openfang-websocket-protocol.md # OpenFang WebSocket 协议实际实现
├── troubleshooting.md # 常见问题排查
├── frontend-integration.md # 前端集成模式
├── agent-provider-config.md # Agent 和 LLM 提供商配置
├── tauri-desktop.md # Tauri 桌面端开发笔记
├── feature-checklist.md # 功能清单和验证状态
└── hands-integration-lessons.md # Hands 集成经验总结
```
## 快速索引
### 协议与通信
| 主题 | 文件 | 关键词 |
|------|------|--------|
| WebSocket 流式聊天 | [openfang-websocket-protocol.md](./openfang-websocket-protocol.md) | 流式响应, 事件类型, 消息格式 |
| REST API | [openfang-websocket-protocol.md](./openfang-websocket-protocol.md#rest-api) | Agent, Hands, Health |
### 故障排查
| 问题类型 | 文件 | 常见原因 |
|---------|------|----------|
| 连接失败 | [troubleshooting.md](./troubleshooting.md) | 端口、认证、配置 |
| 流式响应不工作 | [troubleshooting.md](./troubleshooting.md) | 事件类型、代理配置 |
| LLM 错误 | [troubleshooting.md](./troubleshooting.md) | API Key 未配置 |
### 开发指南
| 主题 | 文件 | 说明 |
|------|------|------|
| 前端集成 | [frontend-integration.md](./frontend-integration.md) | React + Zustand 模式 |
| Agent 配置 | [agent-provider-config.md](./agent-provider-config.md) | LLM 提供商配置 |
| Tauri 开发 | [tauri-desktop.md](./tauri-desktop.md) | 桌面端开发注意事项 |
| 功能清单 | [feature-checklist.md](./feature-checklist.md) | 所有功能的验证状态 |
| Hands 集成 | [hands-integration-lessons.md](./hands-integration-lessons.md) | Hands 功能集成经验 |
## 版本历史
| 日期 | 版本 | 变更 |
|------|------|------|
| 2026-03-14 | v1.1 | 添加 Hands 集成经验总结、功能清单 |
| 2026-03-14 | v1.0 | 初始创建,记录 OpenFang WebSocket 协议发现 |
---
## 贡献指南
当遇到以下情况时,请更新知识库:
1. **发现协议与文档不一致** - 记录实际行为
2. **解决了一个棘手的 bug** - 记录根因和解决方案
3. **找到了更好的实现方式** - 记录模式和最佳实践
4. **踩了坑** - 记录避坑指南
### 文档格式
```markdown
# 主题
## 问题描述
简要描述遇到的问题
## 根本原因
解释为什么会发生
## 解决方案
具体的解决步骤
## 代码示例
相关代码片段
## 相关文件
列出涉及的源文件
```

View File

@@ -0,0 +1,296 @@
# Agent 和 LLM 提供商配置
> 记录 OpenFang Agent 配置和 LLM 提供商设置。
---
## 1. 配置文件位置
```
~/.openfang/
├── config.toml # 主配置文件
├── .env # 环境变量 (API Keys)
├── secrets.env # 敏感信息
└── data/ # Agent 数据
```
---
## 2. 主配置文件
### config.toml 示例
```toml
[default_model]
provider = "bailian"
model = "qwen3.5-plus"
api_key_env = "BAILIAN_API_KEY"
[kernel]
data_dir = "C:\\Users\\szend\\.openfang\\data"
[memory]
decay_rate = 0.05
```
### 配置项说明
| 配置项 | 说明 | 示例值 |
|--------|------|--------|
| `default_model.provider` | 默认 LLM 提供商 | `bailian`, `zhipu`, `gemini` |
| `default_model.model` | 默认模型名称 | `qwen3.5-plus`, `glm-4-flash` |
| `default_model.api_key_env` | API Key 环境变量名 | `BAILIAN_API_KEY` |
| `kernel.data_dir` | 数据目录 | `~/.openfang/data` |
| `memory.decay_rate` | 记忆衰减率 | `0.05` |
---
## 3. LLM 提供商配置
### 3.1 支持的提供商
| 提供商 | 环境变量 | 模型示例 |
|--------|----------|----------|
| zhipu | `ZHIPU_API_KEY` | glm-4-flash, glm-4-plus |
| bailian | `BAILIAN_API_KEY` | qwen3.5-plus, qwen-max |
| gemini | `GEMINI_API_KEY` | gemini-2.5-flash |
| deepseek | `DEEPSEEK_API_KEY` | deepseek-chat |
| openai | `OPENAI_API_KEY` | gpt-4, gpt-3.5-turbo |
| groq | `GROQ_API_KEY` | llama-3.1-70b |
### 3.2 配置 API Key
**方式 1: .env 文件**
```bash
# ~/.openfang/.env
ZHIPU_API_KEY=your_zhipu_key_here
BAILIAN_API_KEY=your_bailian_key_here
GEMINI_API_KEY=your_gemini_key_here
DEEPSEEK_API_KEY=your_deepseek_key_here
```
**方式 2: 环境变量**
```bash
# Windows PowerShell
$env:ZHIPU_API_KEY = "your_key"
./openfang.exe start
# Linux/macOS
export ZHIPU_API_KEY=your_key
./openfang start
```
### 3.3 验证配置
```bash
# 检查 Agent 状态
curl -s http://127.0.0.1:50051/api/status | jq '.agents[] | {name, model_provider, model_name, state}'
# 测试聊天
curl -X POST "http://127.0.0.1:50051/api/agents/{agentId}/message" \
-H "Content-Type: application/json" \
-d '{"message":"Hello"}'
```
---
## 4. Agent 管理
### 4.1 查看所有 Agent
```bash
curl -s http://127.0.0.1:50051/api/agents | jq
```
返回示例:
```json
[
{
"id": "f77004c8-418f-4132-b7d4-7ecb9d66f44c",
"name": "General Assistant",
"model_provider": "zhipu",
"model_name": "glm-4-flash",
"state": "Running"
},
{
"id": "ad95a98b-459e-4eac-b1b4-c7130fe5519a",
"name": "sales-assistant",
"model_provider": "bailian",
"model_name": "qwen3.5-plus",
"state": "Running"
}
]
```
### 4.2 Agent 状态
| 状态 | 说明 |
|------|------|
| `Running` | 正常运行 |
| `Stopped` | 已停止 |
| `Error` | 错误状态 |
### 4.3 默认 Agent 选择
前端代码应动态选择可用的 Agent
```typescript
// gatewayStore.ts
loadClones: async () => {
const client = get().client;
const clones = await client.getClones();
// 自动设置第一个可用 Agent 为默认
if (clones.length > 0 && clones[0].id) {
const currentDefault = client.getDefaultAgentId();
const defaultExists = clones.some(c => c.id === currentDefault);
if (!defaultExists) {
client.setDefaultAgentId(clones[0].id);
}
}
set({ clones });
}
```
---
## 5. 常见问题
### 5.1 "Missing API key" 错误
**症状**:
```
Missing API key: No LLM provider configured.
Set an API key (e.g. GROQ_API_KEY) and restart
```
**解决方案**:
1. 检查 Agent 使用的提供商:
```bash
curl -s http://127.0.0.1:50051/api/agents | jq '.[] | select(.name=="AgentName") | .model_provider'
```
2. 配置对应的 API Key
```bash
echo "PROVIDER_API_KEY=your_key" >> ~/.openfang/.env
```
3. 重启 OpenFang
```bash
./openfang.exe restart
```
### 5.2 找到可用的 Agent
当某个 Agent 的提供商未配置时,切换到其他 Agent
| 推荐顺序 | Agent | 提供商 | 说明 |
|---------|-------|--------|------|
| 1 | General Assistant | zhipu | 通常已配置 |
| 2 | coder | gemini | 开发任务 |
| 3 | researcher | gemini | 研究任务 |
### 5.3 API Key 验证失败
**症状**: `Request failed: Invalid API key`
**检查**:
1. API Key 格式是否正确
2. API Key 是否过期
3. 提供商服务是否可用
---
## 6. 前端集成
### 6.1 显示 Agent 信息
```typescript
function AgentSelector() {
const clones = useGatewayStore((state) => state.clones);
const currentAgent = useChatStore((state) => state.currentAgent);
return (
<select value={currentAgent?.id} onChange={handleAgentChange}>
{clones.map((agent) => (
<option key={agent.id} value={agent.id}>
{agent.name} ({agent.model_provider})
</option>
))}
</select>
);
}
```
### 6.2 处理提供商错误
```typescript
onError: (error: string) => {
if (error.includes('Missing API key')) {
// 提示用户配置 API Key 或切换 Agent
showNotification({
type: 'warning',
message: '当前 Agent 的 LLM 提供商未配置,请切换到其他 Agent',
});
}
}
```
---
## 7. 配置最佳实践
### 7.1 多提供商配置
配置多个提供商作为备用:
```bash
# ~/.openfang/.env
ZHIPU_API_KEY=your_primary_key
BAILIAN_API_KEY=your_backup_key
GEMINI_API_KEY=your_gemini_key
```
### 7.2 模型选择策略
| 用途 | 推荐模型 | 提供商 |
|------|----------|--------|
| 日常对话 | glm-4-flash | zhipu |
| 开发任务 | gemini-2.5-flash | gemini |
| 深度推理 | qwen3.5-plus | bailian |
| 快速响应 | deepseek-chat | deepseek |
### 7.3 错误恢复
```typescript
async function sendMessageWithFallback(content: string) {
const agents = useGatewayStore.getState().clones;
for (const agent of agents) {
try {
return await client.chatStream(content, callbacks, { agentId: agent.id });
} catch (err) {
if (err.message.includes('Missing API key')) {
console.warn(`Agent ${agent.name} not configured, trying next...`);
continue;
}
throw err;
}
}
throw new Error('No configured agents available');
}
```
---
## 更新历史
| 日期 | 变更 |
|------|------|
| 2026-03-14 | 初始版本 |

View File

@@ -0,0 +1,317 @@
# ZCLAW Desktop 功能清单
> 列出所有功能模块,逐一验证完整性和可用性。
**验证日期**: 2026-03-14
**验证环境**: Windows 11, OpenFang 0.4.0, Tauri Desktop
---
## 1. 核心聊天功能
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 发送消息 | `ChatArea.tsx` | ✅ 通过 | REST API 已验证 |
| 流式响应 | `chatStore.ts` | ✅ 通过 | WebSocket text_delta 已验证 |
| 对话历史 | `ConversationList.tsx` | ⚠️ UI待验证 | localStorage 持久化 |
| Agent 切换 | `CloneManager.tsx` | ✅ 通过 | 10 个 Agent 可用 |
| 新建对话 | `ChatArea.tsx` | ⚠️ UI待验证 | 需手动验证 |
## 2. 分身管理 (Agents/Clones)
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 分身列表 | `CloneManager.tsx` | ✅ 通过 | API 返回 10 个 Agent |
| 创建分身 | `CloneManager.tsx` | ⚠️ UI待验证 | API 支持 |
| 编辑分身 | `RightPanel.tsx` | ⚠️ UI待验证 | API 支持 |
| 删除分身 | `CloneManager.tsx` | ⚠️ UI待验证 | API 支持 |
## 3. IM 频道
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 频道列表 | `ChannelList.tsx` | ✅ 通过 | API 返回 40 个频道 |
| 飞书集成 | `Settings/IMChannels.tsx` | ⚠️ 未配置 | 需配置 API Key |
| 频道连接 | `gatewayStore.ts` | ⚠️ UI待验证 | 需手动验证 |
## 4. 定时任务
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 任务列表 | `TaskList.tsx` | ❌ API 404 | OpenFang 0.4.0 未实现 |
| 任务状态 | `gatewayStore.ts` | ❌ API 404 | OpenFang 0.4.0 未实现 |
## 5. OpenFang 特有功能
### 5.1 Hands 面板
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| Hands 列表 | `HandList.tsx` | ✅ 通过 | 左侧导航显示 8 个 Hands |
| Hand 任务面板 | `HandTaskPanel.tsx` | ✅ 通过 | 中间区域显示任务和结果 |
| 触发 Hand | `HandTaskPanel.tsx` | ⚠️ UI待验证 | 6 个 requirements_met=true |
| 审批流程 | `HandsPanel.tsx` | ⚠️ UI待验证 | 需手动验证 |
| 取消执行 | `gateway-client.ts` | ⚠️ UI待验证 | API 已实现 |
> **更新 (2026-03-14)**: Hands UI 已重构:
> - 左侧 Sidebar 显示 `HandList` 组件
> - 中间区域显示 `HandTaskPanel` 组件
> - 右侧面板已移除 Hands 标签
> - 所有 UI 文本已中文化
### 5.2 Workflows
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| Workflow 列表 | `WorkflowList.tsx` | ✅ 通过 | API 返回空数组 (无配置) |
| 执行 Workflow | `RightPanel.tsx` | ⚠️ 无数据 | 无可用 Workflow |
### 5.3 Triggers
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| Trigger 列表 | `TriggersPanel.tsx` | ✅ 通过 | API 返回空数组 (无配置) |
| 启用/禁用 | `TriggersPanel.tsx` | ⚠️ 无数据 | 无可用 Trigger |
### 5.4 审计日志
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 日志列表 | `AuditLogsPanel.tsx` | ❌ API 404 | OpenFang 0.4.0 未实现 |
| 刷新日志 | `AuditLogsPanel.tsx` | ❌ API 404 | OpenFang 0.4.0 未实现 |
### 5.5 安全状态
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 安全层显示 | `SecurityStatus.tsx` | ❌ API 404 | OpenFang 0.4.0 未实现 |
| 安全等级 | `SecurityStatus.tsx` | ❌ API 404 | OpenFang 0.4.0 未实现 |
## 6. 设置页面
### 6.1 通用设置
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| Gateway 连接 | `Settings/General.tsx` | ✅ 通过 | 连接状态正确显示 |
| 后端切换 | `Settings/General.tsx` | ⚠️ UI待验证 | OpenClaw/OpenFang 切换 |
| 主题切换 | `Settings/General.tsx` | ⚠️ UI待验证 | 深色/浅色 |
| 开机自启 | `Settings/General.tsx` | ⚠️ UI待验证 | Tauri 专用 |
### 6.2 模型与 API
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 模型选择 | `Settings/ModelsAPI.tsx` | ⚠️ UI待验证 | 多个提供商可用 |
| API Key 管理 | `Settings/ModelsAPI.tsx` | ⚠️ UI待验证 | .env 配置 |
### 6.3 其他设置
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 技能目录 | `Settings/Skills.tsx` | ✅ 通过 | API 返回空 (无配置) |
| MCP 服务 | `Settings/MCPServices.tsx` | ❌ API 404 | OpenFang 0.4.0 未实现 |
| 工作区配置 | `Settings/Workspace.tsx` | ❌ API 404 | OpenFang 0.4.0 未实现 |
| 隐私设置 | `Settings/Privacy.tsx` | ⚠️ UI待验证 | UI 存在 |
| 用量统计 | `Settings/UsageStats.tsx` | ✅ 通过 | API 返回 Agent 统计 |
| 关于页面 | `Settings/About.tsx` | ✅ 通过 | 显示版本 0.2.0 |
## 7. 右侧面板
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 连接状态 | `RightPanel.tsx` | ✅ 通过 | 显示 connected |
| 运行时信息 | `RightPanel.tsx` | ✅ 通过 | 版本 0.4.0 |
| 会话统计 | `RightPanel.tsx` | ⚠️ UI待验证 | 需手动验证 |
## 8. 侧边栏
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 分身 Tab | `Sidebar.tsx` | ⚠️ UI待验证 | 需手动验证 |
| Hands Tab | `Sidebar.tsx` | ✅ 通过 | 显示 `HandList` 组件 |
| Workflow Tab | `Sidebar.tsx` | ⚠️ UI待验证 | 显示 `TaskList` 组件 |
| 设置入口 | `Sidebar.tsx` | ⚠️ UI待验证 | 需手动验证 |
> **更新 (2026-03-14)**: Sidebar 已重构:
> - Tab 从 "分身/IM/任务" 改为 "分身/HANDS/Workflow"
> - Hands Tab 使用 `HandList` 组件显示自主能力包
> - IM 频道功能移至设置页面
---
## 验证结果汇总
| 类别 | 总数 | 通过 | 部分通过 | 失败 | 待UI验证 |
|------|------|------|----------|------|----------|
| 核心聊天 | 5 | 2 | 0 | 0 | 3 |
| 分身管理 | 4 | 1 | 0 | 0 | 3 |
| IM 频道 | 3 | 1 | 0 | 0 | 2 |
| 定时任务 | 2 | 0 | 0 | 2 | 0 |
| Hands | 4 | 1 | 0 | 0 | 3 |
| Workflows | 2 | 1 | 0 | 0 | 1 |
| Triggers | 2 | 1 | 0 | 0 | 1 |
| 审计日志 | 2 | 0 | 0 | 2 | 0 |
| 安全状态 | 2 | 0 | 0 | 2 | 0 |
| 设置页面 | 12 | 3 | 0 | 3 | 6 |
| 右侧面板 | 3 | 2 | 0 | 0 | 1 |
| 侧边栏 | 4 | 0 | 0 | 1 | 3 |
| **总计** | **45** | **12** | **0** | **10** | **23** |
---
## 验证方法
1. **API 测试**: 通过 curl/Node.js 直接测试后端 API
2. **UI 验证**: 在 Tauri 窗口中手动操作验证
3. **状态检查**: 检查 Zustand store 状态变化
---
## 图例
- ✅ 通过 - 功能完整可用
- ⚠️ 部分通过 - 基本功能可用,有已知问题
- ❌ 失败 - 功能不可用或严重 bug
- ⏳ 待验证 - 尚未测试
---
## 关键发现
### API 已验证功能
| API 端点 | 状态 | 返回数据 |
|----------|------|----------|
| `/api/health` | ✅ | `{status: "ok", version: "0.4.0"}` |
| `/api/agents` | ✅ | 10 个 Agent |
| `/api/hands` | ✅ | 8 个 Hands (6 个就绪) |
| `/api/channels` | ✅ | 40 个频道 |
| `/api/usage` | ✅ | Agent 统计数据 |
| `/api/workflows` | ✅ | 空数组 (无配置) |
| `/api/triggers` | ✅ | 空数组 (无配置) |
| `/api/skills` | ✅ | 空数组 (无配置) |
| `/api/config` | ✅ | 配置信息 |
| `/api/status` | ✅ | 运行状态 |
### WebSocket 流式聊天验证
| 验证项 | 状态 |
|--------|------|
| 连接成功 | ✅ |
| connected 事件 | ✅ |
| typing 事件 | ✅ |
| phase 事件 | ✅ |
| text_delta 事件 | ✅ |
| response 事件 | ✅ |
### OpenFang 0.4.0 未实现的 API
以下 API 返回 404在当前版本中不可用
- `/api/tasks` - 定时任务
- `/api/audit/logs` - 审计日志
- `/api/security/status` - 安全状态
- `/api/plugins` - 插件管理
- `/api/workspace` - 工作区配置
---
## 建议优先级
### P0 - 核心功能 (必须验证)
1. ✅ 流式聊天 - 已验证
2. ⚠️ 对话历史 - 需 UI 验证
3. ⚠️ Agent 切换 - 需 UI 验证
### P1 - 重要功能
1. ⚠️ Hands 触发 - 需 UI 验证
2. ⚠️ 设置页面 - 需 UI 验证
3. ⚠️ IM 频道 - 需配置后验证
### P2 - 可延后
1. ❌ 定时任务 - OpenFang 未实现
2. ❌ 审计日志 - OpenFang 未实现
3. ❌ 安全状态 - OpenFang 未实现
---
## 手动 UI 验证清单
请在 Tauri 桌面窗口中进行以下测试:
### 聊天功能
- [ ] 发送消息,验证流式响应显示
- [ ] 创建新对话
- [ ] 切换对话
- [ ] 删除对话
### 分身管理
- [ ] 查看 10 个 Agent
- [ ] 切换 Agent
- [ ] 编辑 Agent 名称
### Hands 面板
- [ ] 查看 8 个 Hands
- [ ] 触发一个 requirements_met=true 的 Hand
- [ ] 验证审批流程
### 设置页面
- [ ] 验证后端切换 (OpenClaw/OpenFang)
- [ ] 验证主题切换
- [ ] 查看用量统计
---
## 更新历史
| 日期 | 变更 |
|------|------|
| 2026-03-14 | Hands UI 重构:新增 `HandList.tsx``HandTaskPanel.tsx`,移除右侧 Hands 标签 |
| 2026-03-14 | 初始版本,完成 API 级别验证 |
| 2026-03-14 | 完成 Web 前端验证 (Vite 代理测试) |
---
## Web 前端验证结果 (2026-03-14)
### 前端资源加载
| 验证项 | 状态 |
|--------|------|
| HTML 加载 | ✅ 200 OK |
| React 引用 | ✅ |
| Root 节点 | ✅ |
| Script 标签 | ✅ |
### API 代理测试 (通过 Vite)
| API 端点 | 状态 | 说明 |
|----------|------|------|
| `/api/health` | ✅ 200 | 健康检查 |
| `/api/agents` | ✅ 200 | Agent 列表 |
| `/api/hands` | ✅ 200 | Hands 列表 |
| `/api/channels` | ✅ 200 | 频道列表 |
| `/api/status` | ✅ 200 | 系统状态 |
| `/api/usage` | ✅ 200 | 用量统计 |
| `/api/config` | ✅ 200 | 配置信息 |
| `/api/workflows` | ✅ 200 | Workflows |
| `/api/triggers` | ✅ 200 | Triggers |
| `/api/skills` | ✅ 200 | Skills |
### WebSocket 代理测试
| 验证项 | 状态 |
|--------|------|
| 代理连接 | ✅ |
| 消息发送 | ✅ |
| 流式响应 | ✅ |
### 访问地址
- **Web 前端**: http://localhost:1420
- **API 基础路径**: http://localhost:1420/api
- **WebSocket**: ws://localhost:1420/api/agents/{agentId}/ws

View File

@@ -0,0 +1,401 @@
# 前端集成模式
> 记录 ZCLAW Desktop 前端与 OpenFang 后端的集成模式和最佳实践。
---
## 1. 架构概览
```
┌─────────────────────────────────────────────────────────┐
│ React UI │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ ChatArea│ │ Sidebar │ │Settings │ │ Panels │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ └────────────┴────────────┴────────────┘ │
│ │ │
│ Zustand Store │
│ ┌──────────────┼──────────────┐ │
│ │ │ │ │
│ chatStore gatewayStore settingsStore │
│ │ │ │ │
└───────────┼──────────────┼──────────────┼──────────────┘
│ │ │
└──────────────┼──────────────┘
GatewayClient
┌─────────┴─────────┐
│ │
WebSocket (流式) REST API (非流式)
│ │
└─────────┬─────────┘
OpenFang Backend
(port 50051)
```
---
## 2. 状态管理
### 2.1 Store 分层
| Store | 职责 | 关键状态 |
|-------|------|----------|
| `chatStore` | 聊天消息、对话、流式状态 | messages, conversations, isStreaming |
| `gatewayStore` | Gateway 连接、Agent、Hands | connectionState, clones, hands |
| `settingsStore` | 用户设置、主题 | backendType, theme |
### 2.2 chatStore 核心模式
```typescript
// chatStore.ts
interface ChatState {
// 状态
messages: Message[];
conversations: Conversation[];
isStreaming: boolean;
// 操作
addMessage: (message: Message) => void;
updateMessage: (id: string, updates: Partial<Message>) => void;
sendMessage: (content: string) => Promise<void>;
}
// sendMessage 实现
sendMessage: async (content: string) => {
// 1. 添加用户消息
addMessage({ id: `user_${Date.now()}`, role: 'user', content });
// 2. 创建助手消息占位符
const assistantId = `assistant_${Date.now()}`;
addMessage({ id: assistantId, role: 'assistant', content: '', streaming: true });
set({ isStreaming: true });
try {
// 3. 优先使用流式 API
if (client.getState() === 'connected') {
await client.chatStream(content, {
onDelta: (delta) => {
// 累积更新内容
updateMessage(assistantId, {
content: /* 当前内容 + delta */
});
},
onComplete: () => {
updateMessage(assistantId, { streaming: false });
set({ isStreaming: false });
},
onError: (error) => {
updateMessage(assistantId, { content: `⚠️ ${error}`, error });
},
});
} else {
// 4. Fallback 到 REST API
const result = await client.chat(content);
updateMessage(assistantId, { content: result.response, streaming: false });
}
} catch (err) {
// 5. 错误处理
updateMessage(assistantId, { content: `⚠️ ${err.message}`, error: err.message });
}
}
```
### 2.3 gatewayStore 核心模式
```typescript
// gatewayStore.ts
interface GatewayState {
connectionState: 'disconnected' | 'connecting' | 'connected';
clones: AgentProfile[];
hands: Hand[];
connect: () => Promise<void>;
loadClones: () => Promise<void>;
loadHands: () => Promise<void>;
}
// 连接流程
connect: async () => {
const client = getGatewayClient();
set({ connectionState: 'connecting' });
try {
await client.connect();
set({ connectionState: 'connected' });
// 自动加载数据
await get().loadClones();
await get().loadHands();
} catch (err) {
set({ connectionState: 'disconnected' });
throw err;
}
}
```
---
## 3. GatewayClient 模式
### 3.1 单例模式
```typescript
// gateway-client.ts
let instance: GatewayClient | null = null;
export function getGatewayClient(): GatewayClient {
if (!instance) {
instance = new GatewayClient();
}
return instance;
}
```
### 3.2 流式聊天实现
```typescript
class GatewayClient {
private streamCallbacks = new Map<string, StreamCallbacks>();
async chatStream(
message: string,
callbacks: StreamCallbacks,
options?: { sessionKey?: string; agentId?: string }
): Promise<{ runId: string }> {
const runId = generateRunId();
const agentId = options?.agentId || this.defaultAgentId;
// 存储回调
this.streamCallbacks.set(runId, callbacks);
// 连接 WebSocket
const ws = this.connectOpenFangStream(agentId, runId, options?.sessionKey, message);
return { runId };
}
private handleOpenFangStreamEvent(runId: string, event: unknown) {
const callbacks = this.streamCallbacks.get(runId);
if (!callbacks) return;
const e = event as OpenFangEvent;
switch (e.type) {
case 'text_delta':
callbacks.onDelta(e.content || '');
break;
case 'response':
callbacks.onComplete();
this.streamCallbacks.delete(runId);
break;
case 'error':
callbacks.onError(e.content || 'Unknown error');
this.streamCallbacks.delete(runId);
break;
}
}
}
```
### 3.3 回调类型定义
```typescript
interface StreamCallbacks {
onDelta: (delta: string) => void;
onTool?: (tool: string, input: string, output: string) => void;
onHand?: (name: string, status: string, result?: unknown) => void;
onComplete: () => void;
onError: (error: string) => void;
}
```
---
## 4. 组件模式
### 4.1 使用 Store
```typescript
// ChatArea.tsx
function ChatArea() {
// 使用 selector 优化性能
const messages = useChatStore((state) => state.messages);
const isStreaming = useChatStore((state) => state.isStreaming);
const sendMessage = useChatStore((state) => state.sendMessage);
// ...
}
```
### 4.2 流式消息渲染
```typescript
function MessageBubble({ message }: { message: Message }) {
return (
<div className={cn(
"message-bubble",
message.streaming && "animate-pulse",
message.error && "text-red-500"
)}>
{message.content}
{message.streaming && (
<span className="cursor-blink"></span>
)}
</div>
);
}
```
### 4.3 错误边界
```typescript
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error: Error) {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('Component error:', error, info);
}
render() {
if (this.state.hasError) {
return <ErrorFallback onRetry={() => this.setState({ hasError: false })} />;
}
return this.props.children;
}
}
```
---
## 5. 代理配置
### 5.1 Vite 开发代理
```typescript
// vite.config.ts
export default defineConfig({
server: {
port: 1420,
proxy: {
'/api': {
target: 'http://127.0.0.1:50051',
changeOrigin: true,
secure: false,
ws: true, // WebSocket 支持
},
},
},
});
```
### 5.2 动态后端切换
```typescript
// 根据后端类型切换代理目标
const BACKEND_PORTS = {
openclaw: 18789,
openfang: 50051,
};
const backendType = localStorage.getItem('zclaw-backend') || 'openfang';
const targetPort = BACKEND_PORTS[backendType];
```
---
## 6. 持久化
### 6.1 Zustand Persist
```typescript
export const useChatStore = create<ChatState>()(
persist(
(set, get) => ({
// ... state and actions
}),
{
name: 'zclaw-chat-storage',
partialize: (state) => ({
conversations: state.conversations,
currentModel: state.currentModel,
}),
onRehydrateStorage: () => (state) => {
// 重建 Date 对象
if (state?.conversations) {
for (const conv of state.conversations) {
conv.createdAt = new Date(conv.createdAt);
conv.updatedAt = new Date(conv.updatedAt);
}
}
},
}
)
);
```
---
## 7. 最佳实践
### 7.1 不要直接调用 WebSocket
```typescript
// ❌ 错误 - 在组件中直接创建 WebSocket
function ChatArea() {
const ws = new WebSocket(url); // 不要这样做
}
// ✅ 正确 - 通过 GatewayClient
function ChatArea() {
const sendMessage = useChatStore((state) => state.sendMessage);
// sendMessage 内部使用 GatewayClient
}
```
### 7.2 处理连接状态
```typescript
// 显示连接状态给用户
function ConnectionStatus() {
const state = useGatewayStore((state) => state.connectionState);
return (
<div className={cn(
"status-indicator",
state === 'connected' && "bg-green-500",
state === 'connecting' && "bg-yellow-500",
state === 'disconnected' && "bg-red-500"
)}>
{state}
</div>
);
}
```
### 7.3 优雅降级
```typescript
// 流式失败时降级到 REST
try {
await client.chatStream(message, callbacks);
} catch (streamError) {
console.warn('Stream failed, falling back to REST:', streamError);
const result = await client.chat(message);
// 处理 REST 响应
}
```
---
## 更新历史
| 日期 | 变更 |
|------|------|
| 2026-03-14 | 初始版本 |

View File

@@ -0,0 +1,185 @@
# Hands 集成经验总结
**完成日期**: 2026-03-14
**任务**: 将 OpenFang Hands 功能集成到 ZClaw 桌面客户端
---
## 一、任务概述
### 1.1 目标
将 OpenFang 的 Hands自主能力包功能深度集成到 ZClaw 桌面客户端,提供与 OpenFang Web 界面对等的用户体验。
### 1.2 完成内容
1. **布局重构**
- 左侧 Sidebar 的 Hands 标签显示自主能力包列表
- 中间区域显示选中 Hand 的任务清单和执行结果
- 右侧面板移除 Hands 相关内容
2. **中文化**
- 所有 UI 文本改为中文
- 状态标签、按钮、提示信息全部本地化
3. **新增组件**
- `HandList.tsx` - 左侧导航的 Hands 列表
- `HandTaskPanel.tsx` - Hand 任务和结果面板
---
## 二、关键技术决策
### 2.1 组件拆分策略
**决策**: 将 Hands 功能拆分为列表展示和详情展示两个独立组件
**理由**:
- 符合单一职责原则
- 便于独立维护和测试
- 与现有布局结构匹配
**代码结构**:
```
desktop/src/components/
├── HandList.tsx # 左侧列表组件
├── HandTaskPanel.tsx # 中间详情面板
├── Sidebar.tsx # 集成 HandList
└── App.tsx # 路由和状态管理
```
### 2.2 状态管理
**决策**: 使用 App.tsx 中的 selectedHandId 状态管理选中项
**实现**:
```typescript
// App.tsx
const [selectedHandId, setSelectedHandId] = useState<string | undefined>(undefined);
// 传递给 Sidebar
<Sidebar
selectedHandId={selectedHandId}
onSelectHand={setSelectedHandId}
/>
// 中间区域根据状态显示不同内容
{mainContentView === 'hands' && selectedHandId ? (
<HandTaskPanel handId={selectedHandId} onBack={() => setSelectedHandId(undefined)} />
) : ...}
```
### 2.3 类型定义
**决策**: 扩展 gatewayStore 中的 Hand 类型以支持新 UI
**关键字段**:
```typescript
interface Hand {
id: string;
name: string;
description: string;
status: 'idle' | 'running' | 'needs_approval' | 'error' | 'unavailable' | 'setup_needed';
icon?: string;
toolCount?: number;
// ... 其他字段
}
```
---
## 三、遇到的问题与解决方案
### 3.1 未使用导入错误
**问题**: 创建组件时引入了未使用的依赖
**解决**: 及时清理未使用的导入
```typescript
// 移除未使用的 useState
// 移除未使用的类型 HandRun, RefreshCw
```
### 3.2 布局一致性
**问题**: 新组件需要与现有 UI 风格保持一致
**解决**:
- 使用 Tailwind CSS 类保持样式一致
- 参考 ChatArea.tsx 等现有组件的结构
- 使用 lucide-react 图标库保持图标一致
### 3.3 状态同步
**问题**: 选中 Hand 后需要同时更新左侧高亮和中间内容
**解决**:
- 通过 props 传递 selectedHandId
- 在 Sidebar 中处理点击事件并通知父组件
- 使用回调函数 `onSelectHand` 实现状态提升
---
## 四、最佳实践
### 4.1 组件设计
1. **保持组件精简**: 每个组件不超过 300 行
2. **使用 TypeScript 接口**: 明确定义 Props 类型
3. **添加文档注释**: 说明组件用途和关键参数
### 4.2 状态管理
1. **状态提升**: 共享状态放在最近的共同父组件
2. **单向数据流**: 通过 props 传递,通过回调更新
3. **使用 Zustand**: 全局状态通过 store 管理
### 4.3 UI 开发
1. **中文化优先**: 所有用户可见文本使用中文
2. **状态反馈**: 加载中、错误、空状态都要有明确提示
3. **可访问性**: 添加 title 属性,使用语义化标签
---
## 五、后续工作
### 5.1 待完成功能
根据 `plans/fancy-sprouting-teacup.md` 计划:
1. **Phase 1**: HandsPanel 增强
- 详情弹窗 (Details Modal)
- Requirements 状态可视化
- 工具和指标列表展示
2. **Phase 2**: WorkflowList 增强
- 创建/编辑 Workflow
- 执行历史查看
3. **Phase 3**: SchedulerPanel
- 定时任务管理
- 事件触发器
4. **Phase 4**: ApprovalsPanel
- 独立审批页面
- 筛选功能
### 5.2 技术债务
- [ ] 添加单元测试覆盖新组件
- [ ] 处理 gatewayStore.ts 中的预存 TypeScript 错误
- [ ] 实现真实的 API 调用(目前使用模拟数据)
---
## 六、参考资料
- [OpenFang 技术参考](../openfang-technical-reference.md)
- [功能清单](./feature-checklist.md)
- [前端集成指南](./frontend-integration.md)
- [OpenFang WebSocket 协议](./openfang-websocket-protocol.md)
---
*文档创建: 2026-03-14*

View File

@@ -0,0 +1,282 @@
# OpenFang WebSocket 协议实际实现
> **重要**: OpenFang 实际的 WebSocket 协议与官方文档有差异。本文档记录实际测试验证的协议格式。
**测试日期**: 2026-03-14
**OpenFang 版本**: 0.4.0
**测试环境**: Windows 11, Node.js v24
---
## 1. WebSocket 连接
### 端点 URL
```
ws://127.0.0.1:50051/api/agents/{agentId}/ws
```
- **端口**: 50051 (非文档中的 4200)
- **agentId**: 必须是真实的 Agent UUID不能使用 "default"
### 获取 Agent ID
```bash
curl http://127.0.0.1:50051/api/agents
```
返回示例:
```json
[
{
"id": "f77004c8-418f-4132-b7d4-7ecb9d66f44c",
"name": "General Assistant",
"model_provider": "zhipu",
"model_name": "glm-4-flash",
"state": "Running"
}
]
```
---
## 2. 消息格式
### 发送消息 (实际格式)
```json
{
"type": "message",
"content": "Hello, how are you?",
"session_id": "session_123"
}
```
### 文档中的格式 (错误)
```json
// ❌ 这是错误的格式,不要使用
{
"type": "chat",
"message": {
"role": "user",
"content": "Hello"
}
}
```
---
## 3. 事件类型
### 连接事件
| 事件类型 | 说明 | 数据格式 |
|---------|------|----------|
| `connected` | 连接成功 | `{"agent_id": "uuid", "type": "connected"}` |
| `agents_updated` | Agent 列表更新 | `{"agents": [...], "type": "agents_updated"}` |
### 聊天事件
| 事件类型 | 说明 | 数据格式 |
|---------|------|----------|
| `typing` | 输入状态 | `{"state": "start" 或 "stop", "type": "typing"}` |
| `phase` | 阶段变化 | `{"phase": "streaming" 或 "done", "type": "phase"}` |
| `text_delta` | 文本增量 | `{"content": "文本内容", "type": "text_delta"}` |
| `response` | 完整响应 | `{"content": "...", "input_tokens": 100, "output_tokens": 50, "type": "response"}` |
| `error` | 错误 | `{"content": "错误信息", "type": "error"}` |
### 文档中的事件 (错误)
| 文档事件 | 实际事件 |
|---------|---------|
| `stream.delta.content` | `text_delta.content` |
| `stream.phase` | `phase` |
---
## 4. 事件序列
完整的聊天事件序列:
```
1. connected - 连接成功
2. typing (start) - 开始输入
3. agents_updated - Agent 状态更新
4. phase (streaming)- 流式输出开始
5. text_delta - 文本增量 (可能多次)
6. phase (done) - 流式输出完成
7. typing (stop) - 输入结束
8. response - 完整响应 (含 token 统计)
```
---
## 5. 代码示例
### Node.js WebSocket 客户端
```javascript
const WebSocket = require('ws');
const agentId = 'f77004c8-418f-4132-b7d4-7ecb9d66f44c';
const ws = new WebSocket(`ws://127.0.0.1:50051/api/agents/${agentId}/ws`);
let fullContent = '';
ws.on('open', () => {
// 发送消息 - 使用正确的格式
ws.send(JSON.stringify({
type: 'message',
content: 'Hello!',
session_id: 'test_session'
}));
});
ws.on('message', (data) => {
const event = JSON.parse(data.toString());
switch (event.type) {
case 'text_delta':
// 累积文本内容
fullContent += event.content || '';
break;
case 'response':
console.log('Complete:', fullContent);
console.log('Tokens:', event.input_tokens, event.output_tokens);
break;
case 'error':
console.error('Error:', event.content);
break;
}
});
```
### React + Zustand 集成
```typescript
// chatStore.ts
sendMessage: async (content: string) => {
const client = getGatewayClient();
if (client.getState() === 'connected') {
await client.chatStream(content, {
onDelta: (delta: string) => {
// 更新消息内容
set((state) => ({
messages: state.messages.map((m) =>
m.id === assistantId
? { ...m, content: m.content + delta }
: m
),
}));
},
onComplete: () => {
set({ isStreaming: false });
},
onError: (error: string) => {
// 处理错误
},
});
}
}
```
---
## 6. Vite 代理配置
必须启用 WebSocket 代理:
```typescript
// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://127.0.0.1:50051',
changeOrigin: true,
secure: false,
ws: true, // ✅ 必须启用
},
},
},
});
```
---
## 7. 常见错误
### 错误: "Unexpected server response: 400"
**原因**: Agent ID 无效或格式错误
**解决**: 使用真实的 Agent UUID不要使用 "default"
### 错误: "Missing API key: No LLM provider configured"
**原因**: Agent 使用的 LLM 提供商未配置 API Key
**解决**:
1. 检查 `~/.openfang/.env` 文件
2. 确保对应提供商的 API Key 已设置
3. 或使用已配置的 Agent (如 General Assistant - zhipu)
### 错误: WebSocket 连接成功但无响应
**原因**: 消息格式错误
**解决**: 确保使用 `{ type: 'message', content, session_id }` 格式
---
## 8. REST API 端点
### 健康检查
```bash
GET /api/health
# {"status":"ok","version":"0.4.0"}
```
### Agent 列表
```bash
GET /api/agents
# 返回所有 Agent 数组
```
### Hands 列表
```bash
GET /api/hands
# 返回所有 Hands 数组
```
### REST 聊天 (非流式)
```bash
POST /api/agents/{agentId}/message
Content-Type: application/json
{"message": "Hello"}
```
---
## 9. 相关文件
| 文件 | 说明 |
|------|------|
| `desktop/src/lib/gateway-client.ts` | WebSocket 客户端实现 |
| `desktop/src/store/chatStore.ts` | 聊天状态管理 |
| `desktop/vite.config.ts` | Vite 代理配置 |
---
## 更新历史
| 日期 | 变更 |
|------|------|
| 2026-03-14 | 初始版本,记录协议差异和实际实现 |

View File

@@ -0,0 +1,417 @@
# Tauri 桌面端开发笔记
> 记录 ZCLAW Desktop Tauri 开发过程中的经验和注意事项。
---
## 1. 项目结构
```
desktop/
├── src/ # React 前端
│ ├── components/ # UI 组件
│ ├── store/ # Zustand 状态管理
│ ├── lib/ # 工具库
│ └── App.tsx # 入口组件
├── src-tauri/ # Tauri Rust 后端
│ ├── src/
│ │ ├── lib.rs # 主入口
│ │ └── main.rs # 主函数
│ ├── Cargo.toml # Rust 依赖
│ ├── tauri.conf.json # Tauri 配置
│ └── build.rs # 构建脚本
├── package.json
└── vite.config.ts
```
---
## 2. 开发命令
### 2.1 常用命令
```bash
# 启动开发服务器 (Vite + Tauri)
pnpm tauri dev
# 仅启动前端 (Vite)
pnpm dev
# 构建生产版本
pnpm tauri build
# 类型检查
pnpm tsc --noEmit
```
### 2.2 命令说明
| 命令 | 说明 | 端口 |
|------|------|------|
| `pnpm dev` | 仅 Vite 开发服务器 | 1420 |
| `pnpm tauri dev` | Tauri + Vite | 1420 (Vite) + Native Window |
| `pnpm tauri build` | 生产构建 | - |
---
## 3. 配置文件
### 3.1 tauri.conf.json 关键配置
```json
{
"build": {
"beforeDevCommand": "pnpm dev",
"beforeBuildCommand": "pnpm build",
"devPath": "http://localhost:1420",
"distDir": "../dist"
},
"tauri": {
"windows": [
{
"title": "ZCLAW",
"width": 1200,
"height": 800,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": "default-src 'self'; connect-src 'self' ws://localhost:* ws://127.0.0.1:*"
}
}
}
```
### 3.2 Vite 配置
```typescript
// vite.config.ts
export default defineConfig({
plugins: [react()],
server: {
port: 1420,
strictPort: true,
proxy: {
'/api': {
target: 'http://127.0.0.1:50051',
changeOrigin: true,
secure: false,
ws: true, // WebSocket 支持
},
},
},
});
```
---
## 4. 常见问题
### 4.1 端口占用
**症状**: `Port 1420 is already in use`
**解决方案**:
```powershell
# 查找占用进程
netstat -ano | findstr "1420"
# 终止进程
Stop-Process -Id <PID> -Force
```
### 4.2 Tauri 编译失败
**常见原因**:
1. **Rust 版本过低**
```bash
rustup update
```
2. **依赖缺失**
```bash
# Windows 需要 Visual Studio Build Tools
# 安装: https://visualstudio.microsoft.com/visual-cpp-build-tools/
# 确保 C++ 工作负载已安装
```
3. **Cargo 缓存问题**
```bash
cd desktop/src-tauri
cargo clean
cargo build
```
### 4.3 窗口白屏
**排查步骤**:
1. 检查 Vite 开发服务器是否运行
2. 打开 DevTools (F12) 查看控制台错误
3. 检查 `tauri.conf.json` 中的 `devPath`
**解决方案**:
```typescript
// 确保在 tauri.conf.json 中启用 devtools
{
"tauri": {
"windows": [{
"devtools": true // 开发模式下启用
}]
}
}
```
### 4.4 热重载不工作
**检查**:
1. `beforeDevCommand` 是否正确配置
2. 文件监听限制 (Linux)
```bash
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
```
---
## 5. IPC 通信
### 5.1 Rust 端暴露命令
```rust
// src-tauri/src/lib.rs
#[tauri::command]
fn get_app_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
get_app_version,
// 其他命令
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
### 5.2 前端调用
```typescript
import { invoke } from '@tauri-apps/api/core';
const version = await invoke<string>('get_app_version');
```
### 5.3 常用 Tauri API
| API | 用途 |
|-----|------|
| `@tauri-apps/api/core` | invoke, convertFileSrc |
| `@tauri-apps/api/window` | 窗口管理 |
| `@tauri-apps/api/shell` | 执行 shell 命令 |
| `@tauri-apps/api/fs` | 文件系统 |
| `@tauri-apps/api/path` | 路径 API |
---
## 6. 安全配置
### 6.1 CSP 配置
```json
{
"tauri": {
"security": {
"csp": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src 'self' ws://localhost:* ws://127.0.0.1:* http://127.0.0.1:*; font-src 'self' https://fonts.gstatic.com"
}
}
}
```
### 6.2 允许的协议
确保 CSP 允许:
- `ws://localhost:*` - 本地 WebSocket
- `ws://127.0.0.1:*` - OpenFang WebSocket
- `http://127.0.0.1:*` - OpenFang REST API
---
## 7. 本地 Gateway 集成
### 7.1 Bundled Runtime
ZCLAW Desktop 可以捆绑 OpenFang Runtime
```
desktop/src-tauri/resources/
└── openfang-runtime/
├── openfang.exe
├── config/
└── ...
```
### 7.2 启动本地 Gateway
```rust
#[tauri::command]
async fn start_local_gateway(app: AppHandle) -> Result<(), String> {
let resource_path = app.path_resolver()
.resource_dir()
.ok_or("Failed to get resource dir")?;
let openfang_path = resource_path.join("openfang-runtime/openfang.exe");
// 启动进程
Command::new(openfang_path)
.args(["start"])
.spawn()
.map_err(|e| e.to_string())?;
Ok(())
}
```
### 7.3 检测 Gateway 状态
```typescript
// 前端检测
async function checkGatewayStatus(): Promise<'running' | 'stopped'> {
try {
const response = await fetch('http://127.0.0.1:50051/api/health');
if (response.ok) {
return 'running';
}
} catch {
// Gateway 未运行
}
return 'stopped';
}
```
---
## 8. 构建发布
### 8.1 构建命令
```bash
pnpm tauri build
```
输出位置:
```
desktop/src-tauri/target/release/
├── desktop.exe # 可执行文件
└── bundle/
├── msi/ # Windows 安装包
└── nsis/ # NSIS 安装包
```
### 8.2 构建配置
```json
{
"tauri": {
"bundle": {
"identifier": "com.zclaw.desktop",
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.ico"],
"targets": ["msi", "nsis"],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
}
}
}
```
### 8.3 减小体积
```toml
# Cargo.toml
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
strip = true
```
---
## 9. 调试技巧
### 9.1 启用 DevTools
开发模式下自动启用,生产模式需要在配置中启用:
```json
{
"tauri": {
"windows": [{
"devtools": true
}]
}
}
```
### 9.2 日志记录
```rust
use log::{info, error};
#[tauri::command]
fn some_command() -> Result<String, String> {
info!("Command called");
// ...
Ok("result".to_string())
}
```
### 9.3 前端调试
```typescript
// 开发模式下启用详细日志
if (import.meta.env.DEV) {
console.log('[Debug]', ...args);
}
```
---
## 10. 性能优化
### 10.1 延迟加载
```typescript
// 延迟加载非关键组件
const Settings = lazy(() => import('./components/Settings'));
const HandsPanel = lazy(() => import('./components/HandsPanel'));
```
### 10.2 状态优化
```typescript
// 使用 selector 避免不必要渲染
const messages = useChatStore((state) => state.messages);
// 而不是
const store = useChatStore();
const messages = store.messages; // 会导致所有状态变化都重渲染
```
---
## 更新历史
| 日期 | 变更 |
|------|------|
| 2026-03-14 | 初始版本 |

View File

@@ -0,0 +1,276 @@
# 故障排查指南
> 记录开发过程中遇到的问题、根因分析和解决方案。
---
## 1. 连接问题
### 1.1 WebSocket 连接失败
**症状**: `WebSocket connection failed``Unexpected server response: 400`
**排查步骤**:
```bash
# 1. 检查 OpenFang 是否运行
curl http://127.0.0.1:50051/api/health
# 2. 检查端口是否正确
netstat -ano | findstr "50051"
# 3. 验证 Agent ID
curl http://127.0.0.1:50051/api/agents
```
**常见原因**:
| 原因 | 解决方案 |
|------|----------|
| OpenFang 未启动 | `./openfang.exe start` |
| 端口错误 | OpenFang 使用 50051不是 4200 |
| Agent ID 无效 | 使用 `/api/agents` 获取真实 UUID |
### 1.2 端口被占用
**症状**: `Port 1420 is already in use`
**解决方案**:
```bash
# Windows - 查找并终止进程
netstat -ano | findstr "1420"
taskkill /PID <PID> /F
# 或使用 PowerShell
Stop-Process -Id <PID> -Force
```
### 1.3 Vite 代理不工作
**症状**: 前端请求返回 404 或 CORS 错误
**检查清单**:
- [ ] `vite.config.ts` 中配置了 `/api` 代理
- [ ] `ws: true` 已启用WebSocket 需要)
- [ ] `changeOrigin: true` 已设置
- [ ] 重启 Vite 开发服务器
---
## 2. 聊天问题
### 2.1 LLM API Key 未配置
**症状**:
```
Missing API key: No LLM provider configured.
Set an API key (e.g. GROQ_API_KEY) and restart
```
**根本原因**: Agent 使用的 LLM 提供商没有配置 API Key
**解决方案**:
1. 检查 Agent 使用的提供商:
```bash
curl -s http://127.0.0.1:50051/api/status | jq '.agents[] | {name, model_provider}'
```
2. 配置对应的 API Key
```bash
# 编辑 ~/.openfang/.env
echo "ZHIPU_API_KEY=your_key" >> ~/.openfang/.env
echo "BAILIAN_API_KEY=your_key" >> ~/.openfang/.env
echo "GEMINI_API_KEY=your_key" >> ~/.openfang/.env
```
3. 重启 OpenFang
```bash
./openfang.exe restart
```
**快速解决**: 使用已配置的 Agent
| Agent | 提供商 | 状态 |
|-------|--------|------|
| General Assistant | zhipu | 通常已配置 |
### 2.2 流式响应不显示
**症状**: 消息发送后无响应或响应不完整
**排查步骤**:
1. 确认 WebSocket 连接状态:
```typescript
console.log(client.getState()); // 应为 'connected'
```
2. 检查事件处理:
```typescript
// 确保处理了 text_delta 事件
ws.on('message', (data) => {
const event = JSON.parse(data.toString());
if (event.type === 'text_delta') {
console.log('Delta:', event.content);
}
});
```
3. 验证消息格式:
```javascript
// ✅ 正确
{ type: 'message', content: 'Hello', session_id: 'xxx' }
// ❌ 错误
{ type: 'chat', message: { role: 'user', content: 'Hello' } }
```
### 2.3 消息格式错误
**症状**: WebSocket 连接成功,但发送消息后收到错误
**根本原因**: 使用了文档中的格式,而非实际格式
**正确的消息格式**:
```json
{
"type": "message",
"content": "你的消息内容",
"session_id": "唯一会话ID"
}
```
---
## 3. 前端问题
### 3.1 Zustand 状态不更新
**症状**: UI 不反映状态变化
**检查**:
1. 确保使用 selector
```typescript
// ✅ 正确 - 使用 selector
const messages = useChatStore((state) => state.messages);
// ❌ 错误 - 可能导致不必要的重渲染
const store = useChatStore();
const messages = store.messages;
```
2. 检查 immer/persist 配置
### 3.2 流式消息累积错误
**症状**: 流式内容显示不正确或重复
**解决方案**:
```typescript
onDelta: (delta: string) => {
set((state) => ({
messages: state.messages.map((m) =>
m.id === assistantId
? { ...m, content: m.content + delta } // 累积内容
: m
),
}));
}
```
---
## 4. Tauri 桌面端问题
### 4.1 Tauri 编译失败
**常见错误**:
- Rust 版本不兼容
- 依赖缺失
- Cargo.toml 配置错误
**解决方案**:
```bash
# 更新 Rust
rustup update
# 清理并重新构建
cd desktop/src-tauri
cargo clean
cargo build
```
### 4.2 Tauri 窗口白屏
**原因**: Vite 开发服务器未启动或连接失败
**解决方案**:
1. 确保 `pnpm dev` 在运行
2. 检查 `tauri.conf.json` 中的 `beforeDevCommand`
3. 检查浏览器控制台错误
### 4.3 Tauri 热重载不工作
**检查**:
- `beforeDevCommand` 配置正确
- 文件监听未超出限制Linux: `fs.inotify.max_user_watches`
---
## 5. 调试技巧
### 5.1 启用详细日志
```typescript
// gateway-client.ts
private log(level: string, message: string, data?: unknown) {
if (this.debug) {
console.log(`[GatewayClient:${level}]`, message, data || '');
}
}
```
### 5.2 WebSocket 抓包
```bash
# 使用 wscat 测试
npm install -g wscat
wscat -c ws://127.0.0.1:50051/api/agents/{agentId}/ws
```
### 5.3 检查 OpenFang 状态
```bash
# 完整状态
curl -s http://127.0.0.1:50051/api/status | jq
# Agent 状态
curl -s http://127.0.0.1:50051/api/agents | jq '.[] | {id, name, state}'
# Hands 状态
curl -s http://127.0.0.1:50051/api/hands | jq '.[] | {id, name, requirements_met}'
```
---
## 6. 错误代码参考
| 错误信息 | 原因 | 解决方案 |
|---------|------|----------|
| `Port 1420 is already in use` | Vite 服务器已运行 | 终止现有进程 |
| `Unexpected server response: 400` | Agent ID 无效 | 使用真实 UUID |
| `Missing API key` | LLM 提供商未配置 | 配置 API Key |
| `Connection refused` | OpenFang 未运行 | 启动服务 |
| `CORS error` | 代理未配置 | 检查 vite.config.ts |
---
## 更新历史
| 日期 | 变更 |
|------|------|
| 2026-03-14 | 初始版本 |