AI 爆款文章创作器的前端业务本质是一条「选题 → 标题 → 大纲 → 正文 → 配图 → 合成」的多阶段流水线,每个阶段由独立 Agent 驱动,前端通过 Pinia 维护全局阶段状态、用 SSE 事件流推进流程、以「阶段锁」保证用户不可跳步。整体设计原则是让 AI 生成与人工编辑交替进行:每个阶段产出即落库,用户可随时介入修改或触发重新生成,最终统一由「合成」动作把正文与配图合并成完整图文。这套流程在 Vue 3 + TypeScript + Ant Design Vue 的工程里落地,核心是三个模块:任务状态机、SSE 事件分发器、阶段化编辑器。
一、前端业务的主线流程
把一次完整的文章创作拆成五个阶段,前端的职责边界清晰:选题与标题阶段偏「选择型交互」,大纲与正文阶段偏「流式接收 + 编辑」,配图与合成阶段偏「异步任务跟踪」。
| 阶段 | Agent 动作 | 前端交互 | 用户介入点 |
|---|---|---|---|
| 选题 | 生成 3-5 个标题方案 | 卡片列表 + 单选 | 选一个,或自定义输入 |
| 大纲 | 按标题流式生成树形大纲 | SSE 打字机渲染 | 节点增删改、拖拽排序 |
| 正文 | 按大纲分节生成 Markdown | 分段流式追加 | 逐节重写、局部扩写 |
| 配图 | 分析正文并生成配图需求 | 进度条 + 图片预览 | 确认 / 换图 |
| 合成 | 合并图文产出成品 | 预览 + 导出 | 微调后导出 |
前端把一个生成任务对象 task 贯穿全程,后端收到选题后返回 taskId,之后所有阶段都挂在同一个 task 上。SSE 事件里带 taskId 与 eventType,前端据此更新对应阶段的数据,而不是各自开请求。
1.1 阶段状态机的定义
前端用一个联合类型描述任务生命周期,每个阶段内部还有自己的子状态。
type TaskStatus =
| 'PENDING' // 已创建
| 'TITLE_DONE' // 标题已确认
| 'OUTLINE_DONE' // 大纲已确认
| 'CONTENT_DONE' // 正文已生成
| 'MERGED'; // 合成完成
type StageState =
| 'IDLE' // 未开始
| 'GENERATING' // 生成中(含流式)
| 'EDITABLE' // 生成完毕可编辑
| 'FAILED'; // 该阶段失败
Pinia 的 useTaskStore 里存 status 和每个阶段的 state,页面路由依据它们决定渲染哪个流程组件。进入「生成」状态时,前端只做两件事:调接口拿 SSE 流,以及把事件写进 store。
二、SSE 事件流如何驱动业务推进
SSE 在本项目里不只是文本流,它同时承担「流程推进信号」的职责。后端发送的事件类型与前端 store 的 action 一一对应:
// useTaskStore.ts 关键片段
const events = {
AGENT1_COMPLETE: () => { taskStatus.value = 'TITLE_DONE' },
AGENT2_STREAMING: (payload) => {
outlineStream.value += payload.delta
},
AGENT2_COMPLETE: () => {
outlineStream.value = ''
stageState.outline = 'EDITABLE'
outlineTree.value = parseOutline(payload.outline)
},
AGENT3_STREAMING: (payload) => {
contentStream.value += payload.delta
},
AGENT3_COMPLETE: () => { stageState.content = 'EDITABLE' },
IMAGE_COMPLETE: (payload) => { images.value.push(payload.url) },
MERGE_COMPLETE: (payload) => {
article.value = payload.article
taskStatus.value = 'MERGED'
},
ERROR: (payload) => { fail(payload.message) }
}
收到 AGENT2_COMPLETE 等完成事件时,前端要清空流式缓冲、把阶段置为 EDITABLE,并解析出结构化数据(大纲树、图片列表)交给编辑器。这个「流式数据 → 结构化数据」的切换点,是业务推进的关键节点。
2.1 阶段锁与用户介入
流程上用「下一步」按钮做阶段锁:只有当前阶段进入 EDITABLE 才允许跳转,GENERATING 状态下按钮禁用。用户想跳过配图直接合成时,前端校验阶段状态,未完成的阶段先补触发再放行,避免后端拿到残缺数据。
三、页面级流程编排
单页应用里用路由 + 组件切换承载五阶段,共用同一个 task 上下文。
- 用户新建任务,填选题与要求,提交
POST /api/tasks,拿到 taskId; - 前端跳转到「标题确认页」,等待
AGENT1_COMPLETE事件渲染候选标题; - 用户选中标题,前端调
POST /api/tasks/{id}/outline开启大纲生成,进入大纲页; - 大纲流式渲染完成后,用户编辑或直接调
POST /api/tasks/{id}/content生成正文; - 正文完成自动进入配图阶段,配图逐个到达并预览,最后点合成,跳转成品页导出。
每步都由一个独立组件负责:TitlePicker.vue、OutlineEditor.vue、ContentEditor.vue、ImageGallery.vue。它们不直接互相通信,全部读写 useTaskStore,刷新页面后从 store 恢复阶段,避免刷新即丢进度。
3.1 跨阶段的数据流转
大纲树的数据结构是前端统一约定:
interface OutlineNode {
id: string
title: string
children: OutlineNode[]
}
大纲阶段确认后,正文生成接口直接复用这份树;合成阶段又用它做章节定位。配图需求分析的结果挂在对应大纲节点上,形成「章节 → 段落 → 图片」的关联,最终合并成完整 Markdown。
四、异常与中断的处理
流式生成遇到断连,前端用 AbortController 中断请求并提示重试;已落库的阶段数据保留,用户从失败阶段重新触发即可,不重跑已完成步骤。后端在 SSE 关闭时也会收到通知停止生成,避免浪费算力。前端同时监听 ERROR 事件,把失败阶段置回 EDITABLE 让用户选择重试或修改条件再生成。
这套流程跑通后,单篇文章从选题到成品可全自动完成,也可每个阶段人工把关。核心收益是「生成可打断、结果可修改、进度可恢复」,这也是多阶段架构相对单次大生成最重要的差异。
常见问题(FAQ)
Q1:为什么前端要用状态机管理阶段而不是直接跳路由?
状态机能锁跳步、记失败、恢复进度,路由只负责展示,二者解耦。
Q2:刷新页面后生成到一半的流式内容还在吗?
流式缓冲在内存,刷新即丢,但已完成阶段已落库,重新触发即可续跑。
Q3:用户可以跳过配图直接合成吗?
不能,阶段锁会拦截。未完成配图需先补生成,保证成品图文完整。