# WebUI `/api/chat/stream` 前端对接说明 本文档用于指导前端项目接入 LaodingBot 的流式聊天接口,并可直接作为提示词输入给 LLM,批量改造其他前端代码。 ## 1. 接口总览 - 方法: `POST` - 路径: `/api/chat/stream` - 请求头: `Content-Type: application/json` - 响应类型: `text/event-stream` - 协议: SSE (Server-Sent Events) 说明: 该接口为单次请求、多次推送。后端会持续推送 `data: \n\n` 格式的事件。 ## 2. 请求体 ```json { "text": "请帮我分析当前目录", "session_id": "sess_abc", "user_id": "user_001" } ``` 字段说明: - `text` (string, required): 用户输入文本,去除空白后不能为空。 - `session_id` (string, optional): 会话 ID,不传时后端自动生成。 - `user_id` (string, optional): 用户 ID,不传时后端自动生成。 兼容字段: - `sessionId` 等价于 `session_id` - `userId` 等价于 `user_id` ## 3. SSE 事件格式 每条 SSE 消息只包含 `data` 字段,内容是 JSON: ```text data: {"type":"thought","content":"我先判断是否需要调用工具","step":1} data: {"type":"tool_call","content":"{\"input\":\"pwd\"}","step":1,"tool_name":"shell"} data: {"type":"tool_result","content":"C:/Project/MyProject","step":1,"tool_name":"shell"} data: {"type":"final","content":"当前目录是 C:/Project/MyProject","step":2} ``` 事件字段: - `type` (string): 事件类型 - `content` (string): 事件文本内容 - `step` (number, optional): ReAct 步骤编号 - `tool_name` (string, optional): 工具名 事件类型: - `thought`: LLM 思考片段(可选透传) - `tool_call`: 工具调用请求(可选透传) - `tool_result`: 工具执行结果(可选透传) - `final`: 最终回答 - `error`: 错误信息 说明: - 默认情况下(`WEBUI_EXPOSE_REASONING=false`),WebUI 只向前端返回 `final` 和 `error`。 - 当设置 `WEBUI_EXPOSE_REASONING=true` 时,WebUI 会额外透传 `thought`、`tool_call`、`tool_result` 事件。 - 无论是否透传推理事件,`final` 都会分段累计推送,以便前端实现打字机效果。 ## 4. 连接生命周期 - 正常结束: 收到 `type=final` 后结束渲染,连接可由浏览器自然关闭。 - 异常结束: 收到 `type=error`,前端应显示错误并结束当前轮次。 - 网络中断: 前端应允许用户重试,并保留已收到的事件记录。 ## 5. 前端渲染建议 推荐前端仅处理两类结果: - 答案区: 显示最后一个 `final` - 错误区: 显示 `error` 如果开启了 `WEBUI_EXPOSE_REASONING=true`,建议额外提供“思考面板”: - 思考区: 渲染 `thought` - 工具区: 渲染 `tool_call` / `tool_result` 建议状态机: - `idle`: 初始状态 - `streaming`: 请求中且持续接收事件 - `done`: 收到 `final` - `failed`: 收到 `error` 或请求异常 ## 6. TypeScript 对接示例 (fetch + ReadableStream) ```ts type StreamEventType = 'thought' | 'tool_call' | 'tool_result' | 'final' | 'error'; interface StreamEvent { type: StreamEventType; content: string; step?: number; tool_name?: string; } export async function streamChat( payload: { text: string; session_id?: string; user_id?: string }, onEvent: (event: StreamEvent) => void, signal?: AbortSignal, ): Promise { const resp = await fetch('/api/chat/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal, }); if (!resp.ok) { throw new Error(`HTTP ${resp.status}`); } if (!resp.body) { throw new Error('ReadableStream is not available'); } const reader = resp.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // SSE message delimiter: blank line let idx = buffer.indexOf('\n\n'); while (idx >= 0) { const chunk = buffer.slice(0, idx).trim(); buffer = buffer.slice(idx + 2); for (const line of chunk.split('\n')) { const text = line.trim(); if (!text.startsWith('data:')) continue; const raw = text.slice(5).trim(); if (!raw) continue; const event = JSON.parse(raw) as StreamEvent; onEvent(event); } idx = buffer.indexOf('\n\n'); } } } ``` ## 7. 给 LLM 的改造任务提示词模板 将下面模板发给 LLM,可用于自动改造其他前端项目: ```text 你要改造一个前端项目的聊天页面,把非流式接口 `/api/chat` 改为流式接口 `/api/chat/stream`。 后端协议约束: 1) 请求方法 POST,Content-Type=application/json 2) 请求体: { text, session_id?, user_id? } 3) 响应是 SSE 文本流,事件格式为 `data: \n\n` 4) JSON 结构: - type: thought | tool_call | tool_result | final | error - content: string - step?: number - tool_name?: string 5) 收到 final 视为本轮完成;收到 error 视为失败 6) 默认不要假设前端一定会收到 thought/tool_call/tool_result;仅当 `WEBUI_EXPOSE_REASONING=true` 才会透传 你的改造要求: 1) 保留现有 UI 风格和组件结构,不做无关重构 2) 新增流式读取逻辑,支持中途取消 (AbortController) 3) 将事件按 step 渲染到消息区 4) 兼容旧会话字段命名 (session_id / sessionId, user_id / userId) 5) 增加错误态与重试按钮 6) 不破坏原有上传、历史消息和输入框行为 7) 输出改动文件列表 + 每个文件的关键变更说明 请直接给出可运行代码补丁。 ``` ## 8. 调试清单 - 检查响应头是否为 `text/event-stream` - 检查每条事件是否以 `data:` 开头并以空行结尾 - 确认 `final` 和 `error` 都能正确结束当前轮次 - 验证弱网下不会丢失已收到的事件 - 验证用户快速连续提问时,旧流可被取消