# 输入队列功能设计方案

## 问题背景

当前流程：AI 调用 `interactive_feedback` → 创建会话 → 等待用户输入（阻塞）→ 用户提交 → 返回。

用户必须在 AI 调用工具后才能输入。AI 正在处理中（尤其是重试/sleep 期间），用户无法提前准备反馈。这导致了大量等待时间的浪费——用户其实已经知道下一步要回复什么，但必须等 AI 调用工具后才能输入。

## 设计目标

用户可以在**任意时刻**预先输入并加入队列，下次 AI 调用 `interactive_feedback` 时如果队列中有数据就立即返回，无需等待。

---

## 架构设计

### 数据流

```
用户输入 → [加入队列] → POST /api/queue-feedback → WebUIManager.feedback_queue
                                                          ↓
AI 调用 interactive_feedback → launch_web_feedback_ui → 检查队列
                                                          ↓
                                              有数据？ → 立即返回（dequeue）
                                              无数据？ → 正常等待用户提交
```

### 队列项数据结构

```python
item = {
    "id": str,                  # UUID，用于删除操作
    "interactive_feedback": str, # 用户反馈文本 + MANDATORY REMINDER
    "images": list[dict],       # 图片数据（bytes 格式，入队时预处理）
    "settings": dict,           # 设置
    "queued_at": float,         # 入队时间戳 (time.time())
    "command_logs": "",         # 空字符串，保持与正常流程一致
    "from_queue": True,         # 标记来自队列
}
```

---

## 后端实现

### `WebUIManager`（`web/main.py`）

新增属性：
```python
self.feedback_queue: list[dict] = []  # 持久化 FIFO 队列（跨会话）
self._queue_lock = threading.Lock()   # 线程安全
```

新增方法：

| 方法 | 签名 | 说明 |
|------|------|------|
| `enqueue_feedback` | `(feedback, images, settings) -> dict` | 入队，附加 MANDATORY REMINDER，预处理图片 base64→bytes |
| `dequeue_feedback` | `() -> dict \| None` | 出队，返回头部项或 None |
| `get_queue_size` | `() -> int` | 返回队列长度 |
| `get_queue_items` | `() -> list[dict]` | 返回队列项摘要（预览截断到120字，剥离 MANDATORY REMINDER） |
| `remove_queue_item` | `(item_id: str) -> bool` | 删除指定 ID 的队列项 |
| `clear_queue` | `() -> int` | 清空队列，返回清除数量 |
| `_process_queue_images` | `(images: list[dict]) -> list[dict]` | 静态方法，base64→bytes 图片预处理 |

### `launch_web_feedback_ui`（`web/main.py`）

在 `create_session` 之**前**检查队列：

```python
# 1. 确保服务器运行（队列 API 需要）
if manager.server_thread is None or not manager.server_thread.is_alive():
    manager.start_server()

# 2. 检查队列（在创建会话之前）
queued = manager.dequeue_feedback()
if queued:
    debug_log(f"从队列中获取预排反馈，跳过等待")
    # 通知前端（如果有活跃连接）
    if manager.current_session and manager.current_session.websocket:
        await manager.current_session.websocket.send_json({
            "type": "queue_item_consumed",
            "remaining": manager.get_queue_size(),
            "consumed_id": queued.get("id", ""),
        })
    return queued  # 直接返回，不创建新会话

# 3. 没有队列数据，正常创建会话并等待
manager.create_session(project_directory, summary)
```

### 新增 API 路由（`web/routes/main_routes.py`）

| 端点 | 方法 | 说明 | 限制 |
|------|------|------|------|
| `/api/queue-feedback` | POST | 入队 `{feedback, images, settings}` | 上限 20 条，超出返回 429 |
| `/api/queue-status` | GET | 返回 `{size, items: [{id, feedback_preview, has_images, queued_at}]}` | - |
| `/api/queue-item/{item_id}` | DELETE | 删除指定 ID 的队列项 | - |
| `/api/queue` | DELETE | 清空队列 | - |

所有变更操作通过 WebSocket 广播 `queue_updated` 消息。

### WebSocket 通知

| 消息类型 | 触发时机 | 数据 |
|----------|----------|------|
| `queue_updated` | 入队/删除/清空时 | `{type, action, size}` |
| `queue_item_consumed` | 队列项被 AI 调用消费时 | `{type, remaining, consumed_id}` |

---

## 前端实现

### 反馈页面（`feedback.html` + `app.js`）

**按钮布局**：

```
[📋 数字徽标] [✅ 提交回饋 / 📥 提交到队列]
```

- 📋 切换按钮 (`queueToggleBtn`)：点击展开/收起队列面板，带红色数字徽标。展开时边框高亮为蓝色。
- 提交按钮 (`submitBtn`)：**智能按钮**，根据 WebSocket 连接状态自动切换行为和外观：
  - AI 在等待（WebSocket 就绪）：显示 `✅ 提交回饋`（绿色），正常 WebSocket 提交
  - AI 未等待（WebSocket 断开/未就绪）：显示 `📥 提交到队列`（蓝色边框），自动入队
  - 提交反馈后：立即切换为队列模式（AI 开始处理，不再等待）

**队列面板** (`queuePanel`)：
- 显示所有排队项：编号 + 文本摘要（截断） + 入队时间
- 每项有删除按钮（×）
- 底部有「清空」按钮
- 空队列时显示提示文字

**交互逻辑**：
- 点击提交按钮 → 自动判断路由（WebSocket 提交 或 HTTP POST 入队）
- WebSocket `queue_updated` → 更新徽标和面板
- WebSocket `queue_item_consumed` → 更新徽标 + toast 提示 "队列中的反馈已发送给 AI"
- 页面加载时自动 `refreshQueueStatus()` 获取初始徽标状态

### 等待页面（`index.html`）

在 "等待 MCP 服務調用..." 下方提供简化版队列入口：

```
[文本输入框                      ] [📥 加入队列]
队列: N 条                         查看队列/收起队列
┌─────────────────────────────────┐
│ 可展开的队列面板                  │
│ #1 xxxx                    时间 ×│
│ #2 yyyy                    时间 ×│
│                         [清空]   │
└─────────────────────────────────┘
```

- 仅支持纯文本入队（无图片）
- 独立的内联 JS，不依赖 `app.js` 的 FeedbackApp
- 同样调用 `/api/queue-feedback` 和 `/api/queue-status` API

---

## 关键设计决策

1. **队列在 WebUIManager 级别**（不在 Session 级别）
   - Session 在每次 AI 调用时重建，队列需要跨会话持久

2. **FIFO 先进先出**
   - 最早排队的反馈最先被消费

3. **MANDATORY REMINDER 在入队时附加**
   - 保持与正常提交流程一致
   - 展示摘要时剥离（按 `\n\n---\n# [MANDATORY REMINDER]` 分割）

4. **图片在入队时预处理**
   - base64 → bytes，与正常流程中 `session._process_images()` 行为一致
   - 确保出队数据格式与 `wait_for_feedback()` 返回格式相同
   - `server.py` 的 `process_images` 函数支持 bytes 和 base64 两种格式

5. **队列消费在 `create_session` 之前**
   - 避免创建不必要的"幽灵会话"
   - 避免 WebSocket 连接转移和状态流转异常
   - 不触发浏览器打开/刷新

6. **线程安全**
   - `threading.Lock` 保护所有队列读写操作
   - 队列可能被 API 路由线程（FastAPI）和 AI 调用线程同时访问

7. **移动端友好**
   - 所有交互都是单击/触摸操作
   - 独立的「📋」切换按钮替代右键菜单
   - 等待页面的输入框支持触摸 resize

---

## 修改文件清单

| 文件 | 修改内容 |
|------|----------|
| `src/mcp_keep_feedback/web/main.py` | WebUIManager 新增队列属性（6 个方法）；`launch_web_feedback_ui` 在 create_session 前检查队列 |
| `src/mcp_keep_feedback/web/routes/main_routes.py` | 新增 4 个 API 端点 + WebSocket 广播 |
| `src/mcp_keep_feedback/web/static/js/app.js` | 新增队列切换按钮、队列面板、API 调用、WebSocket 消息处理 |
| `src/mcp_keep_feedback/web/templates/feedback.html` | 新增队列切换按钮、入队按钮、队列面板 HTML |
| `src/mcp_keep_feedback/web/templates/index.html` | 新增等待页面队列入口（输入框 + 面板 + 内联 JS） |

---

## 边界条件与异常处理

1. **队列满**：上限 20 条，API 返回 HTTP 429
2. **图片内存**：入队时预处理为 bytes，大图片会占用内存。用户应控制图片数量
3. **服务重启**：队列仅在内存中，服务重启后清空（可接受）
4. **并发安全**：`threading.Lock` 保护所有队列操作
5. **队列项不过期**：用户手动管理
6. **API 可用性**：队列 API 只有在 Web 服务器运行后才可用。`launch_web_feedback_ui` 在检查队列前确保服务器已启动
7. **无活跃会话时消费通知失败**：静默处理，不影响队列消费和数据返回

---

## 设计审查 (Review)

### 已修复问题

| # | 问题 | 严重性 | 解决方案 | 状态 |
|---|------|--------|----------|------|
| 1 | 队列消费时会创建无用会话 | 高 | 队列检查移到 `create_session` 之前 | ✅ 已修复 |
| 2 | 图片数据格式不一致 | 中 | 入队时 base64→bytes 预处理 | ✅ 已修复 |
| 3 | MANDATORY REMINDER 缺失 | 低 | 在 `enqueue_feedback` 中附加 | ✅ 已修复 |
| 4 | 右键操作对移动端不友好 | 中 | 改用独立的 📋 切换按钮 | ✅ 已修复 |
| 5 | 等待页面无法入队 | 低 | index.html 新增简化版队列入口 | ✅ 已修复 |

### 剩余已知限制

1. **等待页面仅支持纯文本**：index.html 的队列入口不支持图片上传（需要完整的 ImageHandler 组件）
2. **队列不持久化到磁盘**：服务重启后清空。如需持久化，可以考虑 SQLite 或 JSON 文件
3. **`list.pop(0)` 为 O(n)**：上限 20 条，性能不是问题。如需优化可用 `collections.deque`

### 结论

设计合理，实现完整。所有已识别的问题均已修复。
