11 KiB
11 KiB
会话管理功能实现计划
需求概述
实现一个会话管理功能store,为每个会话保存一个memory,在本地持久化保存。支持新建会话和切换到历史会话。
现有代码分析
- Store结构 (
src/stores/):使用Pinia + pinia-plugin-persistedstate进行持久化index.ts:配置持久化插件modules/counter.ts、user.ts、routeCache.ts:示例store
- 类型定义 (
src/types/chat.ts):已有ChatMessage接口 - API定义 (
src/api/agent/index.ts):StreamApiParams包含memory对象(thread和resource字段) - 聊天页面 (
src/pages/llm-chat/index.vue):当前单会话实现,未使用memory参数
用户需求确认
- UI交互:下拉菜单切换会话
- 数据结构:扩展会话结构(包含消息列表、memory对象等)
- 页面集成:组合式组件
- memory管理:前端管理(生成thread/resource)
- 标题生成:自动生成(从第一条消息内容)
待解决的问题
- memory中
thread和resource字段的具体生成规则? - 会话标题自动生成的详细规则(截取字数、处理空消息等)?
- 是否需要会话删除、重命名功能?
- 持久化数据迁移和版本兼容性考虑?
详细设计
1. 类型定义
创建 src/types/session.ts 定义以下接口:
export interface SessionMemory {
thread: string; // 会话线程ID,前端生成(如UUID或会话ID)
resource: string; // 资源标识,前端生成(如固定值或动态标识)
}
export interface ChatSession {
id: string; // 会话唯一标识(UUID)
title: string; // 会话标题(自动生成)
createdAt: Date; // 创建时间
updatedAt: Date; // 最后更新时间
messages: ChatMessage[]; // 消息列表(引用现有ChatMessage类型)
memory: SessionMemory; // 会话memory
}
// 扩展现有ChatMessage类型(可选),添加sessionId字段
// 或者在store中通过关联关系管理
2. 会话管理Store
创建 src/stores/modules/chatSession.ts:
import { defineStore } from 'pinia'
import type { ChatSession, SessionMemory } from '@/types/session'
import type { ChatMessage } from '@/types/chat'
interface ChatSessionState {
sessions: ChatSession[] // 所有会话
currentSessionId: string | null // 当前会话ID
}
// 生成唯一ID和memory的辅助函数
const generateId = () => crypto.randomUUID()
const generateMemory = (sessionId: string): SessionMemory => ({
thread: sessionId,
resource: 'webagent_chat' // 固定资源标识,可根据需要调整
})
const generateTitle = (firstMessage: string): string => {
const maxLength = 20
return firstMessage.length > maxLength
? firstMessage.substring(0, maxLength) + '...'
: firstMessage
}
export const useChatSessionStore = defineStore('chatSession', () => {
const state = reactive<ChatSessionState>({
sessions: [],
currentSessionId: null
})
// 获取当前会话
const currentSession = computed(() =>
state.sessions.find(s => s.id === state.currentSessionId)
)
// 创建新会话
const createSession = (initialMessage?: string) => {
const sessionId = generateId()
const now = new Date()
const session: ChatSession = {
id: sessionId,
title: initialMessage ? generateTitle(initialMessage) : '新会话',
createdAt: now,
updatedAt: now,
messages: [],
memory: generateMemory(sessionId)
}
state.sessions.push(session)
state.currentSessionId = sessionId
return session
}
// 切换到指定会话
const switchSession = (sessionId: string) => {
const session = state.sessions.find(s => s.id === sessionId)
if (session) {
state.currentSessionId = sessionId
}
}
// 更新会话(添加消息、更新标题等)
const updateSession = (sessionId: string, updates: Partial<ChatSession>) => {
const index = state.sessions.findIndex(s => s.id === sessionId)
if (index !== -1) {
state.sessions[index] = {
...state.sessions[index],
...updates,
updatedAt: new Date()
}
}
}
// 添加消息到当前会话
const addMessageToCurrentSession = (message: ChatMessage) => {
const session = currentSession.value
if (session) {
const messages = [...session.messages, message]
updateSession(session.id, { messages })
// 如果是第一条消息且标题为默认值,自动生成标题
if (messages.length === 1 && session.title === '新会话') {
const title = generateTitle(message.content)
updateSession(session.id, { title })
}
}
}
// 删除会话
const deleteSession = (sessionId: string) => {
state.sessions = state.sessions.filter(s => s.id !== sessionId)
if (state.currentSessionId === sessionId) {
state.currentSessionId = state.sessions[0]?.id || null
}
}
return {
// 状态
sessions: computed(() => state.sessions),
currentSessionId: computed(() => state.currentSessionId),
currentSession,
// 方法
createSession,
switchSession,
updateSession,
addMessageToCurrentSession,
deleteSession
}
}, {
persist: true // 启用持久化
})
3. 组合式组件
创建 src/composables/useSessionManager.ts:
import { useChatSessionStore } from '@/stores/modules/chatSession'
import type { ChatMessage } from '@/types/chat'
export function useSessionManager() {
const sessionStore = useChatSessionStore()
// 初始化:如果没有会话,创建一个默认会话
const initialize = () => {
if (sessionStore.sessions.length === 0) {
sessionStore.createSession()
}
}
// 获取当前会话的memory(用于API调用)
const getCurrentSessionMemory = () => {
return sessionStore.currentSession?.memory
}
// 处理新消息:添加到当前会话,返回更新后的memory
const handleNewMessage = (message: ChatMessage) => {
sessionStore.addMessageToCurrentSession(message)
return getCurrentSessionMemory()
}
// 新建会话(带可选初始消息)
const newSession = (initialMessage?: string) => {
return sessionStore.createSession(initialMessage)
}
return {
// 状态
sessions: sessionStore.sessions,
currentSession: sessionStore.currentSession,
currentSessionId: sessionStore.currentSessionId,
// 方法
initialize,
getCurrentSessionMemory,
handleNewMessage,
newSession,
switchSession: sessionStore.switchSession,
deleteSession: sessionStore.deleteSession
}
}
4. UI组件 - 下拉菜单切换器
创建 src/components/SessionSelector.vue:
<template>
<div class="session-selector">
<van-dropdown-menu>
<van-dropdown-item
v-model="currentSessionId"
:options="sessionOptions"
@change="onSessionChange"
>
<template #title>
<span class="session-title">{{ currentSessionTitle }}</span>
</template>
</van-dropdown-item>
</van-dropdown-menu>
<van-button
size="small"
@click="handleNewSession"
class="new-session-btn"
>
新建会话
</van-button>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useSessionManager } from '@/composables/useSessionManager'
const sessionManager = useSessionManager()
const sessionOptions = computed(() =>
sessionManager.sessions.map(session => ({
text: session.title,
value: session.id
}))
)
const currentSessionId = computed({
get: () => sessionManager.currentSessionId,
set: (value) => {
if (value && value !== sessionManager.currentSessionId) {
sessionManager.switchSession(value)
}
}
})
const currentSessionTitle = computed(() =>
sessionManager.currentSession?.title || '选择会话'
)
const onSessionChange = (sessionId: string) => {
sessionManager.switchSession(sessionId)
}
const handleNewSession = () => {
sessionManager.newSession()
}
</script>
<style scoped>
.session-selector {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: white;
border-bottom: 1px solid #f0f0f0;
}
.session-title {
font-weight: 500;
}
</style>
5. 修改现有llm-chat页面
更新 src/pages/llm-chat/index.vue:
主要修改:
- 在页面顶部添加SessionSelector组件
- 初始化会话管理器
- 修改handleSend函数以使用会话memory
- 消息存储从本地数组改为会话store
关键修改点:
// 在script setup顶部引入
import { useSessionManager } from '@/composables/useSessionManager'
import SessionSelector from '@/components/SessionSelector.vue'
// 替换现有的messages状态
const sessionManager = useSessionManager()
const messages = computed(() => sessionManager.currentSession?.messages || [])
// 修改handleSend函数
function handleSend(text: string) {
if (!text.trim()) return
// 添加用户消息到会话
const userMessage: ChatMessage = {
id: Date.now(),
content: text,
sender: 'user',
timestamp: new Date(),
status: 'sent'
}
const memory = sessionManager.handleNewMessage(userMessage)
// 创建AI消息占位符
const aiMessage: ChatMessage = {
id: Date.now() + 1,
content: '',
sender: 'ai',
timestamp: new Date(),
status: 'sending'
}
sessionManager.handleNewMessage(aiMessage)
// 调用streamApi,传递memory参数
streamApi(
{
message: text,
options: {
memory // 传递当前会话的memory
}
},
// ... 其他回调保持不变
)
}
// 在模板中添加SessionSelector
<template>
<div class="bg-gray-50 flex flex-col h-screen dark:bg-gray-950">
<!-- 会话选择器 -->
<SessionSelector />
<!-- 原有的消息容器 -->
<!-- ... -->
</div>
</template>
6. API调用调整
修改 src/api/agent/index.ts 中的streamApi调用:
- 确保memory参数正确传递
- 如果memory为undefined,不传递options或传递空对象
7. 持久化考虑
- 使用现有的pinia-plugin-persistedstate机制
- 会话数据会随store自动持久化到localStorage
- 注意:大量消息可能导致localStorage超出限制,未来可考虑分页或清理旧消息
实施步骤(详细)
-
创建类型定义 (
src/types/session.ts)- 定义SessionMemory和ChatSession接口
-
实现会话store (
src/stores/modules/chatSession.ts)- 基于现有store模式
- 包含完整的CRUD操作
-
实现组合式组件 (
src/composables/useSessionManager.ts)- 提供高层API给UI组件使用
-
实现UI组件 (
src/components/SessionSelector.vue)- 下拉菜单切换器
- 新建会话按钮
-
集成到现有页面 (
src/pages/llm-chat/index.vue)- 引入SessionSelector组件
- 修改消息处理逻辑
- 初始化会话管理器
-
测试验证
- 新建会话功能
- 切换历史会话
- memory数据持久化
- 标题自动生成
-
优化和调整
- 根据实际使用反馈调整UI
- 考虑性能优化(大量消息处理)
注意事项
- 数据迁移:现有单会话用户的平滑迁移
- 性能:大量消息时的存储和渲染性能
- 用户体验:会话切换的流畅性
- 错误处理:网络错误、存储失败等情况
- 浏览器兼容性:localStorage和UUID生成