96SEO 2026-04-23 11:51 1
作为前端工程师,我们经常会遇到这样的场景:产品经理跑过来说“咱们Neng不Neng在这个数据大屏里加个智Neng助手?用户就想问一句‘这周哪些指标不对?’,然后让他直接kan到结果。”

听起来简单对吧?不就是调个接口,把返回的文本塞进气泡里吗?但真动起手来你会发现坑无处不在:流式输出怎么处理才不会乱?用户切个页面对话丢了怎么办?报错了怎么展示才不像天书?geng重要的是下次另一个业务线也要Zuo类似功Neng,难道我们要从头再踩一遍坑?
Zui近,我们团队基于 OpenSpec 的实践理念,完成了一套智Neng助手流式对话前端的搭建。这不仅仅是一次功Neng的上线,geng是一次关于“如何让AI理解业务规范”以及“如何沉淀工程化资产”的深度探索。今天我想把这次从设计到落地的全过程摊开来聊聊,希望Neng给同样在“AI前端”领域摸爬滚打的同学一些实实在在的参考。
一、 为什么我们需要OpenSpec?不仅仅是文档过去Zuo这类中等复杂度的需求,我们Zui头疼的往往是“沟通成本”。产品说“要个助手”,设计说“要个抽屉”,后端说“我给你SSE”。大家各说各话,Zui后Zuo出来的东西往往千奇百怪。geng别提过两个月回过头来kan代码,连自己dou记不清当时为什么要这么设计了。
引入 OpenSpec 的初衷非常朴素:在动手写第一行代码之前,先把修改范围、验收标准和任务拆解写清楚。 这不是在搞形式主义,而是为了给后续的PR评审和AI辅助编程找一个“锚点”。
我们在仓库里建立了一个 openspec 目录,结构清晰明了:
openspec/
├── changes/
│ └── add-ai-qa-assistant/ # 本次变geng的专属文件夹
│ ├── proposal.md # Why & What:为什么要Zuo?要Zuo什么?
│ ├── design.md # How:接口设计、目录结构、关键Hook
│ └── tasks.md # 实施清单:可勾选的任务表
└── guides/ # 团队通用的规范沉淀
1. Proposal:先对齐认知,再谈技术
在 proposal.md 中,我们没有写一行代码,而是老老实实回答了三个问题:
Why业务同学现在的痛点是什么?
What我们要交付什么样的东西?
验收标准Zuo到什么程度才算完?
这些kan似琐碎的条目,后来在开发过程中全dou真实地踩到了。写在前面避免了无数次的返工和“这块儿到底要不要”的拉扯。
2. Design:给AIkan的“施工图纸”design.md 是面向实现的细节,它geng像是一份给AI编程助手kan的“施工图纸”。这里详细定义了接口的JSON结构、前端的目录分层,甚至是关键Hook的函数签名。
比如我们明确规定了UI组件和业务逻辑的分层原则:QaDrawer 目录下只放纯展示组件,而 useChatStreamuseConversationSession 这类Hook则放在页面的 hook 目录下。这种解耦设计,让未来把这些UI搬到设计系统级仓库变得轻而易举。
tasks.md 是一张可勾选的任务表,按依赖关系排序,每一行dou带预估工时:
| 序号 | 任务描述 | 依赖 | 预估 |
| ---- | ------------------------------------------ | ----- | ---- |
| 1 | 新增流式接口路径到接口配置 | — | 0.2h |
| 2 | useChatStream:封装 SSE 与事件分派 | 1 | 2h |
| 3 | useConversationSession:sessionStorage 持久化 | — | 0.5h |
| 4 | QaFab:右下角 FAB 悬浮按钮开发 | — | 0.5h |
效果非常显著:我们在PR描述里直接引用 tasks.md 的勾选状态;Code Review 也围绕 proposal.md 的验收条目逐条核对。那种“这块儿逻辑你漏了”的争吵明显变少了大家dou在同一个频道上对话。
有了规范作为指导,接下来的代码架构就水到渠成了。我们的核心目标是将纯展示组件与状态管理逻辑彻底剥离。
Zui终的目录结构大致如下这种结构不仅清晰,而且极易维护:
src/
├── components/
│ └── QaDrawer/ # 抽屉相关 UI
│ ├── index.vue # 容器:FAB + el-drawer
│ ├── QaFab.vue # 右下角悬浮按钮
│ ├── MessageList.vue # 消息列表 + 自动滚动
│ ├── MessageItem.vue # 单条消息
│ ├── ChatInput.vue # 输入区 + 免责声明
│ ├── QaWelcomeEmpty.vue # 空会话欢迎语 + 示例问法
│ └── buildQaContext.js # 页面状态 → 请求上下文
└── views//hook/
├── useChatStream.js # SSE 消费 + 消息追加
├── useConversationSession.js # 会话快照
└── useTypewriter.js # 打字机效果
这种分层的好处在于,UI层只关心“怎么画”,而Hook层只关心“怎么算”。Ru果未来我们要换一套UI库,或者把对话功Neng嵌入到另一个完全不同的页面中,Hook层的代码几乎不需要任何修改,直接复用即可。
三、 核心攻坚:SSE流式输出与前端打字机这是整个功Neng中Zui具技术挑战的部分。AI对话类场景目前有三种主流方案,我们Zui终选择了 SSE。原因hen简单:它基于HTTP,服务端推送成本低,且前端处理起来相对轻量。
1. 踩坑原生EventSource一开始,我们天真地想用浏览器原生的 EventSource。但hen快发现了一个硬伤:原生API不支持 POST 请求,也没法挂载自定义的 Header。这对于需要携带复杂上下文的企业级应用来说简直是致命伤。
没办法,我们只Neng换方案。Zui终选用了 @microsoft/fetch-event-source 这个库。它完美解决了上述问题,让我们Neng在 POST 请求中携带 JSON Body,并自定义 Header。
import { fetchEventSource } from '@microsoft/fetch-event-source'
const ctrl = new AbortController
fetchEventSource(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'text/event-stream',
'Authorization': 'Bearer xxx' // 自定义Header
},
body: JSON.stringify,
signal: ctrl.signal,
openWhenHidden: true, // 页面隐藏时不断连
async onopen {
const ct = resp.headers.get || ''
// 校验响应头,确保是SSE流
if ) {
throw new Error
}
},
onmessage {
const payload = JSON.parse
dispatch // 核心分发逻辑
},
onerror {
// 注意:这里必须抛出错误,否则库会自动无限重连
if throw err
ElMessage.error)
throw err
}
})
2. 事件分派与状态机
后端推过来的数据流是一段段 JSON,我们需要一个“分派器”来处理这些事件。每条消息大致包含 startdataenderror 这几种类型。
我们定义了一个清晰的数据结构来维护每一条消息的状态:
{
__qaMsgId: 'unique_id',
role: 'assistant',
status: 'streaming', // 状态:streaming | done | aborted | error
thoughtsText: '', // 思考过程累积
fullText: '' // 正文累积
}
分派器的逻辑其实就是一个简单的状态机:
function dispatch {
const asst = lastAssistant // 获取当前Zui后一条助手消息
if {
// 初始化会话ID
sessionId.value = payload.sessionId
conversationId.value = payload.conversationId
return
}
if {
// 区分是“思考过程”还是“正文”
if ) {
asst.thoughtsText += payload.content || ''
} else {
asst.fullText += payload.content || ''
}
return
}
if {
asst.status = 'done'
fillEmptyAssistantFallback // 末端兜底,防止空消息
return
}
if {
asst.status = 'error'
asst.fullText = resolveDisplayText
asst.streamErrorDetail = String
return
}
}
3. 那个让人抓狂的“竞态”问题
在开发过程中,我们遇到了一个非常经典的坑:用户在流式输出中途,又发了一条新消息。
现象hen诡异:两条答案的文本块会交替出现在同一个气泡里甚至导致UI错乱。这是因为前一个请求的 onmessage 还在回调,但我们的上下文Yi经切换到了新的请求。
解法其实hen暴力但有效:给每次请求加个 runId。在回调里判断一下Ru果当前的 runId 和消息里的 runId 对不上,直接丢弃。
let runId = 0
function run {
if abort // Ru果正在跑,先停掉
runId += 1
const thisRunId = runId
loading.value = true
fetchEventSource(url, {
onmessage {
// Ru果不是当前请求的消息,直接无视
if return
// ... 正常处理
}
})
}
四、 体验打磨:打字机效果与跨页会话
技术通了不代表体验就好。为了让用户感觉这真的是一个“智Neng助手”,我们在细节上花了不少心思。
1. 打字机效果的CatchUp机制流式输出虽然快,但Ru果文字一下子全蹦出来体验太生硬。所以我们接了一层 useTypewriter Hook,让文字像打字一样逐个出现。
这里有个不起眼但极重要的点:当 status 从 streaming 变为终态时无论打字机当前追到哪里dou必须强制执行 catchUp。
为什么?因为用户可Neng会在打字还没结束时就点开旧消息或者切页面。Ru果不强制追齐,用户kan到的可Neng就是半截文本,甚至以为出错了。
const { displayedText, catchUp } = useTypewriter({
sourceRef: computed => message.fullText),
speed: 30 // 毫秒/字
})
// 监听状态变化
watch => message.status, => {
if ) {
catchUp // 立即显示完整文本
}
})
2. 跨页面共享会话
Zui初的想法是每个分析页一个 storageKey,严格隔离。但上线灰度后用户反馈非常直接:“我在 A 页和助手聊了一半,切到 B 页想继续聊 —— 怎么又重置了?”
确实用户在同一个产品域内,心智模型里只有一个“助手”。所以我们把同一产品域内的页面统一到同一个 storageKey,实现了跨页面共享同一会话。
实现代价只是一行默认参数,但用户体验的提升是巨大的。配合 sessionStorage,我们在路由卸载时 save,路由挂载时 restore,完美解决了“一关就没了”的焦虑。
600px 的默认宽度,对于展示简单的文本没问题,但一旦涉及到代码块或者复杂的表格,就显得太局促了。我们在抽屉左缘加了一个 6px 宽的拖拽手柄。
通过监听 document 的 mousemove 和 mouseup,我们允许用户向左拖动来加宽抽屉。当然我们也加了 clamp 限制,把宽度控制在 400px 到 min 之间,防止用户把抽屉拖得盖住了主内容区。
Zuo前端Zui怕的就是报错,geng怕的是报了一堆kan不懂的错。错误处理尤其棘手。
我们遇到过一种情况:部分错误走 HTTP 4xx,返回体是网关包裹的 JSON,里面又嵌着一层服务返回的 JSON,里面才是真正的业务错误文案。Ru果直接展示Zui外层,用户kan到的就是一串乱码般的 {"code": 500, "msg": "Internal Error"}。
为了解决这个问题,我们写了一个简单的“JSON 剥洋葱”工具 extractNestedErrorMessage。它会递归地解析返回体,直到找到那个Zui像人类语言的字符串,或者直接抛出默认的友好提示。
同时我们在错误渲染的时候,同时暴露了两种入口:一个是“重试”按钮,一个是“复制错误信息”按钮。这样,既照顾了普通用户的体验,也方便了专业用户排查问题。
六、 沉淀与复用:从代码到Agent SkillOpenSpec 解决的是这次怎么Zuo,那下次Zuo类似的事情呢?
我们在团队里试了一种Zuo法:项目交付的同时把可复用的部分抽成一份 Agent Skill,随代码一起沉淀到仓库。
与其他沉淀形式相比,文档需要人主动去找,模板或脚手架容易版本漂移、改动不回灌。而 Skill 介于两者之间:Agent Ke以触发加载,内容随仓库一起演进,脚本甚至Ke以直接拿走用。
下次再Zuo类似的接入——可Neng是另一个业务线、另一种 Agent 平台——AI 编辑器Neng自动识别、加载这份 Skill。新同学从一开始就站在“Yi经踩过坑”的起点上。一个geng实际的衡量是:同类需求的第二次Neng不Neng在geng短时间内跑通Zui小 Demo?
这次把经验沉淀完后我们在另一个业务线上复用了一遍,从依赖安装到kan到流式文本出现在气泡里整体时间比第一次短了一个数量级。这就是规范化和资产化的力量。
从OpenSpec的规范制定,到SSE流式的技术攻坚,再到细节体验的反复打磨,这次智Neng助手的开发过程让我们深刻体会到:AI 时代的工程化,不仅仅是写代码,geng是写规范、写逻辑、写体验。
OpenSpec 让我们不再盲目地堆砌功Neng,而是有了清晰的导航;而将经验沉淀为 Agent Skill,则让我们避免了重复造轮子的宿命。希望我们的这些经验,Neng成为你构建下一个AI应用时的垫脚石。毕竟站在巨人的肩膀上,总比自己从零开始挖坑要来得快,不是吗?
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback