用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.draft。activeId 一变,整个视图跟着换,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?评论区聊聊,我这套撑到两百条就有点喘了,想看看有没有更稳的姿势。

相关推荐
武天旭6 分钟前
AI安全--AI大模型的对抗样本攻击与防御解析
人工智能·深度学习·安全·机器学习
shijuejianceliu40 分钟前
传统规则视觉与嵌入式AI智能相机的技术差异
人工智能·嵌入式硬件·视觉检测·相机
goujunwe1 小时前
GEO 内容的“可摘录性”打磨技巧:段落、表格、小标题怎么写,方便大模型提取独立答案块
大数据·人工智能
羲云网络1 小时前
GPT Image 2.6 接入指南:Flare、Sunburst 怎么选,编辑失败如何排查
人工智能·python·aigc·图像生成·acedatacloud
张张123y1 小时前
DSH 插件调试避坑:client.js 设置面板、热重载与高频报错排查表
开发语言·javascript·人工智能·开源·ecmascript·后端开发·ai agent
tellmewhoisi3 小时前
深度学习0:数学知识:正态分布4(坐标变换的缩放比例(面积微元变换))
人工智能·深度学习
布吉岛的石头4 小时前
Java 程序员第 50 阶段14:灾难性遗忘与回放
人工智能·深度学习·transformer
不吃鱼的羊4 小时前
诊断27解锁dll文件制作(Visual Stdio无法安装版)
人工智能
后端小肥肠4 小时前
漫画工坊上线用户破百:公众号漫画从需求到变现全复盘
人工智能·aigc·agent