feat: AIOps智能助手流式输出(SSE代理,逐字渲染+思考中光标+断连安全持久化)

This commit is contained in:
attacker committed 2026-06-18 05:07:28 +08:00
1 parent 139bddc9d9
commit 5d19878b28
5 files changed
+362 -31

No files matched your search

+50
View File
@@ -1704,3 +1704,53 @@ cd ~/devops/aiops
- 快捷操作可扩展更多数据源: 容量预测(接入 agent history 时序数据)、K8s 集群健康(k8sApi)、云账单分析(billingApi)
- 用户消息气泡中【平台实时数据】块较长,可考虑折叠展示(默认收起,点击展开)提升可读性
- 前端组件拆分候选仍未变: TopologyView.tsx(576)、ExcelServerImport.tsx(549)、DomainManager.tsx(492)
## 2026-06-18 05:30 自动推进(cron)— AIOps 智能助手流式输出(SSE)
- **任务**: 为 AIOps 智能助手实现流式输出(SSE),长报告/长分析无需等待完整响应即可逐字显示,显著提升交互体验
- **背景**: 上次 cron 完成快捷操作接入真实平台数据,dev-log 下一步建议第 1 项明确列出"Chat 流式输出(SSE/streaming)—— 长报告/长分析当前需等完整响应才显示,流式可显著提升体验"。此前 Chat handler 一次性 io.ReadAll 后 JSON 返回,长回复用户需盯 spinner 干等。本次实现完整 SSE 流式代理链路
- **实现(后端 2 步)**:
- backend/internal/api/handlers/llm.go — 新增 ChatStream handler + writeSSEChunk + writeSSEError 辅助函数(~190 行新增):
- 设置 SSE 响应头(Content-Type: text/event-stream + X-Accel-Buffering: no 关闭 Nginx 缓冲)+ 初始 flush
- 向 OpenAI 兼容端点发送 stream:true 请求;用无超时 http.Client(Timeout:0)让 ctx 控制生命周期
- bufio.Reader 逐行解析上游 SSE,解析 `{"choices":[{"delta":{"content":"..."}}]}` 提取增量内容
- 每个 delta 用 `data: {"content":"..."}` 格式转发给前端(JSON 序列化避免换行符破坏 SSE 协议)
- 收到 `[DONE]` 或 EOF 时跳出循环;累计完整内容成对写入 chat_histories(user + assistant),发送 `event: done` 通知前端结束
- 客户端断开检测:每轮迭代检查 `c.Request.Context().Done()`,断开时不再持久化(避免污染历史)
- 错误处理:连接前/连接中错误统一用 writeSSEError 发送 `event: error` + JSON `{code,message}`,避免 HTTP 错误码破坏已开始的 SSE 流
- backend/internal/api/routes/routes.go — 注册 POST /aiops/chat/stream(保留原 /chat 用于非流式回退)
- **实现(前端 2 步)**:
- frontend/src/services/api.ts — 新增 aiApi.chatStream(messages, onChunk, signal)(~70 行):
- 用裸 fetch(绕过 http.ts 的 await json() 一次性消费)+ ReadableStream.getReader() + TextDecoder
- 按 `\n\n` 分割 SSE 事件块,解析 `event:`/`data:` 字段
- message 事件解析 {"content":"..."} 调 onChunk(delta);error 事件抛出 Error(message);done 事件跳过
- 返回累计完整内容;支持 AbortSignal 中断
- frontend/src/components/aiops/AIOpsAssistant.tsx — sendMessage 重构为流式(~30 行改动):
- 发送时立即插入 user 消息 + 空 assistant 占位气泡
- 调用 chatStream,每个 chunk 通过 setMessages 逐块追加到对应 assistant 气泡(React map + 不可变更新)
- 空内容 + isLoading 时显示"思考中..."spinner;有内容后末尾显示紫色闪烁光标(animate-pulse)提示流式进行中
- 错误处理:首个 chunk 到达前出错 → 整条气泡替换为红色错误;已部分输出时出错 → 末尾追加错误说明
- 移除原独立 spinner 块(流式占位气泡已包含思考提示)
- **协议设计(SSE 事件类型)**:
- `data: {"content":"..."}` — 内容增量(默认 message 事件)
- `event: done` + `data: {"model":"..."}` — 流结束(成功)
- `event: error` + `data: {"code":N,"message":"..."}` — 错误(连接失败/上游非200/未配置)
- **结果**:
- 后端 go build 通过;前端 npx tsc --noEmit 零错误;npm run build 通过(3.20s)
- ./run.sh restart 后 /health 正常 + 前端 200
- API 验证(curl + 模拟 OpenAI SSE 服务器 python3 /tmp/mock_openai_sse.py 监听 :18899):
- 临时启用 config 指向 mock server → POST /aiops/chat/stream 返回 SSE 流:`data: {"content":"你好!"}\n\ndata: {"content":"我是 "}\n\n...` 逐块输出,最终 `event: done\ndata: {"model":"qwen2.5:7b"}`
- sqlite3 验证:成对写入 chat_histories(user:"你是谁" + assistant:"你好!我是 EZOps..."),刷新后历史可见
- 未配置时 → `event: error\ndata: {"code":400,"message":"LLM 服务未配置..."}`
- 原非流式 POST /chat 仍正常工作(向后兼容)
- 浏览器验证 /ai-assistant:
- 输入"你是谁"+发送 → 用户气泡立即出现 → 助手空气泡显示"思考中..." → 第一个 chunk 到达后切换为内容 + 末尾紫色光标闪烁 → 完整 66 字回复逐字渲染
- console 零 JS 错误
- 测试后清理:config endpoint 恢复原值(enabled=0) + 清空 chat_histories + 停掉 mock server
- git push 成功
- **下一步建议**:
- 快捷操作可扩展更多数据源(容量预测接入 agent history 时序/K8s 集群健康/云账单分析)
- 用户消息气泡中【平台实时数据】块较长,可考虑折叠展示(默认收起,点击展开)
- 多会话支持(当前单用户单历史,可扩展多命名会话切换)
- 流式中支持"停止生成"按钮(AbortController 中断 fetch,后端 ctx.Done() 自动停止)