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生成 |