WebAgent/doc/plans/partitioned-humming-pinwhee...

11 KiB
Raw Blame History

会话管理功能实现计划

需求概述

实现一个会话管理功能store为每个会话保存一个memory在本地持久化保存。支持新建会话和切换到历史会话。

现有代码分析

  1. Store结构 (src/stores/)使用Pinia + pinia-plugin-persistedstate进行持久化
    • index.ts:配置持久化插件
    • modules/counter.tsuser.tsrouteCache.ts示例store
  2. 类型定义 (src/types/chat.ts):已有ChatMessage接口
  3. API定义 (src/api/agent/index.ts)StreamApiParams包含memory对象(threadresource字段)
  4. 聊天页面 (src/pages/llm-chat/index.vue)当前单会话实现未使用memory参数

用户需求确认

  • UI交互:下拉菜单切换会话
  • 数据结构扩展会话结构包含消息列表、memory对象等
  • 页面集成:组合式组件
  • memory管理前端管理生成thread/resource
  • 标题生成:自动生成(从第一条消息内容)

待解决的问题

  1. memory中threadresource字段的具体生成规则?
  2. 会话标题自动生成的详细规则(截取字数、处理空消息等)?
  3. 是否需要会话删除、重命名功能?
  4. 持久化数据迁移和版本兼容性考虑?

详细设计

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

主要修改:

  1. 在页面顶部添加SessionSelector组件
  2. 初始化会话管理器
  3. 修改handleSend函数以使用会话memory
  4. 消息存储从本地数组改为会话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超出限制未来可考虑分页或清理旧消息

实施步骤(详细)

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