Vue3做AI对话,状态管理我从Pinia踩过来

做 AI 对话界面,状态比想象的复杂。不是一个消息数组那么简单------有正在生成的临时消息、有工具调用的中间态、有多个会话切换、有重新生成。我用 Vue3 + Pinia 做这套,前后改了好几版状态结构。这篇把踩过的坑摊开讲。

坑一:把"正在生成的消息"塞进消息数组

第一版我图方便,流式生成时直接 push 一条空消息进 messages,然后边收边改它的 content:

php 复制代码
// 反面教材
messages.value.push({ role: "assistant", content: "" });
const idx = messages.value.length - 1;
// 流式回调里
messages.value[idx].content += chunk;

问题来了:用户在生成中途切换会话,这个 idx 就指错了,新会话的最后一条被串改。多会话场景直接乱套。

改法: 把"流式中的临时消息"和"已落定的消息"分开存。

scss 复制代码
// store
const messages = ref([]);     // 已完成的
const streaming = ref(null);  // 当前正在生成的,独立一份

function appendChunk(chunk) {
  if (!streaming.value) return;
  streaming.value.content += chunk;
}

function commitStreaming() {
  if (streaming.value) {
    messages.value.push(streaming.value);
    streaming.value = null;
  }
}

渲染时把 streaming 拼在 messages 后面显示。切会话时直接 streaming.value = null 丢弃,干净利落。

坑二:多会话的状态结构

一开始我每个会话存成扁平的一个数组,切换时整个替换。后来要支持"后台还在生成、我先去看别的会话",扁平结构就不够了。改成按会话 ID 组织:

ini 复制代码
const sessions = ref({}); // { [sessionId]: { messages, streaming } }
const activeId = ref(null);

const current = computed(() => sessions.value[activeId.value]);

每个会话各自维护自己的流式状态,互不干扰。后台生成的会话也能继续往它自己的 streaming 里写。

坑三:Pinia 里放 AbortController 被响应式包了

我把中断请求用的 AbortController 直接存进了 Pinia state,结果 Pinia 把它变成响应式 proxy,controller.abort() 调用时行为诡异,偶尔 abort 不掉。

改法: 这种非数据、纯命令式的对象别放响应式 state,用 markRaw 包一下,或者干脆放在 store 外的普通 Map 里按 sessionId 存。我选了后者:

scss 复制代码
const controllers = new Map(); // 不进 Pinia

function send(sessionId, ...) {
  const ctrl = new AbortController();
  controllers.set(sessionId, ctrl);
  fetch(url, { signal: ctrl.signal, ... });
}

function stop(sessionId) {
  controllers.get(sessionId)?.abort();
  controllers.delete(sessionId);
}

坑四:重新生成要不要保留旧答案

产品要"重新生成"功能。第一版我直接覆盖旧回答,结果用户抱怨"新的还不如旧的,找不回来了"。后来我改成把同一个问题的多次回答存成数组,前端给个左右切换:

arduino 复制代码
// 一条 assistant 消息变成
{ role: "assistant", variants: ["第一次的回答", "第二次的回答"], active: 1 }

状态结构复杂了点,但体验对得起。

一点反思

我承认前两版过度纠结状态优雅,花了不少时间重构。如果重来,我会一开始就按"会话隔离 + 流式态独立"这个结构起步,少走很多弯路。状态管理这东西,AI 对话场景的特殊性就在那个"流式中间态",提前为它留位置,后面会省心。

模型那侧我接的是讯飞,模型即服务、不用自建算力,流式接口也规整,我才有余裕在前端状态结构上反复打磨。希望这几个坑能帮你少踩。

相关推荐
Summer-Bright几秒前
深度 | Agent框架大洗牌:AutoGen退场后的新秩序
人工智能·ai·语言模型·ai软件
ThsPool4 分钟前
【遥感学习整理 02】ENVI遥感图像处理基础:从数据读取到遥感信息产品
图像处理·人工智能·学习
非凸科技14 分钟前
非凸智能APP
人工智能
土星云SaturnCloud26 分钟前
抽水蓄能设备智慧运营:基于土星云边缘计算实现机组全生命周期预测性维护
服务器·人工智能·ai·边缘计算
江畔柳前堤27 分钟前
系统的本质——从LTI系统到神经网络
人工智能·深度学习·神经网络·机器学习·计算机视觉·数据挖掘·语音识别
HackTwoHub42 分钟前
轻量化AI渗透武器库|ARTEX自主攻防系统、资产梳理、漏洞挖掘、链路分析全流程智能化落地
人工智能·安全·web安全·网络安全·系统安全·网络攻击模型·安全架构
中电金信1 小时前
WAIC 2026媒体聚焦:中电金信的AI工程化思考与实践
人工智能
冬奇Lab1 小时前
码库知识库系列(03):代码 Embedding 策略——原始代码反而比注释增强更好
人工智能
冬奇Lab1 小时前
开源项目第172期:agentOS — 以库的形式给 AI Agent 一个操作系统,冷启动 92x 更快、内存 47x 更少
人工智能·开源·agent
大橙子打游戏1 小时前
make-goal:把长期 AI 任务从聊天记录里解放出来
人工智能