- Remove direct task.cancel() calls, use stop flag instead - Monitor stop flag every 100ms during tool execution - Cancel tool task immediately when stop flag is detected - Return "命令执行被用户取消" as tool result with role=tool - Save cancellation result to conversation history - Prevent abrupt task termination, allow graceful shutdown Changes: - server/socket_handlers.py: Comment out pending_task.cancel() - server/tasks.py: Comment out entry['task'].cancel() - server/chat_flow_tool_loop.py: Add stop flag monitoring loop Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
188 lines
5.2 KiB
Markdown
188 lines
5.2 KiB
Markdown
# Bug 修复 v3 - 刷新后重复加载问题
|
||
|
||
## 修复的问题
|
||
|
||
### 刷新后加载两遍内容
|
||
**原因**:
|
||
1. `loadInitialData()` 会调用 `fetchAndDisplayHistory()` 加载历史记录
|
||
2. 1 秒后 `restoreTaskState()` 启动轮询
|
||
3. `loadRunningTask()` 设置 `lastEventIndex = 0`,从头开始获取所有事件
|
||
4. 导致已经在历史中的事件被重复处理
|
||
|
||
**解决方案**:
|
||
|
||
#### 1. 等待历史加载完成
|
||
在 `restoreTaskState()` 中检查历史是否已加载:
|
||
```javascript
|
||
async restoreTaskState() {
|
||
// 检查历史是否已加载
|
||
const hasMessages = Array.isArray(this.messages) && this.messages.length > 0;
|
||
|
||
if (!hasMessages) {
|
||
// 等待历史加载完成后再恢复
|
||
setTimeout(() => {
|
||
this.restoreTaskState();
|
||
}, 500);
|
||
return;
|
||
}
|
||
|
||
// 历史已加载,启动轮询
|
||
taskStore.startPolling(...);
|
||
}
|
||
```
|
||
|
||
#### 2. 计算正确的事件偏移量
|
||
在 `loadRunningTask()` 中获取任务详情,计算已处理的事件数量:
|
||
```javascript
|
||
async loadRunningTask(conversationId) {
|
||
// 查找运行中的任务
|
||
const runningTask = result.data.find(...);
|
||
|
||
if (runningTask) {
|
||
// 获取任务详情,计算已处理的事件数量
|
||
const detailResponse = await fetch(`/api/tasks/${runningTask.task_id}`);
|
||
const detailResult = await detailResponse.json();
|
||
|
||
// 设置为当前事件数量,只获取新事件
|
||
this.lastEventIndex = detailResult.data.next_offset || detailResult.data.events.length;
|
||
|
||
debugLog('[Task] 设置起始偏移量:', this.lastEventIndex);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 工作流程
|
||
|
||
### 页面刷新恢复流程(修复后)
|
||
|
||
1. **页面加载** (`mounted()`)
|
||
- 调用 `loadInitialData()`
|
||
- 加载历史记录(`fetchAndDisplayHistory()`)
|
||
- 1 秒后调用 `restoreTaskState()`
|
||
|
||
2. **任务恢复** (`restoreTaskState()`)
|
||
- 检查是否已在流式输出中 → 跳过
|
||
- 查找运行中的任务
|
||
- **检查历史是否已加载** → 如果未加载,等待 500ms 后重试
|
||
- 历史已加载 → 继续
|
||
|
||
3. **加载任务详情** (`loadRunningTask()`)
|
||
- 查找运行中的任务
|
||
- **获取任务详情**
|
||
- **计算已处理的事件数量**
|
||
- 设置 `lastEventIndex` 为当前事件数量
|
||
|
||
4. **启动轮询** (`startPolling()`)
|
||
- 从 `lastEventIndex` 开始轮询
|
||
- **只获取新事件**
|
||
- 每 150ms 轮询一次
|
||
|
||
5. **处理新事件**
|
||
- 只处理新产生的事件
|
||
- 追加到已有的历史记录后面
|
||
- 不重复显示已有内容
|
||
|
||
## 关键改进
|
||
|
||
### 1. 等待历史加载
|
||
- 在 `restoreTaskState()` 中检查 `messages` 是否已加载
|
||
- 如果未加载,延迟 500ms 后重试
|
||
- 确保历史加载完成后再启动轮询
|
||
|
||
### 2. 正确的事件偏移量
|
||
- 在 `loadRunningTask()` 中获取任务详情
|
||
- 计算已处理的事件数量(`next_offset` 或 `events.length`)
|
||
- 设置 `lastEventIndex` 为当前事件数量
|
||
- 轮询只获取新事件(`from=lastEventIndex`)
|
||
|
||
### 3. 避免重复处理
|
||
- 历史记录由 `fetchAndDisplayHistory()` 加载
|
||
- 轮询只处理新事件
|
||
- 不会重复显示已有内容
|
||
|
||
## 修改的文件
|
||
|
||
### 1. static/src/app/methods/taskPolling.ts
|
||
**等待历史加载**:
|
||
```javascript
|
||
async restoreTaskState() {
|
||
// 检查历史是否已加载
|
||
const hasMessages = Array.isArray(this.messages) && this.messages.length > 0;
|
||
|
||
if (!hasMessages) {
|
||
debugLog('[TaskPolling] 历史未加载,等待历史加载完成');
|
||
setTimeout(() => {
|
||
this.restoreTaskState();
|
||
}, 500);
|
||
return;
|
||
}
|
||
|
||
debugLog('[TaskPolling] 历史已加载,启动轮询');
|
||
// ... 启动轮询
|
||
}
|
||
```
|
||
|
||
### 2. static/src/stores/task.ts
|
||
**计算事件偏移量**:
|
||
```javascript
|
||
async loadRunningTask(conversationId) {
|
||
const runningTask = result.data.find(...);
|
||
|
||
if (runningTask) {
|
||
// 获取任务详情
|
||
const detailResponse = await fetch(`/api/tasks/${runningTask.task_id}`);
|
||
const detailResult = await detailResponse.json();
|
||
|
||
// 设置为当前事件数量
|
||
this.lastEventIndex = detailResult.data.next_offset || detailResult.data.events.length;
|
||
|
||
debugLog('[Task] 设置起始偏移量:', this.lastEventIndex);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 测试场景
|
||
|
||
### 1. 正常发送消息
|
||
- 发送消息
|
||
- 观察输出是否正常
|
||
- 观察是否只显示一次
|
||
|
||
### 2. 刷新页面(任务进行中)
|
||
- 任务执行中刷新页面
|
||
- 观察历史记录是否正确显示
|
||
- 观察是否只显示一次
|
||
- 观察新内容是否正常追加
|
||
- 观察工具块是否保留
|
||
|
||
### 3. 刷新页面(任务完成后)
|
||
- 任务完成后刷新页面
|
||
- 观察历史记录是否完整
|
||
- 观察是否没有重复内容
|
||
|
||
### 4. 多次刷新
|
||
- 任务执行中多次刷新
|
||
- 观察每次刷新是否正常
|
||
- 观察是否有累积的重复内容
|
||
|
||
## 构建
|
||
|
||
```bash
|
||
cd static
|
||
npm run build
|
||
```
|
||
|
||
构建成功!
|
||
- `static/dist/assets/main.js` (690.04 kB)
|
||
- `static/dist/assets/task.js` (4.01 kB)
|
||
|
||
## 最终效果
|
||
|
||
- ✅ 刷新后不重复显示内容
|
||
- ✅ 历史记录正确加载
|
||
- ✅ 新事件正常追加
|
||
- ✅ 工具块正确显示
|
||
- ✅ 流畅的输出效果(150ms 轮询)
|
||
|
||
现在可以测试了!刷新页面应该只显示一次内容,新内容会正常追加。
|