Skip to content

SSE 流式对话

后端实现

  • 端点:POST /api/v1/chat/stream(JWT + Handler 内额度)
  • Gin c.Stream() + io.Writer 推送 SSE 事件
  • 请求体:{ sessionId?, message, personIds?, scenarioType?, model?, locale?, regenerate? }regenerate:重放上一轮用户消息并重新生成助手回复)
  • 响应 Header:Content-Type: text/event-stream, Cache-Control: no-cache, X-Accel-Buffering: no
  • Agent 生成的 StreamChunk 通过 Go channel 传递到 Handler,逐个序列化为 SSE 行
  • Pipeline 模式下,primary 和 secondary Agent 的输出连续流入同一 channel,通过 agent 字段区分
  • 事件格式:data: {"content":"你","done":false,"agent":"strategist"}\n\n
  • 结束事件:data: {"content":"","done":true,"sessionId":123,...}\n\n
  • 画像/档案更新提案(可选):当本轮对话触发「资料补全」门控并完成抽取时,结束事件的 JSON 可携带 profileProposal 字段(与 done: true 同包)。前端在流结束后读取该对象,弹出确认 UI;用户确认前不落库
    • reasonincomplete_self(我的画像偏空)、incomplete_person(关联人脉档案偏空)、refinement(上下文已较完整时的精炼补充)。
    • self / persons:字段级 patch 列表(key、currentValue、proposedValue、sourceNote、selected 等);偏空场景下后端可能只返回少量 LLM 抽取行,Web 端会在确认弹窗内 拉取当前画像/人脉并展开为完整可编辑表单(见 frontend.md)。
  • 流结束后:
    1. 保存完整内容为 assistant ChatMessage
    2. 异步触发 EmotionExtractor(非 Mock LLM 时)

前端实现

  • streamChat()@shice/api 包中,使用 fetch API + ReadableStream
  • 支持 POST 请求 + Authorization: Bearer header(非 EventSource)
  • 返回 AbortController 用于取消
  • 解析逻辑:按 \n 分割缓冲区,提取 data: 前缀行,JSON.parseStreamChunk(含可选 profileProposal

UI 效果

  • 打字机光标:流式内容末尾显示 2px 闪烁竖线(stream-cursor class, CSS @keyframes blink
  • 等待指示器:首个 chunk 到达前显示三点跳动动画
  • 停止生成:流式进行中,发送按钮变为红色停止图标(StopOutline),AbortController.abort()
  • 自动滚动:watch streamingContentscrollToBottom()
  • Pipeline 分段:当 agent 字段在流中切换时(如从 analyzer → writer),显示为连续输出,中间有分隔线

非流式回退

POST /api/v1/chat 保留作为非流式端点,内部同样经 Agent System,收集完整响应后返回 JSON。