408 lines
11 KiB
Markdown
408 lines
11 KiB
Markdown
|
|
# 会话管理功能实现计划
|
|||
|
|
|
|||
|
|
## 需求概述
|
|||
|
|
实现一个会话管理功能store,为每个会话保存一个memory,在本地持久化保存。支持新建会话和切换到历史会话。
|
|||
|
|
|
|||
|
|
## 现有代码分析
|
|||
|
|
1. **Store结构** (`src/stores/`):使用Pinia + pinia-plugin-persistedstate进行持久化
|
|||
|
|
- `index.ts`:配置持久化插件
|
|||
|
|
- `modules/counter.ts`、`user.ts`、`routeCache.ts`:示例store
|
|||
|
|
2. **类型定义** (`src/types/chat.ts`):已有`ChatMessage`接口
|
|||
|
|
3. **API定义** (`src/api/agent/index.ts`):`StreamApiParams`包含`memory`对象(`thread`和`resource`字段)
|
|||
|
|
4. **聊天页面** (`src/pages/llm-chat/index.vue`):当前单会话实现,未使用memory参数
|
|||
|
|
|
|||
|
|
## 用户需求确认
|
|||
|
|
- **UI交互**:下拉菜单切换会话
|
|||
|
|
- **数据结构**:扩展会话结构(包含消息列表、memory对象等)
|
|||
|
|
- **页面集成**:组合式组件
|
|||
|
|
- **memory管理**:前端管理(生成thread/resource)
|
|||
|
|
- **标题生成**:自动生成(从第一条消息内容)
|
|||
|
|
|
|||
|
|
## 待解决的问题
|
|||
|
|
1. memory中`thread`和`resource`字段的具体生成规则?
|
|||
|
|
2. 会话标题自动生成的详细规则(截取字数、处理空消息等)?
|
|||
|
|
3. 是否需要会话删除、重命名功能?
|
|||
|
|
4. 持久化数据迁移和版本兼容性考虑?
|
|||
|
|
|
|||
|
|
## 详细设计
|
|||
|
|
|
|||
|
|
### 1. 类型定义
|
|||
|
|
创建 `src/types/session.ts` 定义以下接口:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
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`:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
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`:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
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`:
|
|||
|
|
|
|||
|
|
```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`:
|
|||
|
|
|
|||
|
|
主要修改:
|
|||
|
|
1. 在页面顶部添加SessionSelector组件
|
|||
|
|
2. 初始化会话管理器
|
|||
|
|
3. 修改handleSend函数以使用会话memory
|
|||
|
|
4. 消息存储从本地数组改为会话store
|
|||
|
|
|
|||
|
|
关键修改点:
|
|||
|
|
```typescript
|
|||
|
|
// 在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超出限制,未来可考虑分页或清理旧消息
|
|||
|
|
|
|||
|
|
## 实施步骤(详细)
|
|||
|
|
|
|||
|
|
1. **创建类型定义** (`src/types/session.ts`)
|
|||
|
|
- 定义SessionMemory和ChatSession接口
|
|||
|
|
|
|||
|
|
2. **实现会话store** (`src/stores/modules/chatSession.ts`)
|
|||
|
|
- 基于现有store模式
|
|||
|
|
- 包含完整的CRUD操作
|
|||
|
|
|
|||
|
|
3. **实现组合式组件** (`src/composables/useSessionManager.ts`)
|
|||
|
|
- 提供高层API给UI组件使用
|
|||
|
|
|
|||
|
|
4. **实现UI组件** (`src/components/SessionSelector.vue`)
|
|||
|
|
- 下拉菜单切换器
|
|||
|
|
- 新建会话按钮
|
|||
|
|
|
|||
|
|
5. **集成到现有页面** (`src/pages/llm-chat/index.vue`)
|
|||
|
|
- 引入SessionSelector组件
|
|||
|
|
- 修改消息处理逻辑
|
|||
|
|
- 初始化会话管理器
|
|||
|
|
|
|||
|
|
6. **测试验证**
|
|||
|
|
- 新建会话功能
|
|||
|
|
- 切换历史会话
|
|||
|
|
- memory数据持久化
|
|||
|
|
- 标题自动生成
|
|||
|
|
|
|||
|
|
7. **优化和调整**
|
|||
|
|
- 根据实际使用反馈调整UI
|
|||
|
|
- 考虑性能优化(大量消息处理)
|
|||
|
|
|
|||
|
|
## 注意事项
|
|||
|
|
1. **数据迁移**:现有单会话用户的平滑迁移
|
|||
|
|
2. **性能**:大量消息时的存储和渲染性能
|
|||
|
|
3. **用户体验**:会话切换的流畅性
|
|||
|
|
4. **错误处理**:网络错误、存储失败等情况
|
|||
|
|
5. **浏览器兼容性**:localStorage和UUID生成
|