9.9 KiB
一键流式优化主流程设计
目标
把当前 GEO 文章优化主流程从“分析文章 -> 确认事实卡 -> 开始优化”改成“一键开始优化”。用户只需要粘贴完整文章并点击开始,系统自动提取事实卡、生成草稿、检查质量、定向修复并产出终稿。
第一版要让优化结果区像主流 LLM 产品一样持续有反馈,但不做 token 级逐字模型流。真实内容以阶段事件形式到达,前端负责用光标、呼吸高亮和打字机播放已到达内容来制造流式感。
范围
本设计覆盖现有 GEO 主流程,不改变普通文案优化标签页设计,不引入批量任务、团队审批、发布平台 API 或多品牌事实库。
本设计覆盖并替代旧 MVP 设计中“必须确认事实卡后才能优化”的主路径要求。旧的确认接口和非流式优化接口可以暂时保留为兼容和调试入口,但新首页主路径不再依赖它们。
用户流程
flowchart TD
A["粘贴完整文章"] --> B["点击开始优化"]
B --> C["后端创建任务并提取事实卡"]
C --> D["事实卡区展示紧凑事实卡"]
C --> E["结果区进入生成中状态"]
E --> F["显示优化草稿"]
F --> G["质量检查"]
G --> H{"需要定向修复?"}
H -->|是| I["显示修复中状态并更新稿件"]
I --> J["显示终稿"]
H -->|否| J
D --> K["用户可编辑事实卡"]
K --> B
输入体验
文章输入取消标题和正文的强制区别。
- 主输入改成一个大文本框,标签为
粘贴文章。 body仍是唯一必填内容。title可空,放到“更多选项”里作为可选输入。- 如果标题为空,后端把
title规范化为空字符串并保存;不从正文首行派生内部标题。最终优化结果仍必须返回标题。 - 图片描述、目标平台、用户要求保持现有能力。
用户点击 开始优化 后,前端清空旧结果,进入流式读取状态。
事实卡体验
事实卡仍然是独立事实卡区,不放进进度区。
事实卡区缩小为紧凑展示面板:
- 默认展示公司全称或简称、产品/品牌、目标行业、目标受众、核心事实数量、待确认事项数量。
uncertain_items不再阻塞优化,而是以黄色提示展示。- 用户可以展开编辑完整事实卡字段。
- 用户编辑事实卡后,再次点击
开始优化,使用当前事实卡重新跑一次优化。
第一版重新优化建议创建新 job,而不是覆盖原 job 的事实卡和导出结果。这样历史语义清楚,避免旧导出和新事实卡互相污染。
结果区流式感
结果区采用“草稿到终稿”的路线。
开始后立即显示动态结果框:
- 顶部显示当前阶段:
正在提取事实、正在生成草稿、正在检查质量、正在定向修复、终稿完成。 - 内容区显示空白稿纸、骨架段落和动态光标。
- 等待真实内容时,可以让光标旋转或跳动,让当前段落轻微呼吸高亮。
- 收到真实草稿后,前端用打字机效果播放已到达的草稿内容。
- 进入 QA 或修复阶段时,结果区保留当前稿件,并对正在处理的段落做呼吸高亮。
- 收到修复稿后,更新稿件内容,并可短暂高亮被替换的段落。
- 收到终稿后停止动效,展示终稿、导出链接和 QA 报告。
前端可以展示状态提示和骨架文本,但不能凭空生成正文内容伪装成模型输出。所有正文、标题、摘要、修复稿和终稿必须来自后端真实事件。
API 设计
新增流式一键接口:
POST /api/jobs/optimize-stream
该接口使用 fetch + ReadableStream,不使用浏览器原生 EventSource。原因是当前项目通过 x-api-key 传递访问密钥,fetch 可以继续安全地发送请求头。
请求头继续使用现有访问控制:
x-api-key: <访问密钥>
content-type: application/json
请求体:
{
"title": "",
"body": "完整文章内容",
"image_lines": "",
"platform": "official_site",
"user_instructions": "",
"fact_card": null
}
fact_card 为可选字段。为空时后端从文章提取事实卡;有值时表示用户编辑过事实卡,本次优化直接使用传入事实卡。
流式事件
响应采用 newline-delimited JSON,每行一个事件:
{"type":"job_created","job":{"id":"job_xxx"}}
{"type":"fact_card_ready","job_id":"job_xxx","fact_card":{}}
{"type":"draft_started","job_id":"job_xxx","message":"正在生成优化草稿"}
{"type":"draft_ready","job_id":"job_xxx","article":{}}
{"type":"qa_started","job_id":"job_xxx","message":"正在检查质量"}
{"type":"qa_ready","job_id":"job_xxx","qa_report":{}}
{"type":"rewrite_started","job_id":"job_xxx","round":1}
{"type":"rewrite_ready","job_id":"job_xxx","round":1,"article":{}}
{"type":"final_ready","job_id":"job_xxx","optimized_article":{},"qa_report":{},"export_paths":{}}
失败事件:
{"type":"failed","job_id":"job_xxx","stage":"draft","error":"LLM provider error: ..."}
事件规则:
fact_card_ready只更新事实卡区。draft_ready、rewrite_ready、final_ready更新优化结果区。qa_started、rewrite_started更新结果区状态和动效,不生成正文。failed保留已经收到的事实卡或草稿,并显示失败阶段和真实错误。
后端编排
后端按以下顺序执行:
- 校验访问密钥。
- 校验输入,
body不能为空,title可以为空。 - 创建 article job。
- 如果请求带
fact_card,解析并保存该事实卡;否则调用事实卡提取器。 - 推送
fact_card_ready。 - 调用文章优化器生成草稿,推送
draft_ready。 - 调用质量检查器,推送
qa_ready。 - 如果 QA 有失败项,最多执行现有定向修复轮次,并推送
rewrite_started/rewrite_ready。 - 保存最终优化稿、QA 报告和导出文件。
- 推送
final_ready。
新增一个流式 workflow wrapper,复用 optimizeArticle、inspectQualityWithLlm、rewriteFailedSections 和导出逻辑。现有 runOptimizationWorkflow 保留给非流式兼容接口,避免一次改动同时重塑两条 API 路径。
数据模型调整
ArticleInput.title 和 articleInputSchema.title 需要允许空字符串。
事实卡保存需要支持“未人工确认但可作为本次优化约束”的状态。第一版新增 OptimizationFactCard,并把 repository 的 saveFactCard / getFactCard 类型从 ConfirmedFactCard 放宽到 OptimizationFactCard。OptimizationFactCard 保留 confirmed_by_user?: boolean,避免继续把“系统自动事实卡”伪装成“用户确认事实卡”。
confirmedFactCardSchema 可以保留给旧确认接口;新流式接口使用新的 optimizationFactCardSchema。
错误处理
- 空正文:返回中文错误
请输入需要优化的文章内容。 - 访问密钥缺失或错误:沿用现有
401。 - LLM/provider 错误:推送
failed,显示真实错误,不静默 fallback。 - LLM 结构校验失败:推送
failed,保留阶段和校验摘要。 - 事实卡公司名、行业或受众为空:允许继续优化,但事实卡区提示约束不足,QA 中体现事实风险。
uncertain_items不阻塞优化,只作为事实卡区提示和 QA 风险信息。- 用户中途再次点击开始优化:前端应中断当前 stream,清空旧动效,创建新优化请求。
前端状态
首页主状态新增:
streamStatus: idle | running | completed | failedstreamStage: 当前阶段标签streamError: 失败文本displayedDraft: 结果区正在播放或展示的稿件finalArticle: 终稿factCardExpanded: 事实卡是否展开
结果区组件负责:
- 解析阶段文章。
- 播放打字机效果。
- 展示光标和呼吸高亮。
- 在终稿到达后停止动效。
页面级组件负责:
- 发起 stream 请求。
- 读取 NDJSON。
- 将事实卡事件分发给事实卡区。
- 将稿件事件分发给结果区。
- 处理中断和失败。
兼容策略
保留现有接口:
POST /api/jobsPOST /api/jobs/:jobId/confirm-fact-cardPOST /api/jobs/:jobId/optimizeGET /api/jobs/:jobId/progress
新首页主流程改用 POST /api/jobs/optimize-stream。旧接口可以继续服务测试、调试或未来非流式降级路径。
测试计划
单元测试:
articleInputSchema接受空标题但拒绝空正文。- 新
optimizationFactCardSchema接受未确认事实卡和非空/空的可编辑字段。 - NDJSON 编码 helper 可以按行输出合法 JSON。
API 测试:
- 未提供访问密钥返回
401。 - 空正文返回中文错误。
- 无
fact_card时会调用事实卡提取器,并输出job_created、fact_card_ready、draft_ready、qa_ready、final_ready。 - 有
fact_card时跳过事实卡提取器,直接使用传入事实卡。 - LLM 失败时输出
failed,错误包含阶段。 - QA 失败时仍写导出文件,并通过修复事件展示修复轮次。
组件测试:
- 开始优化后结果区显示动态光标和阶段文案。
- 收到
draft_ready后打字机播放真实草稿。 - 收到
rewrite_ready后结果区更新稿件并高亮变化。 - 收到
final_ready后停止动效并显示导出链接。 fact_card_ready更新紧凑事实卡区,不进入进度区。
E2E 测试:
- 用户只粘贴正文,点击
开始优化。 - 页面显示事实卡小面板。
- 优化结果区出现动态状态和草稿。
- 最终显示终稿、QA 报告和导出链接。
- 用户展开并编辑事实卡后,再次点击
开始优化可以开始新一轮流式优化。
不做范围
- 不做 token 级模型逐字输出。
- 不把事实卡放进进度区。
- 不要求用户确认事实卡后才能优化。
- 不用前端伪造正文内容。
- 不移除旧 API。
- 不新增团队审批、批量队列或发布平台 API。