用Pinia管理AI多会话状态

先说结论:做AI对话类应用,如果你要同时撑住"当前会话、历史会话、没发出去的草稿"这三种状态,别再用一堆零散的 ref 硬扛了,拿 Pinia 开一个 store,把会话当成一个 Map 来管,切起来是真的顺。

事情是这样的。前段时间我接了个内部活儿,给运营那帮人做一个能跟AI聊天的小工具。一开始我想得特简单,不就一个输入框一个消息列表嘛。结果第二天产品就来加需求:能不能像那种聊天软件一样,左边一栏历史会话,点哪个进哪个,而且我打了一半没发的字,切走再切回来不能丢。

行吧。一丢草稿我才意识到,这是个状态管理问题,不是 UI 问题。

数据怎么摆

我先把脑子里乱成一团的东西捋成一个结构。核心就一句话:所有会话存一个对象,再单独记一个"当前是谁"

javascript 复制代码
// stores/chat.js
import { defineStore } from 'pinia'

export const useChatStore = defineStore('chat', {
  state: () => ({
    sessions: {},        // { [id]: { id, title, messages, draft } }
    activeId: null,      // 当前激活的会话 id
  }),
  getters: {
    current: (s) => s.sessions[s.activeId] || null,
    history: (s) => Object.values(s.sessions)
      .sort((a, b) => b.updatedAt - a.updatedAt),
  },
})

current 这个 getter 是关键。组件里我永远只认 current,从来不直接去摸 sessions[xxx]。这样切会话的本质,就退化成改一个 activeId 而已。

切换:就是换个指针

javascript 复制代码
actions: {
  switchTo(id) {
    if (this.sessions[id]) this.activeId = id
  },
  newSession() {
    const id = crypto.randomUUID()
    this.sessions[id] = {
      id, title: '新对话', messages: [],
      draft: '', updatedAt: Date.now(),
    }
    this.activeId = id
  },
}

切换不搬数据,只动 activeId。消息列表组件里绑 current.messages,输入框绑 current.draftactiveId 一变,整个视图跟着换,Vue 的响应式自己就接上了。我第一次跑通的时候还愣了一下------草稿居然真的不丢了,因为它压根没存在组件里,存在会话对象身上呢。

草稿:别防抖,直接写

输入框我是这么接的:

dart 复制代码
// 组件里
const chat = useChatStore()
const draft = computed({
  get: () => chat.current?.draft ?? '',
  set: (v) => { if (chat.current) chat.current.draft = v },
})

一个 computed 的 get/set 怼上去,敲字直接落到当前会话的 draft 字段。我本来还想加个 300ms 防抖,后来一想犯不上,这又不是请求,就是改内存里一个字符串,V 一下就完事。

发送的时候把 draft 推进 messages,再清空:

javascript 复制代码
send() {
  const c = this.current
  if (!c?.draft.trim()) return
  c.messages.push({ role: 'user', content: c.draft })
  c.draft = ''
  c.updatedAt = Date.now()
}

持久化顺手做了

刷新别丢,我用 pinia-plugin-persistedstate 把整个 chat store 落到 localStorage。一行配置的事。唯一的坑是:正在请求中的 loading 态别一起持久化,不然刷新后会卡个假的"正在输入",我后来把临时态单独拎出来不入库才干净。

说点不好的。这套结构会话一多------我塞到两百多条历史的时候------Object.values 那个 sort getter 每次都全量算一遍,列表滚动有点黏。后来给 history 做了个分页截断才缓过来。所以别迷信"全存内存最爽",量上来还是得收着点。

整个 store 写完也就五十来行,但配套的对话本身才是大头:接哪个大模型、要不要挂知识库、prompt 怎么调。这部分我没自己造,用了一个零代码就能配智能体的平台,拖一拖把模型和我那份产品文档的知识库挂上,发布成一个 API,前端这边只管 store 和 UI。等于我把"AI 怎么想"外包了,自己专心管"会话怎么切"。说实话挺解放------一个前端,硬是没写后端就把这玩意儿跑起来了。

(对了,模型这块我走的是讯飞的 MaaS,现成 API 调,没自己部署算力。)

你们做多会话是怎么存草稿的?也是挂在会话对象上,还是单独开了个 draft store?评论区聊聊,我这套撑到两百条就有点喘了,想看看有没有更稳的姿势。

相关推荐
用户054324329702 小时前
Next.js接大模型流式SSE实操踩坑
人工智能
Assby2 小时前
从 Function Calling 到 MCP:理解 Agent 工具调用的底层通信机制
人工智能·后端
小星AI3 小时前
Claude Code 从入门到精通,一步到位
人工智能
后端小肥肠3 小时前
Codex + Obsidian 做人生副本视频:输入主题文案,直通剪映草稿
人工智能·aigc·agent
百度Geek说3 小时前
全链路研发智能体 ——从"体感能用"到"实际可用"的工程实践
人工智能
甲维斯4 小时前
500块的豆包,能帮我搞定这个么?!
人工智能
火山引擎开发者社区5 小时前
当 Agent 自己做 SRE:详解 ArkClaw 自动化可观测体系的工程实践
人工智能
Coffeeee7 小时前
两个例子,帮你快速理解什么是Token
人工智能·程序员·ai编程
饼干哥哥7 小时前
用AI全自动剪辑,日更 100条爆款视频——HyperFrames、Remotion、Git使用入门
人工智能·机器学习·ai编程