AI 原生 UX 模式工程 2026:五维交互的统一架构
把 streaming UI / structured output UI / HITL / inline generation / 版本化 5 个维度放在同一个 document model + schema pipeline + versioned log 的架构层里统一设计,是 AI 原生 UX 从 feature 走向学科的工程拐点。
约 31 分钟阅读9,123 字4 次阅读博主

把 streaming UI / structured output UI / HITL / inline generation / 版本化 5 个维度放在同一个 document model + schema pipeline + versioned log 的架构层里统一设计,是 AI 原生 UX 从 feature 走向学科的工程拐点。

过去三年 LLM 应用的产品形态发生了一次静悄悄但深刻的迁移:从"在网页里塞一个聊天框"进化到一种由流式输出、结构化输出、原地编辑与人机协作共同定义的全新交互语法。本文把这套语法命名为 AI 原生 UX(AI-native UX),把它从一组零散的交互模式提升为一门可被工程化建模、可被架构选型、可被回归测试的学科。我们关注的核心问题不是"如何让 AI 回答更聪明",而是"如何让 AI 的输出与人类的工作流在同一个视觉与状态空间内共存"。这意味着我们要回答五个紧密耦合的工程问题:第一,流式输出在 UX 层应当被建模为什么样的契约;第二,结构化输出(structured output / function calling)在渲染层如何获得类型安全的呈现;第三,human-in-the-loop 不再是"按一个按钮让 AI 重答",而是一种带语义的可中断、可修正、可审计的交互协议;第四,AI 内联生成(inline generation)要求前端状态机能够区分"用户键入的字符"与"AI 续写的字符",并在视觉、focus、撤销栈等细节上呈现;第五,撤销与回滚不再是 Ctrl+Z 独占的本地语义,而是跨模型、跨会话、跨工作区的全局状态问题。我们把上述五点称为 AI 原生 UX 的五维,并主张任何严肃的 LLM 产品都应当把这五维作为一个统一的架构层来设计,而不是五个独立的前端 feature。
2026 年回头看,2023 年的 LLM 应用几乎都长一个样子:一个聊天界面,左侧历史会话列表,右侧对话气泡。Notion AI 第一次把"在文档里高亮一段文字、让它改写"做成了一个产品功能——这看上去只是一个交互变化,本质上却把 LLM 从"独立窗口的协作者"变成了"上下文敏感的内联助手"。自此之后,Figma AI 的 layer 生成、Cursor 的 inline edit、Vercel v0 的 streaming 组件渲染、Linear AI 的 issue 自动填充、Raycast AI 的 command-bar 多步执行,无一例外都遵循同一条原则:AI 不再是一个 destination,而是一个 in-place collaborator。要做到这一点,前端工程必须重新建模三件事:时间的边界(什么时候开始 stream、什么时候打断、什么时候继续)、类型的边界(LLM 输出不再是字符串,而是带 schema 的对象)、权限的边界(哪些字符是用户写的、哪些是 AI 写的、用户能修改哪些)。这三件事在传统 web 框架里没有先例——React、Vue、Svelte 都没有原生支持"局部可中断的流式渲染"和"AI 续写字符的 focus 管理"。
我们把 AI 原生 UX 拆成五个维度,每个维度对应一组明确的失效模式(failure mode)与一组工程契约。
第一维是 streaming UI,对应的失效模式是"打字机效果假象"——把整段模型输出渲染完再一次性 show 给用户,视觉上是流式,实际是一次性返回。真正的 streaming UI 必须在 token 到达即触发 layout reflow,同时不允许因 token 边界造成滚动抖动、布局漂移、光标错位。
第二维是 structured output UI,对应的失效模式是"JSON 字符串渲染"——把 {"name":"x","items":[...]}" 当作 string 显示。正确的做法是把 schema 编译成可识别的 UI primitive(卡片、表格、表单),用户看到的是一个有意义的视图,而不是字符串化的结构。
第三维是 human-in-the-loop,对应的失效模式是"被动等待重答"——用户对 AI 输出不满意,只能按"重新生成"按钮,结果模型从零再来一遍。工程化的人类协作要求用户能在 AI 输出中途插入、删除、改写片段,并且这些修改会被模型作为后续生成的约束。
第四维是 AI 内联生成,对应的失效模式是"AI 字符与用户字符混淆"——AI 续写的文字被错误地纳入撤销栈,用户按 Ctrl+Z 撤销的不是自己的输入,而是 AI 的输出,状态机错乱。
第五维是撤销与回滚,对应的失效模式是"局部可逆、全局不可逆"——单个输入框内可以 Ctrl+Z,但跨会话、跨文档、跨模型版本的回滚没有机制,一旦 AI 写错了关键信息,用户无路可退。
把这五维放在同一个架构层里设计的核心收益是:它们共享一个状态机,共享一份类型 schema,共享一套撤销语义,因而可以统一测试、统一监控、统一发布。
流式输出是 AI 原生 UX 中最显眼的特征,也是最容易做错的。表面上看,它只是"打字机效果"——字符一个一个蹦出来。但工程上要解决的问题远比这个复杂。
第一是 token 到 DOM 的延迟预算。一个 ChatGPT 风格的 UI 在网络良好的情况下,token 从服务端到达浏览器到被 React/Vue 实际渲染到 DOM 中的延迟必须小于 16ms(一帧 60fps 的预算),否则用户会感觉"卡"。这意味着前端不能等到一个 SSE event 到达才 setState,而是要在 microtask 内做 batch——把同一个事件循环 tick 内到达的 token 合并成一次 render。这一点 Vercel AI SDK 的 useChat 用 useSyncExternalStore + flushSync 实现得很干净,值得借鉴。
第二是滚动与 layout 的稳定性。流式输出会让一个长文本容器从 0 行增长到 N 行。如果容器高度是 auto,每次新增一行都会触发祖先元素的 reflow;如果祖先用了 position: sticky 或 virtual scroll,reflow 会带来闪烁。工程上的标准做法是把 AI 输出区域放在一个独立的滚动容器内,与外层页面 chrome(顶部 nav、左侧 sidebar)解耦——这就是为什么 Notion AI、ChatGPT 的对话区都是一个 fixed-height 的 inner scroller。
第三是中断与续写的语义。当用户点击"Stop generating"时,前端必须立刻(< 50ms 感知阈值)停止视觉上的 token 增长,并且把已显示的内容标记为"被截断",后续用户可以接着输入 prompt 让模型续写。这要求 server 端保留一个 stream session id,前端通过 session id resume。这是 SSE protocol 本身不支持的,必须在应用层做 session 管理。
第四是流式输出的"伪完成"问题。模型在生成结束时会有一个 end token(如 </s> 或 <|im_end|>),但人类读者没法从视觉上区分"模型还在打"和"模型打完了"。工程上需要一种 explicit affordance——一个闪烁的光标,一个渐隐的省略号,或者一个明确的"Stop"按钮在流结束后自动隐藏。当前主流框架(Vercel AI SDK、LangChain.js、LlamaIndex TS)的设计是一致的:流期间显示 "Stop" + spinner,流结束后切换为 "Copy / Regenerate" 按钮。
结构化输出(structured output)是 LLM 应用走向严肃产品形态的关键技术。当模型输出不再是聊天文本,而是 JSON、YAML、带 schema 的对象时,前端必须把字符串化的结构翻译成可交互的视图。
最朴素的失败是直接把 {"name":"foo","price":42} 渲染成一个 <pre> 标签里的字符串。ChatGPT 早期在 function calling 上的"JSON 模式"展示就是这样——模型试图把 JSON 写得人类可读(带换行、引号转义),结果既不是合法 JSON 也不是人类友好的描述。后来 OpenAI 的 structured outputs(response_format: json_schema)强制模型输出严格符合 schema,前端用 ajv / zod 校验后才渲染。
更深一层的工程问题是"schema 的版本演进"。一个 weather agent 的输出 schema 在 v1.0 可能有 temperature_c、humidity;v1.1 加了 air_quality_index;v1.2 把 temperature_c 拆成 temperature_minimum_c 和 temperature_maximum_c。前端 UI 必须能在 schema 升级后优雅降级——未知字段不报错,老字段缺失时显示 "—" 而不是崩。这种"渐进式 schema"模式本质上是 API 版本管理的现代形态,与 GraphQL 的 @deprecation 指令精神一致。
第三层是结构化输出的可交互性。一个 {"actions":[{"type":"send_email","to":"...","body":"..."}]} 不应该只是展示,而应该让用户能编辑每一项 action、否决某些项、然后批量提交。这就是 ChatGPT 在 2025 年推出的 "Tasks / Apps SDK" 的核心思想——LLM 输出的是候选 action 列表,用户在 UI 上确认后才执行。这把"AI 决策"和"AI 执行"分离开来,是 AI 原生 UX 的重要设计模式。
类型契约方面,前端必须把 schema 编译成 TypeScript 类型(用 zod-to-ts、quicktype、json-schema-to-typescript),并在编译期检查 UI component 的 props 与 schema 字段是否对得上。一个标准的工程模式是:后端 schema → 单一 source of truth(zod schema)→ 同时生成 OpenAPI / GraphQL / TypeScript 类型 → 前端 component 用 TS 类型驱动 → 任何字段缺失或不匹配在编译期就报错。
Human-in-the-loop(HITL)不是"按 regenerate"那么简单。一个工程化的 HITL 必须支持五种操作:插入(用户在 AI 输出中间添加文字或指令)、删除(用户删除 AI 输出的某段)、改写(用户选中一段并要求 AI 改写)、签批(用户接受 AI 输出)、拒绝(用户拒绝并要求 AI 重做)。这五种操作在状态机层面是不同的:插入和删除是字符级操作,改写是局部流式结构,签批和拒绝是会话级操作。
Cursor 的 Cmd+K inline edit 是 HITL 的经典案例:用户选中文本,输入指令,AI 给出一个 inline 替换建议(diff 形式),用户可以接受(accept all)、拒绝(reject)、或逐块接受(accept chunk by chunk)。这个交互比"重新生成"强在哪里?强在用户保留了对"原始文本"的引用,diff 是双向的,用户能清楚知道 AI 改了哪几个字、改成了什么。diff 作为 HITL 的通用语义已经在代码、文档、3D 模型编辑领域成为事实上的事实标准,未来会扩展到所有富媒体 AI 编辑场景。
更深一层的 HITL 是"用户在 AI 输出中途介入"。例如 AI 在写一段 500 字的文章,用户读到 200 字时觉得方向不对,直接在 200 字处插入一段 prompt 要求 AI 改方向。这在工程上要求模型支持"中途中断 + 重新生成剩余部分",而不是从零重答。这是 Anthropic Claude 的 prompt caching + 增量生成 + interrupt API 在产品层面的体现,也是为什么 2026 年的 LLM AI 应用越来越倾向于支持 interrupt。
HITL 的可逆性体现在:用户的每一次介入都应该被记录成一个 event,event log 是后续版本控制、回滚、A/B 测试的数据源。一个标准的 HITL event schema 包括:{event_id, timestamp, session_id, action: "insert|delete|rewrite|approve|reject", range: {start, end}, payload}。这个 log 应该被持久化到数据库,而不是仅存在前端内存——否则刷新页面就丢失。
AI 内联生成(inline generation)是 AI 原生 UX 最具迷惑性的能力。它的核心特征是:AI 的输出和用户的输入在同一个文本框内共存,视觉上像同一个文档,但语义上是两种字符。这种"视觉同形、语义异质"给前端状态机带来了极大挑战。
Notion AI 的 "Continue writing" 是早期范例:用户在某段文字后按空格 + tab,AI 接着写一段灰色文字(gray-out),用户可以 tab 接受、esc 拒绝、继续输入打断 AI。这一交互看上去只有四个状态(idle / streaming / accepted / rejected),实际工程实现要处理:AI 续写字符的 focus 边界(用户光标落在 AI 字符上时光标形状必须暗示"这是 AI 写的")、撤销栈的处理(Ctrl+Z 应该撤销 AI 字符而不是用户的输入)、selection 行为(用户能否选中 AI 字符并改写)。
更深一层是"内容与产品的边界"。当 AI 续写的字符被用户接受后,这些字符的"归属"是什么?从法律上是用户的(因为是用户接受的),从审计上是 AI 的(因为模型生成),从版本控制上是 mixed(用户在 AI 基础上又改写)。这种归属问题在 SaaS 产品里通常用一个"color tag"机制解决:AI 写的字符显示为灰色斜体,用户改写为黑色常规,AI 再改写为蓝色下划线——三色 tag 让归属一目了然。
工程实现上,inline generation 要求文本编辑器放弃传统的"线性 buffer"模型,改用"多 source buffer"模型。一个开源典范是 Lexical(Facebook 出品)和 Slate,它们的 document model 都是基于 Node Tree 而不是 string,每个 Node 带一个 origin: "user" | "ai" | "tool" 标签。撤销栈重做节点级的 diff,而不是字符级的 diff。这种 model 的工程成本显著高于传统 contenteditable,但在 AI 原生 UX 场景下是必需的。
撤销(undo)与回滚(rollback)是 AI 原生 UX 最被低估的能力,也是用户信任的根基。一个不支持可靠撤销的 AI 编辑器会让用户"不敢让 AI 改"——因为改坏了回不去,AI 就从"助手"变成了"风险源"。
传统 web 编辑器的撤销栈是线性的、本地的、会话级的。但 AI 编辑需要更复杂的语义:
第一是"局部撤销"与"全局撤销"的统一。用户在一个段落内 Ctrl+Z 应该撤销最近一次操作(无论是用户输入还是 AI 续写),但用户也应该能撤销 30 分钟前的整次 AI 生成——这要求撤销栈是分层级的:底层是字符级 delta(毫秒级),中层是编辑操作(秒级),上层是 AI 生成会话(分钟级)。每一层都有自己的 group/ungroup 语义。
第二是"跨文档撤销"。Notion、Linear 这类多文档 AI 应用经常出现"AI 在 A 文档的建议是基于 B 文档的内容生成的"——用户撤销 A 文档的建议时,是否也要撤销对 B 文档的引用?工程上常用 dependency graph 解决:每个 AI 生成节点是一个 node,它的 parent 是用户当时的 prompt + 上下文;撤销一个 node 时,cascade 地把所有 child node 也标记为 invalid。
第三是"版本化"。Linear AI 的 issue 创建自动化、Cursor 的代码 commit 自动化、Notion AI 的 page 自动更新,都有一个共同点——每一次 AI 写入都应该是一个 git-like 的 commit,带 message("AI 自动补全"、"AI 改写")、author("AI:claude-3.7")、timestamp、parent。用户能 diff 任意两个 commit,能 revert 到任意历史版本,能 cherry-pick 某个 AI commit 应用到其他位置。
工程实现上,一个稳定的 AI 原生 UX 版本化架构通常包含:CRDT-based document model(Yjs / Automerge)+ operation log(每次 AI 写入是一个 op)+ commit layer(把一组 op 打包成一个 commit,带 message)+ UI layer(diff viewer、commit history、cherry-pick tool)。这是为什么 2026 年的严肃 AI 编辑产品(Cursor、Linear、Notion)都在用 CRDT——不是因为 CROT 性能好,而是因为 CRDT 的 operation-based 本质天然契合 AI 编辑的语义。
把以上五维放回工程实践,我们给前端架构师一个 5-step 决策树:
第一步:评估产品的"AI 编辑密度"。如果产品的核心交互是"AI 一次性输出、用户看完就走"(如搜索型 AI、问答型 AI),streaming UI 是核心,structured output 与 HITL 次要,inline generation 与版本化不必做。反之,如果产品是"AI 持续与用户协作编辑一份内容"(如 AI 写作助手、AI 代码助手),inline generation 与版本化是核心,五维全部要做。
第二步:选型 document model。对于"一次性输出"产品,传统 string buffer + virtual scroll 即可。对于"协作编辑"产品,必须用 CRDT(Yjs / Automerge)或 operation-based model(Lexical / Slate)。不要尝试自己造 document model——已经有大量开源方案。
第三步:选型 schema pipeline。后端 schema 必须有 single source of truth(zod / JSON Schema / Protobuf),并通过 codegen 同时生成 OpenAPI、TypeScript 类型、Swift/Kotlin 类型、UI primitive。任何在多个地方手动维护 schema 的做法都会在第一个 schema 演进时崩。
第四步:选型 streaming protocol。SSE、WebSocket、gRPC stream 都可,但要注意:SSE 是单向(server → client)、HTTP 友好、断线恢复难;WebSocket 是双向、需要独立 server、断线恢复容易。如果产品有 interrupt / cancel 需求,WebSocket 更合适;如果只是单向 stream,SSE 足够。
第五步:选型撤销与版本化存储。本地 LocalStorage / IndexedDB 是 baseline,但要支持跨设备撤销就必须上 server-side log。建议的最小可行架构是:每次 AI 写入作为一个 op 写入 server-side log + 本地 mirror,撤销从 server-side log 重放。
补充一个常被忽视的细节:撤销栈的 group 粒度必须按"用户感知边界"而不是"事件边界"。例如 AI 在 5 秒内连续写了 12 段文字,事件层是 12 个 op,但用户感知是一次写作——撤销栈必须把这 12 个 op 合并成一个 group,让用户按一次 Ctrl+Z 就回到 AI 开始前的状态。这是为什么 Linear AI 和 Notion AI 都采用"会话级 group"的撤销实现。一个工程化的方案是给每个 op 标记一个 session_id 和 coalesce_key,撤销栈按 session_id 合并显示。
最后给前端工程师一个具体可执行的设计清单,落到 6 条具体规则。
第一,永远不要直接把 LLM 输出当作字符串渲染。任何 LLM 输出都必须先经过 schema 校验,再翻译成 typed object,最后渲染成对应的 UI primitive——卡片、表格、表单,而不是 <pre> 字符串。
第二,AI 续写的字符必须用视觉 tag 区分。AI 写的字符显示为灰色斜体,用户改写后变为黑色常规;用户与 AI 的边界用 color / underline / opacity 三选一标识,不要让用户靠记忆分辨。
第四,AI 的每一次写入都是一个可审计的 commit。每次 AI 生成都应带 message、author、timestamp、parent chain,并存入 server-side log。这是产品信任与法务合规的双重要求——2026 年欧盟 AI Act、中国生成式 AI 管理暂行办法都已明确"AI 生成内容必须可追溯"。
第五,撤销栈必须分层。毫秒级的字符 delta、秒级的编辑操作、分钟级的 AI session 必须分桶,每一层有自己的 group/ungroup 语义。不要把所有操作混在一个线性栈里——用户会迷失。
第六,interrupt 是 first-class capability。任何 AI 生成都应该允许用户在 100ms 感知阈值内中断,并且中断后能 resume 续写或重新定向——这不是锦上添花,而是 HITL 的最低门槛。
补充第七条看似基础但常被忽略的规则:所有 AI 输出必须在视觉上有一个明确的"边界标记"。不论是用浅色背景框、用左侧细 border、还是用右下角的小角标,只要让用户一眼看出"这段是 AI 写的,下面是我写的"就行。这是 AI 原生 UX 的最小可访问性要求——色弱用户、屏幕阅读器用户、跨语言翻译场景下的用户都需要这个边界才能区分内容归属。一些团队采用 AI 输出区用浅蓝背景框 + 右上角"AI"小图标的双重视觉编码,色觉正常用户看背景框、辅助技术用户听图标 alt 文本,形成双重冗余。
把 streaming UI / structured output UI / HITL / inline generation / 版本化 5 个维度放在同一个 document model + schema pipeline + versioned log 的架构层里统一设计,是 AI 原生 UX 从 feature 走向学科的工程拐点。
Conversation
0 条