742 lines
22 KiB
Markdown
742 lines
22 KiB
Markdown
# AI Agent 系统停止按钮和运行状态机制分析
|
||
|
||
## 概述
|
||
|
||
本文档详细分析 AI Agent 系统中停止按钮和运行状态机制的实现,这是系统中 bug 最多的部分。系统采用 REST API + 轮询模式(`usePollingMode: true`),已禁用 WebSocket 实时推送。
|
||
|
||
## 1. 停止按钮的显示/隐藏逻辑
|
||
|
||
### 1.1 组件定义
|
||
|
||
**文件位置**: `/static/src/components/input/InputComposer.vue:48-61`
|
||
|
||
```vue
|
||
<button
|
||
type="button"
|
||
class="stadium-btn send-btn"
|
||
@click="$emit('send-or-stop')"
|
||
:disabled="
|
||
!isConnected ||
|
||
(inputLocked && !streamingMessage) ||
|
||
(mediaUploading && !streamingMessage) ||
|
||
((!(inputMessage || '').trim() && (!selectedImages?.length && !selectedVideos?.length)) && !streamingMessage)
|
||
"
|
||
>
|
||
<span v-if="streamingMessage" class="stop-icon"></span>
|
||
<span v-else class="send-icon"></span>
|
||
</button>
|
||
```
|
||
|
||
### 1.2 显示条件
|
||
|
||
停止按钮的显示由 `streamingMessage` 状态控制:
|
||
- **显示停止按钮**: `streamingMessage === true`
|
||
- **显示发送按钮**: `streamingMessage === false`
|
||
|
||
### 1.3 禁用条件
|
||
|
||
按钮被禁用的情况:
|
||
1. 未连接服务器 (`!isConnected`)
|
||
2. 输入被锁定且不在流式输出中 (`inputLocked && !streamingMessage`)
|
||
3. 媒体上传中且不在流式输出中 (`mediaUploading && !streamingMessage`)
|
||
4. 没有输入内容且不在流式输出中
|
||
|
||
### 1.4 关键状态变量
|
||
|
||
**文件位置**: `/static/src/app/state.ts:23`
|
||
|
||
```typescript
|
||
taskInProgress: false, // 任务是否在进行中(用于保持输入区的"停止"状态)
|
||
```
|
||
|
||
**文件位置**: `/static/src/stores/chat.ts:78`
|
||
|
||
```typescript
|
||
streamingMessage: false, // 是否正在流式输出消息
|
||
```
|
||
|
||
**文件位置**: `/static/src/app/computed.ts:160-166`
|
||
|
||
```typescript
|
||
streamingUi() {
|
||
return this.streamingMessage || this.hasPendingToolActions();
|
||
},
|
||
composerBusy() {
|
||
const monitorLock = this.monitorIsLocked && this.chatDisplayMode === 'monitor';
|
||
return this.streamingUi || this.taskInProgress || monitorLock || this.stopRequested;
|
||
}
|
||
```
|
||
|
||
## 2. 点击停止按钮的完整流程
|
||
|
||
### 2.1 前端事件处理
|
||
|
||
**文件位置**: `/static/src/app/methods/message.ts:104-110`
|
||
|
||
```typescript
|
||
handleSendOrStop() {
|
||
if (this.composerBusy) {
|
||
this.stopTask();
|
||
} else {
|
||
this.sendMessage();
|
||
}
|
||
},
|
||
```
|
||
|
||
### 2.2 停止任务方法
|
||
|
||
**文件位置**: `/static/src/app/methods/message.ts:254-282`
|
||
|
||
```typescript
|
||
async stopTask() {
|
||
const canStop = this.composerBusy && !this.stopRequested;
|
||
if (!canStop) {
|
||
return;
|
||
}
|
||
|
||
const shouldDropToolEvents = this.streamingUi;
|
||
this.stopRequested = true;
|
||
this.dropToolEvents = shouldDropToolEvents;
|
||
|
||
// 使用 REST API 取消任务
|
||
try {
|
||
const { useTaskStore } = await import('../../stores/task');
|
||
const taskStore = useTaskStore();
|
||
|
||
if (taskStore.currentTaskId) {
|
||
await taskStore.cancelTask();
|
||
debugLog('[Message] 任务已取消');
|
||
}
|
||
} catch (error) {
|
||
console.error('[Message] 取消任务失败:', error);
|
||
}
|
||
|
||
// 立即清理前端状态,避免出现"不可输入也不可停止"的卡死状态
|
||
this.clearPendingTools('user_stop');
|
||
this.streamingMessage = false;
|
||
this.taskInProgress = false;
|
||
this.forceUnlockMonitor('user_stop');
|
||
},
|
||
```
|
||
|
||
### 2.3 REST API 取消请求
|
||
|
||
**文件位置**: `/static/src/stores/task.ts:185-214`
|
||
|
||
```typescript
|
||
async cancelTask() {
|
||
if (!this.currentTaskId) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
debugLog('[Task] 取消任务:', this.currentTaskId);
|
||
|
||
const response = await fetch(`/api/tasks/${this.currentTaskId}/cancel`, {
|
||
method: 'POST',
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('取消任务失败');
|
||
}
|
||
|
||
const result = await response.json();
|
||
if (!result.success) {
|
||
throw new Error(result.error || '取消任务失败');
|
||
}
|
||
|
||
this.taskStatus = 'canceled';
|
||
this.stopPolling();
|
||
|
||
debugLog('[Task] 任务已取消');
|
||
} catch (error) {
|
||
console.error('[Task] 取消任务失败:', error);
|
||
throw error;
|
||
}
|
||
},
|
||
```
|
||
|
||
### 2.4 后端接收停止请求
|
||
|
||
**文件位置**: `/server/tasks.py:372-379`
|
||
|
||
```python
|
||
@tasks_bp.route("/api/tasks/<task_id>/cancel", methods=["POST"])
|
||
@api_login_required
|
||
def cancel_task_api(task_id: str):
|
||
username = get_current_username()
|
||
ok = task_manager.cancel_task(username, task_id)
|
||
if not ok:
|
||
return jsonify({"success": False, "error": "任务不存在"}), 404
|
||
return jsonify({"success": True})
|
||
```
|
||
|
||
### 2.5 设置停止标志
|
||
|
||
**文件位置**: `/server/tasks.py:144-164`
|
||
|
||
```python
|
||
def cancel_task(self, username: str, task_id: str) -> bool:
|
||
rec = self.get_task(username, task_id)
|
||
if not rec:
|
||
return False
|
||
rec.stop_requested = True
|
||
# 标记停止标志;chat_flow 会检测 stop_flags
|
||
entry = stop_flags.get(task_id)
|
||
if not isinstance(entry, dict):
|
||
entry = {'stop': False, 'task': None, 'terminal': None}
|
||
stop_flags[task_id] = entry
|
||
entry['stop'] = True
|
||
# 注释掉直接取消任务,改为通过停止标志让任务内部处理
|
||
# try:
|
||
# if entry.get('task') and hasattr(entry['task'], "cancel"):
|
||
# entry['task'].cancel()
|
||
# except Exception:
|
||
# pass
|
||
with self._lock:
|
||
rec.status = "cancel_requested"
|
||
rec.updated_at = time.time()
|
||
return True
|
||
```
|
||
|
||
### 2.6 聊天流程检测停止标志
|
||
|
||
系统在多个关键位置检查停止标志:
|
||
|
||
**位置1**: `/server/chat_flow_tool_loop.py:23-30` - 工具调用前检查
|
||
|
||
```python
|
||
# 检查停止标志
|
||
client_stop_info = get_stop_flag(client_sid, username)
|
||
if client_stop_info:
|
||
stop_requested = client_stop_info.get('stop', False) if isinstance(client_stop_info, dict) else client_stop_info
|
||
if stop_requested:
|
||
debug_log("在工具调用过程中检测到停止状态")
|
||
# ... 取消工具调用
|
||
```
|
||
|
||
**位置2**: `/server/chat_flow_tool_loop.py:229-236` - 工具执行过程中检查
|
||
|
||
```python
|
||
check_count += 1
|
||
client_stop_info = get_stop_flag(client_sid, username)
|
||
if client_stop_info:
|
||
stop_requested = client_stop_info.get('stop', False) if isinstance(client_stop_info, dict) else client_stop_info
|
||
if stop_requested:
|
||
debug_log(f"[停止检测] 工具执行过程中检测到停止请求(检查次数:{check_count}),立即取消工具")
|
||
tool_task.cancel()
|
||
tool_cancelled = True
|
||
```
|
||
|
||
**位置3**: `/server/chat_flow_stream_loop.py:46-53` - 流式输出过程中检查
|
||
|
||
```python
|
||
# 检查停止标志
|
||
client_stop_info = get_stop_flag(client_sid, username)
|
||
if client_stop_info:
|
||
stop_requested = client_stop_info.get('stop', False) if isinstance(client_stop_info, dict) else client_stop_info
|
||
if stop_requested:
|
||
debug_log(f"检测到停止请求,中断流处理")
|
||
cancel_pending_tools(tool_calls_list=tool_calls, sender=sender, messages=messages)
|
||
sender('task_stopped', {...})
|
||
```
|
||
|
||
**位置4**: `/server/chat_flow_task_support.py:147-154` - 命令执行过程中检查
|
||
|
||
```python
|
||
while time.time() < deadline:
|
||
client_stop_info = get_stop_flag(client_sid, username)
|
||
if client_stop_info:
|
||
stop_requested = client_stop_info.get('stop', False) if isinstance(client_stop_info, dict) else client_stop_info
|
||
if stop_requested:
|
||
sender('task_stopped', {
|
||
'message': '命令执行被用户取消',
|
||
'reason': 'user_stop'
|
||
})
|
||
```
|
||
|
||
## 3. 运行状态的设置和清除
|
||
|
||
### 3.1 taskInProgress 状态管理
|
||
|
||
**设置为 true 的时机**:
|
||
|
||
1. **发送消息时** (`/static/src/app/methods/message.ts:200`)
|
||
```typescript
|
||
this.taskInProgress = true;
|
||
```
|
||
|
||
2. **AI消息开始时** (`/static/src/app/methods/taskPolling.ts:128,138`)
|
||
```typescript
|
||
this.taskInProgress = true;
|
||
```
|
||
|
||
3. **收到用户消息事件时** (`/static/src/app/methods/taskPolling.ts:566`)
|
||
```typescript
|
||
this.taskInProgress = true;
|
||
```
|
||
|
||
4. **恢复任务状态时** (`/static/src/app/methods/taskPolling.ts:739,882`)
|
||
```typescript
|
||
this.taskInProgress = true;
|
||
```
|
||
|
||
**设置为 false 的时机**:
|
||
|
||
1. **任务完成时** (`/static/src/app/methods/taskPolling.ts:538`)
|
||
```typescript
|
||
this.taskInProgress = false;
|
||
```
|
||
|
||
2. **任务错误时** (`/static/src/app/methods/taskPolling.ts:588`)
|
||
```typescript
|
||
this.taskInProgress = false;
|
||
```
|
||
|
||
3. **停止任务时** (`/static/src/app/methods/message.ts:280`)
|
||
```typescript
|
||
this.taskInProgress = false;
|
||
```
|
||
|
||
4. **创建任务失败时** (`/static/src/app/methods/message.ts:218`)
|
||
```typescript
|
||
this.taskInProgress = false;
|
||
```
|
||
|
||
### 3.2 streamingMessage 状态管理
|
||
|
||
**设置为 true 的时机**:
|
||
|
||
1. **AI消息开始时** (`/static/src/app/methods/taskPolling.ts:130,141`)
|
||
```typescript
|
||
this.streamingMessage = true;
|
||
```
|
||
|
||
2. **恢复任务状态时** (`/static/src/app/methods/taskPolling.ts:738,881`)
|
||
```typescript
|
||
this.streamingMessage = true;
|
||
```
|
||
|
||
**设置为 false 的时机**:
|
||
|
||
1. **任务完成时** (`/static/src/app/methods/taskPolling.ts:532`)
|
||
```typescript
|
||
this.streamingMessage = false;
|
||
```
|
||
|
||
2. **任务错误时** (`/static/src/app/methods/taskPolling.ts:587`)
|
||
```typescript
|
||
this.streamingMessage = false;
|
||
```
|
||
|
||
3. **停止任务时** (`/static/src/app/methods/message.ts:279`)
|
||
```typescript
|
||
this.streamingMessage = false;
|
||
```
|
||
|
||
4. **收到用户消息事件时** (`/static/src/app/methods/taskPolling.ts:567`)
|
||
```typescript
|
||
this.streamingMessage = false;
|
||
```
|
||
|
||
### 3.3 stopRequested 状态管理
|
||
|
||
**设置为 true 的时机**:
|
||
|
||
1. **停止任务时** (`/static/src/app/methods/message.ts:261`)
|
||
```typescript
|
||
this.stopRequested = true;
|
||
```
|
||
|
||
**设置为 false 的时机**:
|
||
|
||
1. **AI消息开始时** (`/static/src/app/methods/taskPolling.ts:129,140`)
|
||
```typescript
|
||
this.stopRequested = false;
|
||
```
|
||
|
||
2. **任务完成时** (`/static/src/app/methods/taskPolling.ts:533`)
|
||
```typescript
|
||
this.stopRequested = false;
|
||
```
|
||
|
||
3. **任务错误时** (`/static/src/app/methods/taskPolling.ts:589`)
|
||
```typescript
|
||
this.stopRequested = false;
|
||
```
|
||
|
||
## 4. 状态转换图
|
||
|
||
```
|
||
[用户点击发送]
|
||
↓
|
||
[taskInProgress = true]
|
||
[streamingMessage = false]
|
||
↓
|
||
[创建任务 POST /api/tasks]
|
||
↓
|
||
[开始轮询 GET /api/tasks/{id}?from={offset}]
|
||
↓
|
||
[收到 ai_message_start 事件]
|
||
↓
|
||
[streamingMessage = true]
|
||
↓
|
||
[显示停止按钮]
|
||
↓
|
||
┌─────────────────────────────────┐
|
||
│ 用户点击停止按钮 │
|
||
│ ↓ │
|
||
│ [stopRequested = true] │
|
||
│ ↓ │
|
||
│ [POST /api/tasks/{id}/cancel] │
|
||
│ ↓ │
|
||
│ [stop_flags[task_id]['stop'] = True] │
|
||
│ ↓ │
|
||
│ [立即清理前端状态] │
|
||
│ [streamingMessage = false] │
|
||
│ [taskInProgress = false] │
|
||
│ [stopRequested = false] │
|
||
│ ↓ │
|
||
│ [显示发送按钮] │
|
||
└─────────────────────────────────┘
|
||
或
|
||
↓
|
||
[收到 task_complete 事件]
|
||
↓
|
||
[streamingMessage = false]
|
||
[taskInProgress = false]
|
||
[stopRequested = false]
|
||
↓
|
||
[停止轮询]
|
||
↓
|
||
[显示发送按钮]
|
||
```
|
||
|
||
## 5. 事件流程图
|
||
|
||
### 5.1 正常完成流程
|
||
|
||
```
|
||
前端 后端 任务线程
|
||
│ │ │
|
||
├─ sendMessage() │ │
|
||
├─ taskInProgress = true │ │
|
||
├─ POST /api/tasks ─────────>│ │
|
||
│ ├─ create_chat_task() │
|
||
│ ├─ thread.start() ──────────>│
|
||
│ │ ├─ run_chat_task_sync()
|
||
│<──── task_id ──────────────┤ │
|
||
├─ startPolling() │ │
|
||
│ │ │
|
||
├─ GET /api/tasks/{id} ─────>│ │
|
||
│<──── events ────────────────┤ │
|
||
├─ handleTaskEvent() │ │
|
||
├─ streamingMessage = true │ │
|
||
│ │ │
|
||
│ (轮询继续...) │ │
|
||
│ │ │
|
||
│ │ ├─ 任务完成
|
||
│ │<──── task_complete ────────┤
|
||
├─ GET /api/tasks/{id} ─────>│ │
|
||
│<──── task_complete ─────────┤ │
|
||
├─ handleTaskComplete() │ │
|
||
├─ streamingMessage = false │ │
|
||
├─ taskInProgress = false │ │
|
||
├─ stopPolling() │ │
|
||
```
|
||
|
||
### 5.2 用户停止流程
|
||
|
||
```
|
||
前端 后端 任务线程
|
||
│ │ │
|
||
├─ (任务运行中) │ │
|
||
├─ streamingMessage = true │ │
|
||
│ │ │
|
||
├─ stopTask() │ │
|
||
├─ stopRequested = true │ │
|
||
├─ POST /api/tasks/{id}/cancel ─>│ │
|
||
│ ├─ cancel_task() │
|
||
│ ├─ stop_flags[task_id]['stop'] = True
|
||
│<──── success ──────────────┤ │
|
||
├─ streamingMessage = false │ │
|
||
├─ taskInProgress = false │ │
|
||
├─ stopRequested = false │ │
|
||
│ │ │
|
||
│ │ ├─ get_stop_flag()
|
||
│ │ ├─ 检测到停止标志
|
||
│ │ ├─ 取消工具调用
|
||
│ │ ├─ sender('task_stopped')
|
||
│ │<──── task_stopped ─────────┤
|
||
│ ├─ status = 'canceled' │
|
||
│ ├─ stop_flags.pop(task_id) │
|
||
```
|
||
|
||
## 6. 停止后的UI状态变化
|
||
|
||
### 6.1 输入框状态
|
||
|
||
**文件位置**: `/static/src/components/input/InputComposer.vue:41`
|
||
|
||
```vue
|
||
:disabled="!isConnected || streamingMessage || inputLocked"
|
||
```
|
||
|
||
- **停止前**: `streamingMessage = true` → 输入框禁用
|
||
- **停止后**: `streamingMessage = false` → 输入框启用
|
||
|
||
### 6.2 停止按钮变为发送按钮
|
||
|
||
**文件位置**: `/static/src/components/input/InputComposer.vue:59-60`
|
||
|
||
```vue
|
||
<span v-if="streamingMessage" class="stop-icon"></span>
|
||
<span v-else class="send-icon"></span>
|
||
```
|
||
|
||
- **停止前**: 显示停止图标
|
||
- **停止后**: 显示发送图标
|
||
|
||
### 6.3 工具块状态
|
||
|
||
停止时会调用 `clearPendingTools('user_stop')`,清理所有待处理的工具调用。
|
||
|
||
## 7. 已知问题和Bug
|
||
|
||
### 7.1 停止按钮无法点击
|
||
|
||
**问题描述**: 在某些情况下,停止按钮显示但无法点击。
|
||
|
||
**可能原因**:
|
||
1. `composerBusy` 计算错误,导致 `canStop` 条件不满足
|
||
2. `stopRequested` 已经为 true,阻止重复停止
|
||
3. 按钮被其他元素遮挡(CSS z-index 问题)
|
||
|
||
**相关代码**: `/static/src/app/methods/message.ts:255-258`
|
||
|
||
```typescript
|
||
const canStop = this.composerBusy && !this.stopRequested;
|
||
if (!canStop) {
|
||
return;
|
||
}
|
||
```
|
||
|
||
### 7.2 停止后任务仍在运行
|
||
|
||
**问题描述**: 点击停止按钮后,前端状态已清除,但后端任务仍在执行。
|
||
|
||
**可能原因**:
|
||
1. 后端停止标志检查不够频繁
|
||
2. 某些长时间运行的工具没有检查停止标志
|
||
3. 停止标志设置和检查之间存在竞态条件
|
||
|
||
**相关代码**:
|
||
- `/server/chat_flow_tool_loop.py:229-236` - 工具执行过程中每 0.1 秒检查一次
|
||
- `/server/chat_flow_task_support.py:147-154` - 命令执行过程中检查
|
||
|
||
### 7.3 状态不同步
|
||
|
||
**问题描述**: 前端和后端的任务状态不一致。
|
||
|
||
**可能原因**:
|
||
1. 轮询间隔过长(150ms),导致状态更新延迟
|
||
2. 事件处理顺序错误
|
||
3. 多个状态变量(`taskInProgress`, `streamingMessage`, `stopRequested`)更新不原子
|
||
|
||
**相关代码**: `/static/src/stores/task.ts:154-157`
|
||
|
||
```typescript
|
||
// 设置定时轮询(150ms 间隔,接近流式输出效果)
|
||
this.pollingInterval = window.setInterval(() => {
|
||
this.pollTaskEvents(handler);
|
||
}, 150);
|
||
```
|
||
|
||
### 7.4 页面刷新后状态丢失
|
||
|
||
**问题描述**: 页面刷新后,运行中的任务状态无法正确恢复。
|
||
|
||
**解决方案**: 已实现 `restoreTaskState()` 方法,在页面加载时查找运行中的任务并恢复状态。
|
||
|
||
**相关代码**: `/static/src/app/methods/taskPolling.ts:637-921`
|
||
|
||
```typescript
|
||
async restoreTaskState() {
|
||
// 查找运行中的任务
|
||
const runningTask = await taskStore.loadRunningTask(this.currentConversationId);
|
||
|
||
if (!runningTask) {
|
||
return;
|
||
}
|
||
|
||
// 恢复状态
|
||
this.streamingMessage = true;
|
||
this.taskInProgress = true;
|
||
|
||
// 启动轮询
|
||
taskStore.startPolling((event: any) => {
|
||
this.handleTaskEvent(event);
|
||
});
|
||
}
|
||
```
|
||
|
||
### 7.5 立即清理导致的问题
|
||
|
||
**问题描述**: 停止任务时立即清理前端状态,可能导致后端仍在发送事件,前端无法正确处理。
|
||
|
||
**相关代码**: `/static/src/app/methods/message.ts:278-281`
|
||
|
||
```typescript
|
||
// 立即清理前端状态,避免出现"不可输入也不可停止"的卡死状态
|
||
this.clearPendingTools('user_stop');
|
||
this.streamingMessage = false;
|
||
this.taskInProgress = false;
|
||
this.forceUnlockMonitor('user_stop');
|
||
```
|
||
|
||
**潜在风险**:
|
||
- 后端可能仍在发送 `tool_start`, `text_chunk` 等事件
|
||
- 前端已清理状态,这些事件会被忽略或导致错误
|
||
- 可能出现 UI 不一致的情况
|
||
|
||
### 7.6 dropToolEvents 机制
|
||
|
||
**问题描述**: `dropToolEvents` 标志用于在停止后忽略工具事件,但可能导致状态不一致。
|
||
|
||
**相关代码**:
|
||
- `/static/src/app/methods/message.ts:262` - 设置标志
|
||
- `/static/src/app/methods/taskPolling.ts:294-296` - 检查标志
|
||
|
||
```typescript
|
||
if (this.dropToolEvents) {
|
||
return;
|
||
}
|
||
```
|
||
|
||
**潜在问题**:
|
||
- 如果后端发送了 `task_complete` 事件,但前端已经 `dropToolEvents`,可能导致状态不清理
|
||
- `dropToolEvents` 何时清除不明确
|
||
|
||
### 7.7 composerBusy 计算复杂
|
||
|
||
**问题描述**: `composerBusy` 依赖多个状态变量,计算逻辑复杂,容易出错。
|
||
|
||
**相关代码**: `/static/src/app/computed.ts:163-166`
|
||
|
||
```typescript
|
||
composerBusy() {
|
||
const monitorLock = this.monitorIsLocked && this.chatDisplayMode === 'monitor';
|
||
return this.streamingUi || this.taskInProgress || monitorLock || this.stopRequested;
|
||
}
|
||
```
|
||
|
||
**问题**:
|
||
- `streamingUi` 又依赖 `streamingMessage` 和 `hasPendingToolActions()`
|
||
- 多层依赖导致状态更新顺序敏感
|
||
- 难以调试和维护
|
||
|
||
## 8. 改进建议
|
||
|
||
### 8.1 统一状态管理
|
||
|
||
建议使用单一的状态机模式,而不是多个独立的布尔变量:
|
||
|
||
```typescript
|
||
enum TaskState {
|
||
IDLE = 'idle',
|
||
CREATING = 'creating',
|
||
RUNNING = 'running',
|
||
STREAMING = 'streaming',
|
||
STOPPING = 'stopping',
|
||
STOPPED = 'stopped',
|
||
COMPLETED = 'completed',
|
||
FAILED = 'failed'
|
||
}
|
||
|
||
state: {
|
||
taskState: TaskState.IDLE
|
||
}
|
||
```
|
||
|
||
### 8.2 增加停止确认机制
|
||
|
||
在前端立即清理状态后,等待后端确认停止成功:
|
||
|
||
```typescript
|
||
async stopTask() {
|
||
this.stopRequested = true;
|
||
|
||
// 发送停止请求
|
||
await taskStore.cancelTask();
|
||
|
||
// 等待后端确认(轮询直到收到 task_stopped 或 task_complete)
|
||
await this.waitForStopConfirmation();
|
||
|
||
// 清理前端状态
|
||
this.streamingMessage = false;
|
||
this.taskInProgress = false;
|
||
this.stopRequested = false;
|
||
}
|
||
```
|
||
|
||
### 8.3 增加停止超时机制
|
||
|
||
如果后端在一定时间内没有响应停止请求,强制清理前端状态:
|
||
|
||
```typescript
|
||
const STOP_TIMEOUT = 5000; // 5秒
|
||
|
||
setTimeout(() => {
|
||
if (this.stopRequested) {
|
||
// 强制清理
|
||
this.streamingMessage = false;
|
||
this.taskInProgress = false;
|
||
this.stopRequested = false;
|
||
}
|
||
}, STOP_TIMEOUT);
|
||
```
|
||
|
||
### 8.4 改进停止标志检查频率
|
||
|
||
在长时间运行的操作中,增加停止标志检查频率:
|
||
|
||
```python
|
||
# 当前: 每 0.1 秒检查一次
|
||
await asyncio.sleep(0.1)
|
||
|
||
# 建议: 每 0.05 秒检查一次
|
||
await asyncio.sleep(0.05)
|
||
```
|
||
|
||
### 8.5 添加停止日志
|
||
|
||
在所有停止相关的代码路径中添加详细日志,便于调试:
|
||
|
||
```typescript
|
||
debugLog('[Stop] 用户点击停止按钮', {
|
||
composerBusy: this.composerBusy,
|
||
stopRequested: this.stopRequested,
|
||
taskInProgress: this.taskInProgress,
|
||
streamingMessage: this.streamingMessage,
|
||
currentTaskId: taskStore.currentTaskId
|
||
});
|
||
```
|
||
|
||
### 8.6 前端状态恢复优化
|
||
|
||
页面刷新后的状态恢复逻辑复杂,建议:
|
||
1. 简化恢复逻辑,只恢复必要的状态
|
||
2. 添加恢复失败的降级处理
|
||
3. 提供手动刷新按钮,让用户可以重新加载状态
|
||
|
||
## 9. 总结
|
||
|
||
停止按钮和运行状态机制是系统中最复杂的部分之一,涉及前端多个状态变量、REST API 轮询、后端停止标志检查等多个环节。主要问题包括:
|
||
|
||
1. **状态管理复杂**: 多个独立的布尔变量(`taskInProgress`, `streamingMessage`, `stopRequested`)难以维护
|
||
2. **停止响应延迟**: 后端停止标志检查不够频繁,导致停止响应慢
|
||
3. **状态同步问题**: 前端立即清理状态,但后端可能仍在运行,导致状态不一致
|
||
4. **页面刷新恢复**: 状态恢复逻辑复杂,容易出错
|
||
|
||
建议采用状态机模式统一管理任务状态,增加停止确认机制,改进停止标志检查频率,并添加详细的调试日志。
|