Phase 4 completion: - Add ApprovalQueue component for managing pending approvals - Add ExecutionResult component for displaying hand/workflow results - Update Sidebar navigation to use unified AutomationPanel - Replace separate 'hands' and 'workflow' tabs with single 'automation' tab - Fix TypeScript type safety issues with unknown types in JSX expressions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
749 lines
20 KiB
Markdown
749 lines
20 KiB
Markdown
# 故障排查指南
|
||
|
||
> 记录开发过程中遇到的问题、根因分析和解决方案。
|
||
|
||
---
|
||
|
||
## 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.1.1 配置热重载限制(重要)
|
||
|
||
**症状**: 修改 `config.toml` 后,`/api/config` 和 `/api/status` 仍然返回旧配置
|
||
|
||
**根本原因**: OpenFang 将配置持久化在 SQLite 数据库中,`config.toml` 只在启动时读取
|
||
|
||
**验证问题**:
|
||
```bash
|
||
# 检查 config.toml 内容
|
||
cat ~/.openfang/config.toml
|
||
# 输出: provider = "zhipu"
|
||
|
||
# 检查 API 返回的配置
|
||
curl -s http://127.0.0.1:50051/api/config
|
||
# 输出: {"default_model":{"provider":"bailian",...}} # 不一致!
|
||
```
|
||
|
||
**解决方案**:
|
||
1. **必须完全重启 OpenFang**(热重载 `/api/config/reload` 不会更新持久化配置)
|
||
```bash
|
||
# 方法 1: 通过 API 关闭(然后手动重启)
|
||
curl -X POST http://127.0.0.1:50051/api/shutdown
|
||
|
||
# 方法 2: 使用 CLI
|
||
./openfang.exe stop
|
||
./openfang.exe start
|
||
```
|
||
|
||
2. **验证配置已生效**:
|
||
```bash
|
||
curl -s http://127.0.0.1:50051/api/status | grep -E "default_provider|default_model"
|
||
# 应输出: "default_provider":"zhipu"
|
||
```
|
||
|
||
**配置文件位置**:
|
||
| 文件 | 用途 |
|
||
|------|------|
|
||
| `~/.openfang/config.toml` | 主配置(启动时读取) |
|
||
| `~/.openfang/.env` | API Key 环境变量 |
|
||
| `~/.openfang/secrets.env` | 敏感信息 |
|
||
| `~/.openfang/data/openfang.db` | SQLite 数据库(持久化配置) |
|
||
|
||
**支持的 Provider**:
|
||
| Provider | 环境变量 | 模型示例 |
|
||
|----------|----------|----------|
|
||
| zhipu | `ZHIPU_API_KEY` | glm-4-flash, GLM-4-Plus |
|
||
| bailian | `BAILIAN_API_KEY` | qwen3.5-plus, qwen3-coder-next |
|
||
| gemini | `GEMINI_API_KEY` | gemini-2.5-flash |
|
||
| deepseek | `DEEPSEEK_API_KEY` | deepseek-chat |
|
||
| openai | `OPENAI_API_KEY` | gpt-4, gpt-3.5-turbo |
|
||
|
||
### 2.2 Agent ID 获取失败导致无法对话
|
||
|
||
**症状**: Gateway 显示已连接,但发送消息无响应或报错 "No agent available"
|
||
|
||
**根本原因**: `fetchDefaultAgentId()` 使用错误的 API 端点
|
||
|
||
**错误代码**:
|
||
```typescript
|
||
// ❌ 错误 - /api/status 不返回 agents 字段
|
||
const status = await this.restGet('/api/status');
|
||
if (status?.agents && status.agents.length > 0) { ... }
|
||
```
|
||
|
||
**修复代码**:
|
||
```typescript
|
||
// ✅ 正确 - 使用 /api/agents 端点
|
||
const agents = await this.restGet<Array<{ id: string; name?: string; state?: string }>>('/api/agents');
|
||
if (agents && agents.length > 0) {
|
||
const runningAgent = agents.find(a => a.state === 'Running');
|
||
this.defaultAgentId = (runningAgent || agents[0]).id;
|
||
}
|
||
```
|
||
|
||
**历史背景**: 这个问题与 OpenClaw 的握手认证问题类似,但根因不同:
|
||
- OpenClaw: 需要 `cli/cli/operator` 身份 + Ed25519 配对
|
||
- OpenFang: 不需要认证握手,但需要正确的 Agent UUID
|
||
|
||
**验证修复**:
|
||
```bash
|
||
# 确认 /api/agents 返回数据
|
||
curl http://127.0.0.1:50051/api/agents
|
||
# 应返回: [{ "id": "uuid", "name": "...", "state": "Running" }]
|
||
```
|
||
|
||
### 2.3 流式响应不显示
|
||
|
||
**症状**: 消息发送后无响应或响应不完整
|
||
|
||
**排查步骤**:
|
||
|
||
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 切换 Agent 后对话消失
|
||
|
||
**症状**: 点击其他 Agent 后,之前的对话内容丢失
|
||
|
||
**根本原因**: `setCurrentAgent` 切换 Agent 时清空了 `messages`,但没有恢复该 Agent 之前的对话
|
||
|
||
**解决方案**:
|
||
|
||
修改 `chatStore.ts` 中的 `setCurrentAgent` 函数:
|
||
```typescript
|
||
setCurrentAgent: (agent) =>
|
||
set((state) => {
|
||
if (state.currentAgent?.id === agent.id) {
|
||
return { currentAgent: agent };
|
||
}
|
||
|
||
// Save current conversation before switching
|
||
const conversations = upsertActiveConversation([...state.conversations], state);
|
||
|
||
// Try to find existing conversation for this agent
|
||
const agentConversation = conversations.find(c => c.agentId === agent.id);
|
||
|
||
if (agentConversation) {
|
||
// Restore the agent's previous conversation
|
||
return {
|
||
conversations,
|
||
currentAgent: agent,
|
||
messages: [...agentConversation.messages],
|
||
sessionKey: agentConversation.sessionKey,
|
||
currentConversationId: agentConversation.id,
|
||
};
|
||
}
|
||
|
||
// No existing conversation, start fresh
|
||
return {
|
||
conversations,
|
||
currentAgent: agent,
|
||
messages: [],
|
||
sessionKey: null,
|
||
currentConversationId: null,
|
||
};
|
||
}),
|
||
```
|
||
|
||
修改 `partialize` 配置以保存 `currentAgentId`:
|
||
```typescript
|
||
partialize: (state) => ({
|
||
conversations: state.conversations,
|
||
currentModel: state.currentModel,
|
||
currentAgentId: state.currentAgent?.id, // 添加此行
|
||
currentConversationId: state.currentConversationId,
|
||
}),
|
||
```
|
||
|
||
添加 `onRehydrateStorage` 钩子恢复消息:
|
||
```typescript
|
||
onRehydrateStorage: () => (state) => {
|
||
// Rehydrate Date objects from JSON strings
|
||
if (state?.conversations) {
|
||
for (const conv of state.conversations) {
|
||
conv.createdAt = new Date(conv.createdAt);
|
||
conv.updatedAt = new Date(conv.updatedAt);
|
||
for (const msg of conv.messages) {
|
||
msg.timestamp = new Date(msg.timestamp);
|
||
msg.streaming = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Restore messages from current conversation if exists
|
||
if (state?.currentConversationId && state.conversations) {
|
||
const currentConv = state.conversations.find(c => c.id === state.currentConversationId);
|
||
if (currentConv) {
|
||
state.messages = [...currentConv.messages];
|
||
state.sessionKey = currentConv.sessionKey;
|
||
}
|
||
}
|
||
},
|
||
```
|
||
|
||
**验证修复**:
|
||
1. 与 Agent A 对话
|
||
2. 切换到 Agent B
|
||
3. 切换回 Agent A → 对话应恢复
|
||
|
||
**文件**: `desktop/src/store/chatStore.ts`
|
||
|
||
### 3.3 流式消息累积错误
|
||
|
||
**症状**: 流式内容显示不正确或重复
|
||
|
||
**解决方案**:
|
||
```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 |
|
||
|
||
---
|
||
|
||
## 7. 新用户引导 (Onboarding)
|
||
|
||
### 7.1 首次使用引导流程
|
||
|
||
**需求**: 当用户第一次使用系统时,引导用户设置默认助手的人格信息(about me、you in my eye 等)。
|
||
|
||
**实现方案**:
|
||
|
||
1. **检测首次使用** - 使用 `useOnboarding` hook 检查 localStorage:
|
||
```typescript
|
||
// desktop/src/lib/use-onboarding.ts
|
||
const ONBOARDING_COMPLETED_KEY = 'zclaw-onboarding-completed';
|
||
const USER_PROFILE_KEY = 'zclaw-user-profile';
|
||
|
||
export function useOnboarding(): OnboardingState {
|
||
// 检查 localStorage 是否有完成记录
|
||
// 返回 { isNeeded, isLoading, markCompleted, resetOnboarding }
|
||
}
|
||
```
|
||
|
||
2. **引导向导** - 使用 `AgentOnboardingWizard` 组件:
|
||
```typescript
|
||
// App.tsx 中的集成
|
||
if (showOnboarding) {
|
||
return (
|
||
<AgentOnboardingWizard
|
||
isOpen={true}
|
||
onClose={() => {
|
||
markCompleted({ userName: 'User', userRole: 'user' });
|
||
setShowOnboarding(false);
|
||
}}
|
||
onSuccess={(clone) => {
|
||
markCompleted({
|
||
userName: clone.userName || 'User',
|
||
userRole: clone.userRole,
|
||
});
|
||
setCurrentAgent({
|
||
id: clone.id,
|
||
name: clone.name,
|
||
icon: clone.emoji || '🦞',
|
||
// ...
|
||
});
|
||
setShowOnboarding(false);
|
||
}}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
3. **数据持久化** - 用户信息存储在 localStorage:
|
||
```typescript
|
||
interface UserProfile {
|
||
userName: string;
|
||
userRole?: string;
|
||
completedAt: string;
|
||
}
|
||
```
|
||
|
||
**文件位置**:
|
||
| 文件 | 用途 |
|
||
|------|------|
|
||
| `desktop/src/lib/use-onboarding.ts` | 引导状态管理 hook |
|
||
| `desktop/src/components/AgentOnboardingWizard.tsx` | 5 步引导向导组件 |
|
||
| `desktop/src/App.tsx` | 引导流程集成 |
|
||
|
||
**引导步骤**:
|
||
1. 认识用户 - 收集用户名称和角色
|
||
2. Agent 身份 - 设置助手名称、昵称、emoji
|
||
3. 人格风格 - 选择沟通风格
|
||
4. 使用场景 - 选择应用场景
|
||
5. 工作环境 - 配置工作目录
|
||
|
||
### 7.2 Onboarding 创建 Agent 失败
|
||
|
||
**症状**: 首次使用引导完成后,点击"完成"按钮报错,Agent 创建失败
|
||
|
||
**根本原因**: Onboarding 应该**更新现有的默认 Agent**,而不是创建新的 Agent
|
||
|
||
**错误代码**:
|
||
```typescript
|
||
// ❌ 错误 - 总是尝试创建新 Agent
|
||
const clone = await createClone(createOptions);
|
||
```
|
||
|
||
**修复代码**:
|
||
```typescript
|
||
// ✅ 正确 - 如果存在现有 Agent 则更新
|
||
let clone: Clone | undefined;
|
||
|
||
if (clones && clones.length > 0) {
|
||
// 更新现有的默认 Agent
|
||
clone = await updateClone(clones[0].id, personalityUpdates);
|
||
} else {
|
||
// 没有现有 Agent 才创建新的
|
||
clone = await createClone(createOptions);
|
||
}
|
||
```
|
||
|
||
**文件**: `desktop/src/components/AgentOnboardingWizard.tsx`
|
||
|
||
**验证修复**:
|
||
1. 清除 localStorage 中的 onboarding 标记
|
||
2. 重新启动应用
|
||
3. 完成引导流程 → 应该成功更新默认 Agent
|
||
|
||
### 3.4 刷新页面后对话内容丢失
|
||
|
||
**症状**: 切换 Tab 时对话正常保留,但按 F5 刷新页面后对话内容消失
|
||
|
||
**根本原因**: `onComplete` 回调中没有将当前对话保存到 `conversations` 数组,导致 `persist` 中间件无法持久化
|
||
|
||
**问题分析**:
|
||
|
||
Zustand 的 `persist` 中间件只保存 `partialize` 中指定的字段:
|
||
```typescript
|
||
partialize: (state) => ({
|
||
conversations: state.conversations, // ← 从这里恢复
|
||
currentModel: state.currentModel,
|
||
currentAgentId: state.currentAgent?.id,
|
||
currentConversationId: state.currentConversationId,
|
||
}),
|
||
```
|
||
|
||
但 `messages` 数组只在内存中,刷新后丢失。恢复逻辑依赖 `conversations` 数组:
|
||
```typescript
|
||
onRehydrateStorage: () => (state) => {
|
||
if (state?.currentConversationId && state.conversations) {
|
||
const currentConv = state.conversations.find(c => c.id === state.currentConversationId);
|
||
if (currentConv) {
|
||
state.messages = [...currentConv.messages]; // ← 从 conversations 恢复
|
||
}
|
||
}
|
||
},
|
||
```
|
||
|
||
**问题**: `onComplete` 回调中只更新了 `messages`,没有调用 `upsertActiveConversation` 保存到 `conversations`
|
||
|
||
**修复代码**:
|
||
```typescript
|
||
onComplete: () => {
|
||
const state = get();
|
||
|
||
// Save conversation to persist across refresh
|
||
const conversations = upsertActiveConversation([...state.conversations], state);
|
||
const currentConvId = state.currentConversationId || conversations[0]?.id;
|
||
|
||
set({
|
||
isStreaming: false,
|
||
conversations, // ← 保存到 conversations 数组
|
||
currentConversationId: currentConvId, // ← 确保 ID 被设置
|
||
messages: state.messages.map((m) =>
|
||
m.id === assistantId ? { ...m, streaming: false, runId } : m
|
||
),
|
||
});
|
||
|
||
// ... rest of the callback
|
||
},
|
||
```
|
||
|
||
**文件**: `desktop/src/store/chatStore.ts`
|
||
|
||
**验证修复**:
|
||
1. 发送消息并获得 AI 回复
|
||
2. 按 F5 刷新页面
|
||
3. 对话内容应该保留
|
||
|
||
### 3.5 ChatArea 输入框布局错乱
|
||
|
||
**症状**: 对话过程中输入框被移动到页面顶部,而不是固定在底部
|
||
|
||
**根本原因**: `ChatArea` 组件返回的是 React Fragment (`<>...</>`),没有包裹在 flex 容器中
|
||
|
||
**问题代码**:
|
||
```typescript
|
||
// ❌ 错误 - Fragment 没有 flex 布局
|
||
return (
|
||
<>
|
||
<div className="h-14 ...">Header</div>
|
||
<div className="flex-1 ...">Messages</div>
|
||
<div className="border-t ...">Input</div>
|
||
</>
|
||
);
|
||
```
|
||
|
||
**修复代码**:
|
||
```typescript
|
||
// ✅ 正确 - 使用 flex 容器
|
||
return (
|
||
<div className="flex flex-col h-full">
|
||
<div className="h-14 flex-shrink-0 ...">Header</div>
|
||
<div className="flex-1 overflow-y-auto ...">Messages</div>
|
||
<div className="flex-shrink-0 ...">Input</div>
|
||
</div>
|
||
);
|
||
```
|
||
|
||
**文件**: `desktop/src/components/ChatArea.tsx`
|
||
|
||
**布局原理**:
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ Header (h-14, flex-shrink-0) │ ← 固定高度
|
||
├─────────────────────────────────────┤
|
||
│ │
|
||
│ Messages (flex-1, overflow-y-auto) │ ← 占据剩余空间,可滚动
|
||
│ │
|
||
├─────────────────────────────────────┤
|
||
│ Input (flex-shrink-0) │ ← 固定在底部
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 记忆系统问题
|
||
|
||
### 7.1 Memory Tab 为空,多轮对话后无记忆
|
||
|
||
**症状**: 经过多次对话后,右侧面板的"记忆"Tab 内容为空
|
||
|
||
**根本原因**: 多个配置问题导致记忆未被提取
|
||
|
||
**问题分析**:
|
||
|
||
1. **LLM 提取默认禁用**: `useLLM: false` 导致只使用规则提取
|
||
2. **提取阈值过高**: `minMessagesForExtraction: 4` 短对话不会触发
|
||
3. **agentId 不一致**: `MemoryPanel` 用 `'zclaw-main'`,`MemoryGraph` 用 `'default'`
|
||
4. **Gateway 端点不存在**: `GatewayLLMAdapter` 调用 `/api/llm/complete`,OpenFang 无此端点
|
||
|
||
**修复方案**:
|
||
|
||
```typescript
|
||
// 1. 启用 LLM 提取 (memory-extractor.ts)
|
||
export const DEFAULT_EXTRACTION_CONFIG: ExtractionConfig = {
|
||
useLLM: true, // 从 false 改为 true
|
||
minMessagesForExtraction: 2, // 从 4 降低到 2
|
||
// ...
|
||
};
|
||
|
||
// 2. 统一 agentId fallback (MemoryGraph.tsx)
|
||
const agentId = currentAgent?.id || 'zclaw-main'; // 从 'default' 改为 'zclaw-main'
|
||
|
||
// 3. 修复 Gateway 适配器端点 (llm-service.ts)
|
||
// 使用 OpenFang 的 /api/agents/{id}/message 端点
|
||
const response = await fetch(`/api/agents/${agentId}/message`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({ message: fullPrompt, ... }),
|
||
});
|
||
```
|
||
|
||
**文件**:
|
||
- `desktop/src/lib/memory-extractor.ts`
|
||
- `desktop/src/lib/llm-service.ts`
|
||
- `desktop/src/components/MemoryGraph.tsx`
|
||
|
||
**验证修复**:
|
||
1. 打开浏览器控制台
|
||
2. 进行至少 2 轮对话
|
||
3. 查看日志: `[MemoryExtractor] Using LLM-powered semantic extraction`
|
||
4. 检查 Memory Tab 是否显示提取的记忆
|
||
|
||
### 7.2 Memory Graph UI 与系统风格不一致
|
||
|
||
**症状**: 记忆图谱使用深色主题,与系统浅色主题不协调
|
||
|
||
**根本原因**: `MemoryGraph.tsx` 硬编码深色背景 `#1a1a2e`
|
||
|
||
**修复方案**:
|
||
|
||
```typescript
|
||
// Canvas 背景 - 支持亮/暗双主题
|
||
ctx.fillStyle = '#f9fafb'; // gray-50 (浅色)
|
||
|
||
// 工具栏 - 添加 dark: 变体
|
||
<div className="bg-gray-100 dark:bg-gray-800/50">
|
||
|
||
// 图谱画布 - 添加 dark: 变体
|
||
<div className="bg-gray-50 dark:bg-gray-900">
|
||
```
|
||
|
||
**文件**: `desktop/src/components/MemoryGraph.tsx`
|
||
|
||
**设计规范**:
|
||
- 使用 Tailwind 的 `dark:` 变体支持双主题
|
||
- 强调色使用 `orange-500` 而非 `blue-600`
|
||
- 文字颜色使用 `gray-700 dark:text-gray-300`
|
||
|
||
---
|
||
|
||
## 8. 相关文档
|
||
|
||
- [OpenFang 配置指南](./openfang-configuration.md) - 配置文件位置、格式和最佳实践
|
||
- [Agent 和 LLM 提供商配置](./agent-provider-config.md) - Agent 管理和 Provider 配置
|
||
- [OpenFang WebSocket 协议](./openfang-websocket-protocol.md) - WebSocket 通信协议
|
||
|
||
---
|
||
|
||
## 更新历史
|
||
|
||
| 日期 | 变更 |
|
||
|------|------|
|
||
| 2026-03-18 | 添加记忆提取和图谱 UI 问题 |
|
||
| 2026-03-18 | 添加刷新后对话丢失问题和 ChatArea 布局问题 |
|
||
| 2026-03-17 | 添加首次使用引导流程 |
|
||
| 2026-03-17 | 添加配置热重载限制问题 |
|
||
| 2026-03-14 | 初始版本 |
|