乐于分享
好东西不私藏

前端与 AI(十一):AI 应用里的状态管理,比传统前端复杂在哪里

前端与 AI(十一):AI 应用里的状态管理,比传统前端复杂在哪里

传统前端状态管理已经不轻松:表单、请求、列表分页、权限、弹窗、缓存、路由,任何一类处理不好,页面都会变乱。

AI 应用又多了一层复杂度。一次用户输入不再只是触发接口请求,而可能启动意图识别、知识检索、模型生成、流式输出、工具调用、用户确认、失败重试和任务恢复。

传统交互更像:

用户点击按钮,前端发请求,接口返回结果,页面渲染反馈。

AI 交互更像:

用户提出目标,系统启动一个不确定任务;任务可能分阶段推进、中断、分叉、等待确认,最后形成一个可接受的结果。

所以 AI 应用状态管理的核心变化,不是多一个 loading,而是从“请求状态”转向“任务状态”。

难点是任务生命周期,而不是组件局部数据。

状态越清楚,用户越有控制感;状态越含糊,应用越像黑盒。


01|传统前端状态管理解决什么

传统前端状态大致有四类。

视图状态

比如当前 tab、弹窗开关、表格选中项、输入框内容、滚动位置。这类状态靠近 UI,生命周期短,通常局部 useState 就够。

服务端数据状态

比如用户信息、订单详情、搜索结果、权限数据。前端要处理请求中、成功、失败、缓存、重新请求、分页筛选和乐观更新。React Query、SWR、Apollo Client 擅长解决这一类问题。

业务流程状态

比如多步骤表单、审批、支付、上传、登录注册。它们有阶段、前置条件、失败回退、用户确认和服务端同步,比普通请求更复杂。

全局状态

比如登录态、主题、语言、当前组织、权限集合、WebSocket 连接、全局通知。难点在作用域:一旦边界不清,就会到处读、到处改、难以追踪。

这些状态并不简单,但大多数传统交互仍有一个确定前提:用户动作、请求响应和页面变化之间的关系相对清楚。AI 应用打破的正是这种确定性。


02|AI 应用新增了哪些状态

AI 应用不是在传统前端上加一个聊天框。它会引入几类新状态。

生成中

AI 生成常常是流式的,结果一边产生一边展示。前端需要区分请求是否建立、首 token 是否返回、是否仍在输出、内容是否完整、是否可引用、是否已持久化。

传统 loading 表示“还没有结果”;流式生成里,用户已经看到部分结果,但任务尚未完成。这是典型的中间态。

可取消

用户可能因为问题问错、生成方向不对、结果太长、成本太高或上下文切换而停止任务。取消时可能已有部分内容,前端要决定是否保留、如何标记、能否继续生成、后端是否真的停止,以及历史和计费如何记录。

“完成、失败、用户中断”不能混在一起。

工具调用

AI 应用不只生成文本,还可能查询数据库、检索知识库、读取文件、生成图表、创建任务、修改代码或发送邮件。

一旦有工具调用,状态就要表达:模型为何调用工具、调用了什么、参数是什么、执行是否成功、结果如何回到模型、是否需要用户确认。否则用户只能看到一个转圈和最终答案,无法判断过程是否可信。

待确认

当 AI 准备删除数据、发送邮件、修改代码、提交订单、调用付费接口或发布内容时,结果还没有真正生效,状态应该是“待确认”。

待确认要回答:建议做什么、影响范围是什么、用户能否编辑或拒绝、确认后是否可撤销、下一步进入哪个状态。动作越强,边界越要清楚。

不确定结果

AI 输出可能是草稿、建议、推测、摘要、解释、候选版本或待校验答案,不能一律当成最终事实。

可以按场景区分:

  • draft
    :草稿。
  • candidate
    :候选答案。
  • verified
    :已校验。
  • rejected
    :用户拒绝。
  • applied
    :已应用。

这不是文字游戏,而是产品责任边界。

可恢复

AI 任务失败可能发生在输入校验、检索、模型生成、工具调用、流式连接、内容审核、用户取消或前端断开等阶段。不同阶段对应不同恢复方式:原地重试、换模型、重新检索、补充信息、继续生成或重新开始。

因此错误状态至少要包含失败阶段、原因、是否可重试、从哪里重试、是否保留已生成内容,以及用户可执行动作。


03|为什么简单 loading 不够

很多 AI 应用一开始会写成这样:

type State = {    loadingboolean;    error?: string;    data?: Message[];};

Demo 阶段能跑,真实产品很快不够用。

它无法表达阶段

用户发送一句话后,系统可能要创建会话、保存用户消息、检索知识库、调用模型、流式返回、调用工具、继续生成、保存助手消息。它们都可以叫 loading,但交互完全不同。

检索慢,要告诉用户正在查资料;生成慢,要展示流式内容;工具慢,要说明正在执行什么;保存失败,应该只重试持久化,而不是重新生成全部内容。

它无法表达中断

流式生成中断后,loading 变成 false,但这不等于成功,也不等于失败。它可能是用户停止、网络断开、服务端超时、内容被拦截或页面切换。

如果只记录 loading: false,下一步操作就无从判断。

它无法表达部分结果

一篇文章生成到一半、一段 SQL 只给了结论、一次代码生成只完成文件 A,这些都不是“空”,也不是“完成”,而是“有部分结果但任务未完成”。

缺少这个状态,用户容易误用半成品。

它无法表达控制权

AI 应用里,用户需要知道能否取消、编辑、继续追问、重新生成、确认执行、撤销或保存当前结果。状态不清楚,按钮就会乱,系统也会显得不可靠。


04|从请求状态转向任务状态

AI 应用更适合用“任务”建模。一个简化状态可以是:

type AiTaskStatus =  | "idle"  | "submitted"  | "planning"  | "retrieving"  | "generating"  | "tool_calling"  | "waiting_user_confirm"  | "applying"  | "completed"  | "cancelled"  | "failed";

这些状态比 loading 复杂,但表达的信息更接近真实交互。

状态
含义
前端重点
submitted
用户输入已被接收
创建任务、保存输入、建立连接
planning
系统判断是否检索、调用工具或追问
识别规划失败和生成失败的区别
retrieving
正在检索知识或上下文
展示资料来源、空结果和降级策略
generating
正在流式生成
处理停止、滚动、Markdown 半成品
tool_calling
正在调用外部能力
展示工具、参数摘要、结果和失败原因
waiting_user_confirm
等待用户确认
区分建议和执行
applying
正在把结果应用到系统
处理保存、写入、提交失败
completed
任务完成
明确结果是否完整、保存、可追问
cancelled
任务取消
区分用户主动、系统自动、页面离开
failed
任务失败
带上失败阶段和恢复方式

失败状态建议带阶段:

type AiTaskError = {    stageAiTaskStatus;    messagestring;    retryableboolean;    recoverFrom?: "restart" | "resume" | "retry_stage" | "edit_input";};

这样前端才能判断是重新检索、重新保存、继续生成,还是让用户修改输入。


05|消息状态也要拆开

很多 AI 应用以消息列表为核心,但消息不是普通数组。一条助手消息也有生命周期:

type MessageStatus =  | "pending"  | "streaming"  | "interrupted"  | "completed"  | "failed"  | "replaced";

几个关键状态要分清:

  • pending
    :助手消息占位已出现,但还没有内容。
  • streaming
    :内容正在写入,需要处理 Markdown 半成品、自动滚动、停止按钮和复制限制。
  • interrupted
    :生成被中断,不等于失败,也不等于完成,应允许继续、重试、追问或删除。
  • completed
    :内容完整生成,但还要看是否保存、引用、校验或应用。
  • failed
    :失败要区分首 token 前、生成中途和保存失败。
  • replaced
    :重新生成后旧版本是否保留、能否切换,需要明确版本关系。

任务和消息最好分开建模:一个任务可能生成多条消息,一条消息可能有多个版本,一个任务也可能调用多个工具。全部塞进 messages 数组,后续会很难维护。


06|工具调用状态要透明,但不能吵

Agent 类产品经常展示工具调用过程,展示过少会让用户不信任,过多又会制造噪音。前端状态应支持分层展示。

普通用户只需要关键阶段:

  • 正在查找资料。
  • 正在分析文件。
  • 正在生成方案。
  • 等待你确认发送。

专业用户可以展开细节:

  • 使用了哪些检索结果。
  • 调用了哪个工具。
  • 参数摘要是什么。
  • 返回了什么错误。
  • 哪一步耗时最长。

高风险动作必须明确确认。涉及写操作、外部影响、金钱、权限和发布时,界面要说明将执行什么、影响范围、是否可撤销,以及确认后的结果。这里的状态不仅是技术状态,也是责任边界。


07|失败恢复要从一开始设计

AI 任务复杂后,失败不是一个点,而是可能发生在任何阶段。

失败阶段
常见原因
更合适的恢复
输入失败
缺少文件、知识库、关键条件或权限
让用户补充信息
检索失败
超时、空结果、知识库不可用
重试、扩大查询、切换知识库或降级回答
生成失败
模型超时、连接断开、token 限制、内容拦截
保留部分内容,允许继续或换模型
工具失败
参数错误、外部服务异常、权限不足
保留计划或参数,允许修改后重试
保存失败
内容已生成但历史未持久化
重新保存、导出、复制,刷新前提醒

不要把所有错误都变成“出错了,请重试”。阶段越清楚,恢复越精确,用户越不容易被迫从头来过。


08|一张 AI 应用状态分层表

可以把 AI 应用状态分成五层。

状态层
关注问题
常见字段
典型 UI
输入层
用户给了什么目标和约束
input、attachments、context、validation
输入框、文件列表、上下文标签
任务层
当前任务推进到哪一步
taskId、status、stage、progress、abortable
阶段提示、停止按钮、进度反馈
消息层
每条输入输出是什么状态
messageId、role、content、status、version
消息气泡、流式光标、版本切换
工具层
系统调用了什么能力
toolName、params、result、approvalStatus
工具卡片、确认面板、调用记录
恢复层
出错后还能怎么处理
errorStage、retryable、recoverAction
重试、继续、重新生成、补充信息

只有消息层,没有任务层,用户不知道系统正在做什么;只有任务层,没有恢复层,失败后只能重来;没有工具层,Agent 行为会变成黑盒;没有输入层记录,用户也难以理解答案基于什么生成。


09|一个更合理的前端状态模型

可以用一个简化模型表达 AI 任务:

type AiTask = {    idstring;    conversationIdstring;    status:    | "submitted"    | "retrieving"    | "generating"    | "tool_calling"    | "waiting_user_confirm"    | "applying"    | "completed"    | "cancelled"    | "failed";    input: {        textstring;        attachmentsAttachment[];        contextIdsstring[];    };    outputMessageId?: string;    currentToolCallId?: string;    error?: {        stagestring;        messagestring;        retryableboolean;        recoverAction?: "retry" | "resume" | "restart" | "edit_input";    };    createdAtnumber;    updatedAtnumber;};

消息和工具调用可以独立建模:

type AiMessage = {    idstring;    taskId?: string;    role"user" | "assistant" | "tool" | "system";    contentstring;    status"pending" | "streaming" | "interrupted" | "completed" | "failed";    version?: number;    references?: Reference[];};type ToolCall = {    idstring;    taskIdstring;    namestring;    status"pending" | "running" | "waiting_approval" | "succeeded" | "failed";    paramsSummarystring;    resultSummary?: string;    error?: string;};

重点不是照抄类型,而是避免用一个 messages 数组或一个 loading 统治所有阶段。任务、消息、工具、错误恢复要有边界。


10|状态越清楚,交互越稳定

状态建模会直接影响交互。

停止按钮在 retrievinggeneratingtool_calling 阶段含义不同:可能是中断查询、停止模型输出,也可能是取消外部任务。如果不分阶段,用户点了也不知道到底停了什么。

重新生成同样不是一个动作。它可能是同上下文重来、重新检索后生成、换模型、基于编辑后的输入生成,或从部分结果继续。没有阶段和上下文记录,就只能粗暴“从头再来”。

页面切换和多任务并发也依赖任务模型。AI 任务可能比页面停留时间更长,用户回来后能否恢复、旧任务是否会污染新结果,都需要任务 ID、服务端状态和可恢复机制支持。


11|状态不是越多越好

状态要清楚,但不是越细越好。判断原则是:

只有当不同状态对应不同交互、恢复方式或责任边界时,才值得拆开。

比如“模型排队中”和“模型连接中”是否要分开,取决于用户是否需要看到、失败恢复是否不同、是否影响权限或计费。若只是内部实现细节,可以不暴露到主状态。

好的状态设计,不是枚举所有过程,而是表达关键差异。


12|给前端工程师的实践建议

做 AI 应用前端时,可以按五步检查。

  1. 先画任务生命周期
    :用户发起任务后,哪些阶段可以取消、确认、重试,哪里会产生部分结果,哪里需要持久化。
  2. 把部分结果当成一等状态
    :半截回答、未完成代码、已生成未应用的建议、已执行一半的工具任务,都要有明确标识。
  3. 高风险动作必须有确认态
    :发消息、写数据库、删除文件、发布内容、修改配置、触发付款,都要区分“建议”和“执行”。
  4. 错误要带阶段
    :至少记录在哪个阶段失败、是否可重试、是否保留已有内容、用户下一步能做什么。
  5. 状态要能支持调试
    :记录输入、阶段、工具调用、检索摘要、模型返回、用户取消或确认动作、最终输出版本。

这些不是额外装饰,而是让复杂任务可追踪、可恢复。


结语:AI 前端不是多一个 loading 就够了

AI 应用会把前端带到更复杂的状态世界:任务提交、检索、生成、工具调用、等待确认、应用结果、完成、中断和恢复。

这些状态决定用户是否知道系统在做什么,能否及时停止,能否理解结果来源,能否从失败中恢复,也决定他们是否敢让 AI 执行真实动作。

前端价值不只是把模型输出渲染出来,而是把不确定任务组织成用户能理解、能控制、能恢复的产品过程。

下一篇继续讨论技术判断:

工程师如何建立自己的技术罗盘。

相关学习资料