08 · 前端:Vue3 + SSE 流式与执行链路可视化

对应代码:web/src/**

前端是很多 Agent 项目被忽略的一环,但它决定了用户敢不敢用

一、技术选型

技术 理由
Vue 3 + TypeScript 主栈,组合式 API
Vite 秒级启动
Pinia 状态管理(比 Vuex 轻)
Ant Design Vue 组件齐全,企业风格
原生 fetch + ReadableStream SSE(见下文)

二、为什么不用 EventSource

EventSource 只支持 GET ,而对话需要 POST 携带较长的问题文本和 thread_id

所以用 fetch + ReadableStream 手动解析 SSE:

ts 复制代码
export async function* streamChat(payload: ChatPayload): AsyncGenerator<AgentEvent> {
  const resp = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload)
  })
  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 })
    const parts = buffer.split('\n\n')      // SSE 以空行分隔
    buffer = parts.pop() ?? ''              // 保留不完整的尾巴
    for (const part of parts) {
      if (!part.trim().startsWith('data:')) continue
      try { yield JSON.parse(part.trim().slice(5)) } catch {}
    }
  }
}

两个关键点

  1. decoder.decode(value, { stream: true }) ------ 处理 UTF-8 中文被分包截断
  2. buffer = parts.pop() ------ 处理 TCP 分包导致的半个事件

三、状态设计(Pinia)

ts 复制代码
state: {
  threadId, messages[], steps[], loading, engine, tokens, iterations
}

steps[] 就是执行链路时间线的数据源,由事件流驱动:

ts 复制代码
consume(ev: AgentEvent, answerId: string) {
  switch (ev.type) {
    case 'token':      this.append(answerId, ev.delta); break      // 打字机
    case 'plan':       this.steps.push({ label: '规划', detail: 'sql_query → send_alert' }); break
    case 'tool_start': this.steps.push({ label: `调用工具:${ev.tool}` }); break
    case 'tool_end':   last.ok = ev.ok; last.detail += `\n结果:${ev.output}`; break
    case 'reflect':    this.steps.push({ label: '反思', detail: `信息是否足够:是/否` }); break
    case 'hitl':       msg.pendingHITL = ev.pending; break          // 确认卡片
    case 'final':      msg.citations = ev.citations; break
  }
}

四、三个交互设计

1. 打字机 + 自动滚动

ts 复制代码
watch(
  () => store.messages.map(m => m.content).join('').length,
  () => nextTick(() => { listRef.value.scrollTop = listRef.value.scrollHeight })
)

2. 执行链路时间线(可观测的前端呈现)

vue 复制代码
<a-timeline>
  <a-timeline-item v-for="s in store.steps" :color="colorOf(s.type, s.ok)">
    <div>{{ s.label }}</div>
    <div>{{ s.detail }}</div>
  </a-timeline-item>
</a-timeline>

颜色语义:规划=蓝,工具成功=绿,工具失败=红,反思=灰。

为什么重要 :企业用户看到"它在查数据库、它查到了 3 行、它在核对", 才会信任最终答案。黑盒输出 = 不敢用。

3. 引用溯源

答案里的 [1] [2] 渲染成 Tag,展示来源文档和章节:

vue 复制代码
<a-tag v-for="c in msg.citations" :key="c.id" color="blue">
  [{{ c.id }}] {{ c.title }} · {{ c.heading }}
</a-tag>

4. 人工确认卡片(HITL)

vue 复制代码
<a-alert type="warning" :message="`高风险操作待确认:${msg.pendingHITL.tool}`"
         :description="JSON.stringify(msg.pendingHITL.args)">
  <template #action>
    <a-button type="primary" @click="emit('approve')">确认执行</a-button>
  </template>
</a-alert>

点击后带 approved=true 重新请求,后端才真正执行。

五、知识库调试面板

左侧面板故意做成了"可观测 RAG":

ini 复制代码
[1] 缺陷分级与处理规范 · 缺陷等级定义
    score=0.42  vector=0.31  bm25=8.7
    P0 | 核心功能不可用... | 15 分钟 | 4 小时

同时展示融合分 / 向量分 / BM25 分,调参时能一眼看出: "这条是被向量召回的还是被关键词捞上来的"。

六、代理配置

ts 复制代码
server: {
  port: 5173,
  proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true } }
}

想连 Java 服务,把 target 改成 http://localhost:8081 即可------ 同一个前端,两个后端,这也验证了接口契约一致。

七、构建产物

bash 复制代码
npm run build
✓ 3179 modules transformed
dist/index.html                 0.40 kB
dist/assets/index-*.css         4.67 kB │ gzip:  1.80 kB
dist/assets/index-*.js      1,510.09 kB │ gzip: 466.24 kB

(1.5MB 主要是 ant-design-vue 全量引入,生产可按需引入 + 分包优化)

Python 后端会自动托管 web/dist,所以只启动后端也能访问完整页面


下一篇:09 · Java 双栈:Spring Boot + LangChain4j

相关推荐
再吃一根胡萝卜1 小时前
03 · 后端:FastAPI 与分层架构
面试
Interview Aid1125 小时前
Walmart Global Tech SDE 三轮面经|基础、并发、压力面
面试·职场和发展
ocean210310 小时前
2025-2026年AI提效与实践大厂面试高频问题
人工智能·面试·职场和发展·提示词工程·ai提效
程序员梅雨11 小时前
Linux & Shell 实用干货
linux·运维·服务器·后端·面试·php
YonyouHRSaaS13 小时前
AI视频面试系统定义、功能作用、品牌推荐、选择攻略
人工智能·面试·职场和发展·ai面试·视频面试·ai视频面试
小鱼爱吃草灬灬14 小时前
实时面试辅助排查清单:音频来源、问题输入与上下文
面试·职场和发展·音视频
颜挺锐17 小时前
如何轻松通过性能测试面试之第九篇:RPO、RTO是什么意思
面试·职场和发展
李剑一17 小时前
大环境或许真的恶劣了起来,打工人你焦虑吗?
面试·程序员·招聘
Rain的Java大神之路18 小时前
手写一个Spring容器
java·后端·mysql·spring·面试