diff --git a/src/main/services/MemoryFileService.ts b/src/main/services/MemoryFileService.ts index 4aea44f8ea..258f7a3366 100644 --- a/src/main/services/MemoryFileService.ts +++ b/src/main/services/MemoryFileService.ts @@ -10,35 +10,48 @@ const memoryDataPath = path.join(getConfigDir(), 'memory-data.json') export class MemoryFileService { constructor() { - this.ensureMemoryFileExists() this.registerIpcHandlers() } - private async ensureMemoryFileExists() { - try { - const directory = path.dirname(memoryDataPath) - await fs.mkdir(directory, { recursive: true }) - try { - await fs.access(memoryDataPath) - } catch (error) { - // 文件不存在,创建一个空文件 - await fs.writeFile(memoryDataPath, JSON.stringify({ - memoryLists: [], - memories: [], - shortMemories: [] - }, null, 2)) - } - } catch (error) { - log.error('Failed to ensure memory file exists:', error) - } - } - private registerIpcHandlers() { // 读取记忆数据 ipcMain.handle(IpcChannel.Memory_LoadData, async () => { try { + // 确保配置目录存在 + const configDir = path.dirname(memoryDataPath) + try { + await fs.mkdir(configDir, { recursive: true }) + } catch (mkdirError) { + log.warn('Failed to create config directory, it may already exist:', mkdirError) + } + + // 检查文件是否存在 + try { + await fs.access(memoryDataPath) + } catch (accessError) { + // 文件不存在,创建默认文件 + log.info('Memory data file does not exist, creating default file') + const defaultData = { + memoryLists: [{ + id: 'default', + name: '默认列表', + isActive: true + }], + memories: [], + shortMemories: [], + analyzeModel: 'gpt-3.5-turbo', + shortMemoryAnalyzeModel: 'gpt-3.5-turbo', + vectorizeModel: 'gpt-3.5-turbo' + } + await fs.writeFile(memoryDataPath, JSON.stringify(defaultData, null, 2)) + return defaultData + } + + // 读取文件 const data = await fs.readFile(memoryDataPath, 'utf-8') - return JSON.parse(data) + const parsedData = JSON.parse(data) + log.info('Memory data loaded successfully') + return parsedData } catch (error) { log.error('Failed to load memory data:', error) return null @@ -48,7 +61,32 @@ export class MemoryFileService { // 保存记忆数据 ipcMain.handle(IpcChannel.Memory_SaveData, async (_, data) => { try { - await fs.writeFile(memoryDataPath, JSON.stringify(data, null, 2)) + // 确保配置目录存在 + const configDir = path.dirname(memoryDataPath) + try { + await fs.mkdir(configDir, { recursive: true }) + } catch (mkdirError) { + log.warn('Failed to create config directory, it may already exist:', mkdirError) + } + + // 尝试读取现有数据并合并 + let existingData = {} + try { + await fs.access(memoryDataPath) + const fileContent = await fs.readFile(memoryDataPath, 'utf-8') + existingData = JSON.parse(fileContent) + log.info('Existing memory data loaded for merging') + } catch (readError) { + log.warn('No existing memory data found or failed to read:', readError) + // 如果文件不存在或读取失败,使用空对象 + } + + // 合并数据,优先使用新数据 + const mergedData = { ...existingData, ...data } + + // 保存合并后的数据 + await fs.writeFile(memoryDataPath, JSON.stringify(mergedData, null, 2)) + log.info('Memory data saved successfully') return true } catch (error) { log.error('Failed to save memory data:', error) diff --git a/src/renderer/src/components/MemoryProvider.tsx b/src/renderer/src/components/MemoryProvider.tsx index 6df2b28b38..6d782911d1 100644 --- a/src/renderer/src/components/MemoryProvider.tsx +++ b/src/renderer/src/components/MemoryProvider.tsx @@ -41,8 +41,19 @@ const MemoryProvider: FC = ({ children }) => { // 在组件挂载时加载记忆数据 useEffect(() => { console.log('[MemoryProvider] Loading memory data from file') + // 使用Redux Thunk加载记忆数据 dispatch(loadMemoryData()) - }, []) + .then((result) => { + if (result.payload) { + console.log('[MemoryProvider] Memory data loaded successfully via Redux Thunk') + } else { + console.log('[MemoryProvider] No memory data loaded or loading failed') + } + }) + .catch(error => { + console.error('[MemoryProvider] Error loading memory data:', error) + }) + }, [dispatch]) // 当对话更新时,触发记忆分析 useEffect(() => { diff --git a/src/renderer/src/pages/settings/MemorySettings/ContextualRecommendationSettings.tsx b/src/renderer/src/pages/settings/MemorySettings/ContextualRecommendationSettings.tsx new file mode 100644 index 0000000000..ed97d5e7ed --- /dev/null +++ b/src/renderer/src/pages/settings/MemorySettings/ContextualRecommendationSettings.tsx @@ -0,0 +1,141 @@ +import { InfoCircleOutlined } from '@ant-design/icons' +import { useAppDispatch, useAppSelector } from '@renderer/store' +import { + setContextualRecommendationEnabled, + setAutoRecommendMemories, + setRecommendationThreshold, + clearCurrentRecommendations +} from '@renderer/store/memory' +import { Button, InputNumber, Slider, Switch, Tooltip } from 'antd' +import { FC } from 'react' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' + +import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..' + +const SliderContainer = styled.div` + display: flex; + align-items: center; + width: 100%; + max-width: 300px; + margin-right: 16px; +` + +const ContextualRecommendationSettings: FC = () => { + const { t } = useTranslation() + const dispatch = useAppDispatch() + + // 获取相关状态 + const contextualRecommendationEnabled = useAppSelector((state) => state.memory.contextualRecommendationEnabled) + const autoRecommendMemories = useAppSelector((state) => state.memory.autoRecommendMemories) + const recommendationThreshold = useAppSelector((state) => state.memory.recommendationThreshold) + + // 处理开关状态变化 + const handleContextualRecommendationToggle = (checked: boolean) => { + dispatch(setContextualRecommendationEnabled(checked)) + } + + const handleAutoRecommendToggle = (checked: boolean) => { + dispatch(setAutoRecommendMemories(checked)) + } + + // 处理推荐阈值变化 + const handleThresholdChange = (value: number | null) => { + if (value !== null) { + dispatch(setRecommendationThreshold(value)) + } + } + + // 清除当前推荐 + const handleClearRecommendations = () => { + dispatch(clearCurrentRecommendations()) + } + + return ( + + {t('settings.memory.contextualRecommendation.title') || '上下文感知记忆推荐'} + + {t('settings.memory.contextualRecommendation.description') || + '根据当前对话上下文智能推荐相关记忆,提高AI回复的相关性和连贯性。'} + + + + + {t('settings.memory.contextualRecommendation.enable') || '启用上下文感知记忆推荐'} + + + + + + + + + + + + {t('settings.memory.contextualRecommendation.autoRecommend') || '自动推荐记忆'} + + + + + + + + + + {t('settings.memory.contextualRecommendation.threshold') || '推荐阈值'} + + + + +
+ + + + +
+
+ + + + {t('settings.memory.contextualRecommendation.clearRecommendations') || '清除当前推荐'} + + + + + + +
+ ) +} + +export default ContextualRecommendationSettings diff --git a/src/renderer/src/pages/settings/MemorySettings/index.tsx b/src/renderer/src/pages/settings/MemorySettings/index.tsx index 2458bec4fa..cebea3d924 100644 --- a/src/renderer/src/pages/settings/MemorySettings/index.tsx +++ b/src/renderer/src/pages/settings/MemorySettings/index.tsx @@ -9,7 +9,7 @@ import { import { useTheme } from '@renderer/context/ThemeProvider' import { TopicManager } from '@renderer/hooks/useTopic' import { analyzeAndAddShortMemories, useMemoryService } from '@renderer/services/MemoryService' -import { useAppDispatch, useAppSelector } from '@renderer/store' +import store, { useAppDispatch, useAppSelector } from '@renderer/store' import { addMemory, clearMemories, @@ -41,6 +41,7 @@ import MemoryDeduplicationPanel from './MemoryDeduplicationPanel' import MemoryListManager from './MemoryListManager' import MemoryMindMap from './MemoryMindMap' import PriorityManagementSettings from './PriorityManagementSettings' +import ContextualRecommendationSettings from './ContextualRecommendationSettings' const MemorySettings: FC = () => { const { t } = useTranslation() @@ -255,13 +256,49 @@ const MemorySettings: FC = () => { } // 处理选择长期记忆分析模型 - const handleSelectModel = (modelId: string) => { + const handleSelectModel = async (modelId: string) => { dispatch(setAnalyzeModel(modelId)) + console.log('[Memory Settings] Analyze model set:', modelId) + + // 手动保存到JSON文件 + try { + const state = store.getState().memory + await window.api.memory.saveData({ + analyzeModel: modelId, + shortMemoryAnalyzeModel: state.shortMemoryAnalyzeModel, + vectorizeModel: state.vectorizeModel, + // 确保其他必要的数据也被保存 + memoryLists: state.memoryLists || [], + memories: state.memories || [], + shortMemories: state.shortMemories || [] + }) + console.log('[Memory Settings] Analyze model saved to file successfully:', modelId) + } catch (error) { + console.error('[Memory Settings] Failed to save analyze model to file:', error) + } } // 处理选择短期记忆分析模型 - const handleSelectShortMemoryModel = (modelId: string) => { + const handleSelectShortMemoryModel = async (modelId: string) => { dispatch(setShortMemoryAnalyzeModel(modelId)) + console.log('[Memory Settings] Short memory analyze model set:', modelId) + + // 手动保存到JSON文件 + try { + const state = store.getState().memory + await window.api.memory.saveData({ + analyzeModel: state.analyzeModel, + shortMemoryAnalyzeModel: modelId, + vectorizeModel: state.vectorizeModel, + // 确保其他必要的数据也被保存 + memoryLists: state.memoryLists || [], + memories: state.memories || [], + shortMemories: state.shortMemories || [] + }) + console.log('[Memory Settings] Short memory analyze model saved to file successfully:', modelId) + } catch (error) { + console.error('[Memory Settings] Failed to save short memory analyze model to file:', error) + } } // 手动触发分析 @@ -571,6 +608,8 @@ const MemorySettings: FC = () => { children: ( + + ) }, diff --git a/src/renderer/src/services/ContextualMemoryService.ts b/src/renderer/src/services/ContextualMemoryService.ts new file mode 100644 index 0000000000..f3a954d814 --- /dev/null +++ b/src/renderer/src/services/ContextualMemoryService.ts @@ -0,0 +1,530 @@ +// src/renderer/src/services/ContextualMemoryService.ts + +import store from '@renderer/store' +import { vectorService } from './VectorService' +import { addMemoryRetrievalLatency } from '@renderer/store/memory' +import { fetchGenerate } from '@renderer/services/ApiService' +import { Message } from '@renderer/types' +import { TopicManager } from '@renderer/hooks/useTopic' + +// 记忆项接口(从store/memory.ts导入) +interface Memory { + id: string + content: string + createdAt: string + source?: string + category?: string + listId: string + analyzedMessageIds?: string[] + lastMessageId?: string + topicId?: string + vector?: number[] + entities?: string[] + keywords?: string[] + importance?: number + accessCount?: number + lastAccessedAt?: string + decayFactor?: number + freshness?: number +} + +interface ShortMemory { + id: string + content: string + createdAt: string + topicId: string + analyzedMessageIds?: string[] + lastMessageId?: string + vector?: number[] + entities?: string[] + keywords?: string[] + importance?: number + accessCount?: number + lastAccessedAt?: string + decayFactor?: number + freshness?: number +} + +// 记忆推荐结果接口 +export interface MemoryRecommendation { + memory: Memory | ShortMemory + relevanceScore: number + source: 'long-term' | 'short-term' + matchReason?: string +} + +/** + * ContextualMemoryService 类负责实现上下文感知的记忆推荐和检索功能 + */ +class ContextualMemoryService { + /** + * 基于当前对话上下文推荐相关记忆 + * @param messages - 当前对话的消息列表 + * @param topicId - 当前对话的话题ID + * @param limit - 返回的最大记忆数量 + * @returns 推荐的记忆列表,按相关性排序 + */ + async getContextualMemoryRecommendations( + messages: Message[], + topicId: string, + limit: number = 5 + ): Promise { + console.log(`[ContextualMemory] Getting contextual memory recommendations for topic ${topicId}`) + + const startTime = performance.now() + + try { + // 获取当前状态 + const state = store.getState() + const memoryState = state.memory + + if (!memoryState) { + console.log('[ContextualMemory] Memory state not available') + return [] + } + + // 检查记忆功能是否激活 + if (!memoryState.isActive && !memoryState.shortMemoryActive) { + console.log('[ContextualMemory] Memory features are not active') + return [] + } + + // 获取最近的消息作为上下文 + const recentMessages = messages.slice(-5) + if (recentMessages.length === 0) { + console.log('[ContextualMemory] No recent messages available') + return [] + } + + // 构建上下文查询文本 + const contextQuery = this._buildContextQuery(recentMessages) + console.log(`[ContextualMemory] Context query: ${contextQuery}`) + + // 并行获取长期记忆和短期记忆的推荐 + const [longTermRecommendations, shortTermRecommendations] = await Promise.all([ + this._getLongTermMemoryRecommendations(contextQuery, topicId), + this._getShortTermMemoryRecommendations(contextQuery, topicId) + ]) + + // 合并并排序推荐结果 + let allRecommendations = [...longTermRecommendations, ...shortTermRecommendations] + + // 按相关性分数排序 + allRecommendations.sort((a, b) => b.relevanceScore - a.relevanceScore) + + // 限制返回数量 + const limitedRecommendations = allRecommendations.slice(0, limit) + + // 记录性能指标 + const endTime = performance.now() + const latency = endTime - startTime + store.dispatch(addMemoryRetrievalLatency(latency)) + + console.log(`[ContextualMemory] Found ${limitedRecommendations.length} recommendations in ${latency.toFixed(2)}ms`) + + return limitedRecommendations + } catch (error) { + console.error('[ContextualMemory] Error getting contextual memory recommendations:', error) + return [] + } + } + + /** + * 基于当前对话主题自动提取相关记忆 + * @param topicId - 当前对话的话题ID + * @param limit - 返回的最大记忆数量 + * @returns 与当前主题相关的记忆列表 + */ + async getTopicRelatedMemories(topicId: string, limit: number = 10): Promise { + console.log(`[ContextualMemory] Getting topic-related memories for topic ${topicId}`) + + try { + // 获取当前状态 + const state = store.getState() + const memoryState = state.memory + const messagesState = state.messages + + if (!memoryState || !messagesState) { + console.log('[ContextualMemory] Required state not available') + return [] + } + + // 获取话题信息 + // 使用TopicManager获取话题 + let topicQuery = '' + try { + const topic = await TopicManager.getTopic(topicId) + if (!topic) { + console.log(`[ContextualMemory] Topic ${topicId} not found`) + return [] + } + + // 使用话题ID作为查询 + // 注意:TopicManager.getTopic返回的类型只有id和messages属性 + topicQuery = `Topic ${topicId}` + if (!topicQuery.trim()) { + console.log('[ContextualMemory] No topic information available for query') + return [] + } + } catch (error) { + console.error(`[ContextualMemory] Error getting topic ${topicId}:`, error) + return [] + } + + console.log(`[ContextualMemory] Topic query: ${topicQuery}`) + + // 并行获取长期记忆和短期记忆的推荐 + const [longTermRecommendations, shortTermRecommendations] = await Promise.all([ + this._getLongTermMemoryRecommendations(topicQuery, topicId), + this._getShortTermMemoryRecommendations(topicQuery, topicId) + ]) + + // 合并并排序推荐结果 + let allRecommendations = [...longTermRecommendations, ...shortTermRecommendations] + + // 按相关性分数排序 + allRecommendations.sort((a, b) => b.relevanceScore - a.relevanceScore) + + // 限制返回数量 + const limitedRecommendations = allRecommendations.slice(0, limit) + + console.log(`[ContextualMemory] Found ${limitedRecommendations.length} topic-related memories`) + + return limitedRecommendations + } catch (error) { + console.error('[ContextualMemory] Error getting topic-related memories:', error) + return [] + } + } + + /** + * 使用语义搜索查找与查询相关的记忆 + * @param query - 搜索查询 + * @param limit - 返回的最大记忆数量 + * @returns 与查询相关的记忆列表 + */ + async searchMemoriesBySemantics(query: string, limit: number = 10): Promise { + console.log(`[ContextualMemory] Semantic search for: ${query}`) + + try { + // 获取当前状态 + const state = store.getState() + const memoryState = state.memory + + if (!memoryState) { + console.log('[ContextualMemory] Memory state not available') + return [] + } + + // 并行获取长期记忆和短期记忆的推荐 + const [longTermRecommendations, shortTermRecommendations] = await Promise.all([ + this._getLongTermMemoryRecommendations(query), + this._getShortTermMemoryRecommendations(query) + ]) + + // 合并并排序推荐结果 + let allRecommendations = [...longTermRecommendations, ...shortTermRecommendations] + + // 按相关性分数排序 + allRecommendations.sort((a, b) => b.relevanceScore - a.relevanceScore) + + // 限制返回数量 + const limitedRecommendations = allRecommendations.slice(0, limit) + + console.log(`[ContextualMemory] Found ${limitedRecommendations.length} memories matching query`) + + return limitedRecommendations + } catch (error) { + console.error('[ContextualMemory] Error searching memories by semantics:', error) + return [] + } + } + + /** + * 使用AI分析当前对话上下文,提取关键信息并推荐相关记忆 + * @param messages - 当前对话的消息列表 + * @param limit - 返回的最大记忆数量 + * @returns 基于AI分析的相关记忆推荐 + */ + async getAIEnhancedMemoryRecommendations(messages: Message[], limit: number = 5): Promise { + console.log('[ContextualMemory] Getting AI-enhanced memory recommendations') + + try { + // 获取当前状态 + const state = store.getState() + const memoryState = state.memory + + if (!memoryState) { + console.log('[ContextualMemory] Memory state not available') + return [] + } + + // 获取分析模型 + const analyzeModel = memoryState.analyzeModel + if (!analyzeModel) { + console.log('[ContextualMemory] No analyze model set') + return [] + } + + // 获取最近的消息作为上下文 + const recentMessages = messages.slice(-10) + if (recentMessages.length === 0) { + console.log('[ContextualMemory] No recent messages available') + return [] + } + + // 构建对话内容 + const conversation = recentMessages.map(msg => `${msg.role || 'user'}: ${msg.content || ''}`).join('\n') + + // 构建提示词 + const prompt = ` +请分析以下对话内容,提取出关键信息和主题,以便我可以找到相关的记忆。 + +请提供: +1. 对话的主要主题 +2. 用户可能关心的关键信息点 +3. 可能与此对话相关的背景知识或上下文 + +请以简洁的关键词和短语形式回答,每行一个要点,不要使用编号或项目符号。 + +对话内容: +${conversation} +` + + // 调用AI生成文本 + console.log('[ContextualMemory] Calling AI for context analysis...') + const result = await fetchGenerate({ + prompt: prompt, + content: conversation, + modelId: analyzeModel + }) + + if (!result || typeof result !== 'string' || result.trim() === '') { + console.log('[ContextualMemory] No valid result from AI analysis') + return [] + } + + // 提取关键信息 + const keyPoints = result + .split('\n') + .map(line => line.trim()) + .filter(line => line && !line.startsWith('#') && !line.startsWith('-')) + + console.log('[ContextualMemory] Extracted key points:', keyPoints) + + // 使用提取的关键信息作为查询 + const enhancedQuery = keyPoints.join(' ') + + // 获取相关记忆 + return await this.searchMemoriesBySemantics(enhancedQuery, limit) + } catch (error) { + console.error('[ContextualMemory] Error getting AI-enhanced memory recommendations:', error) + return [] + } + } + + /** + * 构建上下文查询文本 + * @param messages - 消息列表 + * @returns 构建的查询文本 + * @private + */ + private _buildContextQuery(messages: Message[]): string { + // 提取最近消息的内容 + const messageContents = messages.map(msg => msg.content || '').filter(content => content.trim() !== '') + + // 如果没有有效内容,返回空字符串 + if (messageContents.length === 0) { + return '' + } + + // 合并消息内容,最多使用最近的3条消息 + return messageContents.slice(-3).join(' ') + } + + /** + * 获取与查询相关的长期记忆推荐 + * @param query - 查询文本 + * @param topicId - 可选的话题ID,用于过滤记忆 + * @returns 长期记忆推荐列表 + * @private + */ + private async _getLongTermMemoryRecommendations( + query: string, + topicId?: string + ): Promise { + // 获取当前状态 + const state = store.getState() + const memoryState = state.memory + + // 检查长期记忆功能是否激活 + if (!memoryState || !memoryState.isActive) { + return [] + } + + // 获取所有激活的记忆列表 + const activeListIds = memoryState.memoryLists + .filter(list => list.isActive) + .map(list => list.id) + + if (activeListIds.length === 0) { + return [] + } + + // 获取激活列表中的记忆 + const memories = memoryState.memories.filter(memory => activeListIds.includes(memory.listId)) + + if (memories.length === 0) { + return [] + } + + // 使用向量服务查找相似记忆 + const results = await vectorService.findSimilarMemoriesToQuery( + query, + memories, + 20, // 获取更多结果,后续会进一步优化排序 + 0.5 // 降低阈值以获取更多潜在相关记忆 + ) + + // 转换为推荐格式 + const recommendations: MemoryRecommendation[] = results.map(result => ({ + memory: result.memory as Memory, + relevanceScore: result.similarity, + source: 'long-term', + matchReason: '语义相似' + })) + + // 应用高级排序优化 + return this._optimizeRelevanceRanking(recommendations, query, topicId) + } + + /** + * 获取与查询相关的短期记忆推荐 + * @param query - 查询文本 + * @param topicId - 可选的话题ID,用于过滤记忆 + * @returns 短期记忆推荐列表 + * @private + */ + private async _getShortTermMemoryRecommendations( + query: string, + topicId?: string + ): Promise { + // 获取当前状态 + const state = store.getState() + const memoryState = state.memory + + // 检查短期记忆功能是否激活 + if (!memoryState || !memoryState.shortMemoryActive) { + return [] + } + + // 获取短期记忆 + let shortMemories = memoryState.shortMemories + + // 如果指定了话题ID,只获取该话题的短期记忆 + if (topicId) { + shortMemories = shortMemories.filter(memory => memory.topicId === topicId) + } + + if (shortMemories.length === 0) { + return [] + } + + // 使用向量服务查找相似记忆 + const results = await vectorService.findSimilarMemoriesToQuery( + query, + shortMemories, + 20, // 获取更多结果,后续会进一步优化排序 + 0.5 // 降低阈值以获取更多潜在相关记忆 + ) + + // 转换为推荐格式 + const recommendations: MemoryRecommendation[] = results.map(result => ({ + memory: result.memory as ShortMemory, + relevanceScore: result.similarity, + source: 'short-term', + matchReason: '与当前对话相关' + })) + + // 应用高级排序优化 + return this._optimizeRelevanceRanking(recommendations, query, topicId) + } + + /** + * 优化记忆推荐的相关性排序 + * @param recommendations - 初始推荐列表 + * @param query - 查询文本 + * @param topicId - 可选的话题ID + * @returns 优化排序后的推荐列表 + * @private + */ + private _optimizeRelevanceRanking( + recommendations: MemoryRecommendation[], + query: string, + topicId?: string + ): MemoryRecommendation[] { + if (recommendations.length === 0) { + return [] + } + + // 获取当前状态 + const state = store.getState() + const memoryState = state.memory + + // 应用多因素排序优化 + return recommendations.map(rec => { + const memory = rec.memory + let adjustedScore = rec.relevanceScore + + // 1. 考虑记忆的重要性 + if (memory.importance && memoryState.priorityManagementEnabled) { + adjustedScore *= (1 + memory.importance * 0.5) // 重要性最多提升50%的分数 + } + + // 2. 考虑记忆的鲜度 + if (memory.freshness && memoryState.freshnessEnabled) { + adjustedScore *= (1 + memory.freshness * 0.3) // 鲜度最多提升30%的分数 + } + + // 3. 考虑记忆的衰减因子 + if (memory.decayFactor && memoryState.decayEnabled) { + adjustedScore *= memory.decayFactor // 直接应用衰减因子 + } + + // 4. 如果记忆与当前话题相关,提高分数 + if (topicId && memory.topicId === topicId) { + adjustedScore *= 1.2 // 提高20%的分数 + } + + // 5. 考虑访问频率,常用的记忆可能更相关 + if (memory.accessCount && memory.accessCount > 0) { + // 访问次数越多,提升越大,但有上限 + const accessBoost = Math.min(memory.accessCount / 10, 0.2) // 最多提升20% + adjustedScore *= (1 + accessBoost) + } + + // 6. 考虑关键词匹配 + if (memory.keywords && memory.keywords.length > 0) { + const queryLower = query.toLowerCase() + const keywordMatches = memory.keywords.filter(keyword => + queryLower.includes(keyword.toLowerCase()) + ).length + + if (keywordMatches > 0) { + // 关键词匹配越多,提升越大 + const keywordBoost = Math.min(keywordMatches * 0.1, 0.3) // 最多提升30% + adjustedScore *= (1 + keywordBoost) + } + } + + // 返回调整后的推荐 + return { + ...rec, + relevanceScore: adjustedScore + } + }).sort((a, b) => b.relevanceScore - a.relevanceScore) // 按调整后的分数重新排序 + } +} + +// 导出 ContextualMemoryService 的单例 +export const contextualMemoryService = new ContextualMemoryService() diff --git a/src/renderer/src/services/MemoryService.ts b/src/renderer/src/services/MemoryService.ts index dd2ce8484a..bec80e933d 100644 --- a/src/renderer/src/services/MemoryService.ts +++ b/src/renderer/src/services/MemoryService.ts @@ -16,9 +16,15 @@ import { updateMemoryPriorities, accessMemory, Memory, - saveMemoryData // <-- 添加 saveMemoryData + saveMemoryData, + updateCurrentRecommendations, + setRecommending, + clearCurrentRecommendations, + MemoryRecommendation } from '@renderer/store/memory' import { useCallback, useEffect, useRef } from 'react' // Add useRef back +import { contextualMemoryService } from './ContextualMemoryService' // Import contextual memory service +import { Message } from '@renderer/types' // Import Message type // 计算对话复杂度,用于调整分析深度 const calculateConversationComplexity = (conversation: string): 'low' | 'medium' | 'high' => { @@ -167,6 +173,162 @@ ${conversation} // This function definition is a duplicate, removing it. +/** + * 获取上下文感知的记忆推荐 + * @param messages - 当前对话的消息列表 + * @param topicId - 当前对话的话题ID + * @param limit - 返回的最大记忆数量 + * @returns 推荐的记忆列表,按相关性排序 + */ +export const getContextualMemoryRecommendations = async ( + messages: Message[], + topicId: string, + limit: number = 5 +): Promise => { + try { + // 获取当前状态 + const state = store.getState().memory + + // 检查上下文感知记忆推荐是否启用 + if (!state?.contextualRecommendationEnabled) { + console.log('[ContextualMemory] Contextual recommendation is not enabled') + return [] + } + + // 设置推荐状态 + store.dispatch(setRecommending(true)) + + // 调用上下文感知记忆服务获取推荐 + const recommendations = await contextualMemoryService.getContextualMemoryRecommendations( + messages, + topicId, + limit + ) + + // 转换为Redux状态中的推荐格式 + const memoryRecommendations: MemoryRecommendation[] = recommendations.map(rec => ({ + memoryId: rec.memory.id, + relevanceScore: rec.relevanceScore, + source: rec.source, + matchReason: rec.matchReason + })) + + // 更新Redux状态 + store.dispatch(updateCurrentRecommendations(memoryRecommendations)) + + // 重置推荐状态 + store.dispatch(setRecommending(false)) + + return memoryRecommendations + } catch (error) { + console.error('[ContextualMemory] Error getting contextual memory recommendations:', error) + store.dispatch(setRecommending(false)) + return [] + } +} + +/** + * 基于当前对话主题自动提取相关记忆 + * @param topicId - 当前对话的话题ID + * @param limit - 返回的最大记忆数量 + * @returns 与当前主题相关的记忆列表 + */ +export const getTopicRelatedMemories = async ( + topicId: string, + limit: number = 10 +): Promise => { + try { + // 获取当前状态 + const state = store.getState().memory + + // 检查上下文感知记忆推荐是否启用 + if (!state?.contextualRecommendationEnabled) { + console.log('[ContextualMemory] Contextual recommendation is not enabled') + return [] + } + + // 设置推荐状态 + store.dispatch(setRecommending(true)) + + // 调用上下文感知记忆服务获取推荐 + const recommendations = await contextualMemoryService.getTopicRelatedMemories( + topicId, + limit + ) + + // 转换为Redux状态中的推荐格式 + const memoryRecommendations: MemoryRecommendation[] = recommendations.map(rec => ({ + memoryId: rec.memory.id, + relevanceScore: rec.relevanceScore, + source: rec.source, + matchReason: rec.matchReason + })) + + // 更新Redux状态 + store.dispatch(updateCurrentRecommendations(memoryRecommendations)) + + // 重置推荐状态 + store.dispatch(setRecommending(false)) + + return memoryRecommendations + } catch (error) { + console.error('[ContextualMemory] Error getting topic-related memories:', error) + store.dispatch(setRecommending(false)) + return [] + } +} + +/** + * 使用AI分析当前对话上下文,提取关键信息并推荐相关记忆 + * @param messages - 当前对话的消息列表 + * @param limit - 返回的最大记忆数量 + * @returns 基于AI分析的相关记忆推荐 + */ +export const getAIEnhancedMemoryRecommendations = async ( + messages: Message[], + limit: number = 5 +): Promise => { + try { + // 获取当前状态 + const state = store.getState().memory + + // 检查上下文感知记忆推荐是否启用 + if (!state?.contextualRecommendationEnabled) { + console.log('[ContextualMemory] Contextual recommendation is not enabled') + return [] + } + + // 设置推荐状态 + store.dispatch(setRecommending(true)) + + // 调用上下文感知记忆服务获取推荐 + const recommendations = await contextualMemoryService.getAIEnhancedMemoryRecommendations( + messages, + limit + ) + + // 转换为Redux状态中的推荐格式 + const memoryRecommendations: MemoryRecommendation[] = recommendations.map(rec => ({ + memoryId: rec.memory.id, + relevanceScore: rec.relevanceScore, + source: rec.source, + matchReason: rec.matchReason + })) + + // 更新Redux状态 + store.dispatch(updateCurrentRecommendations(memoryRecommendations)) + + // 重置推荐状态 + store.dispatch(setRecommending(false)) + + return memoryRecommendations + } catch (error) { + console.error('[ContextualMemory] Error getting AI-enhanced memory recommendations:', error) + store.dispatch(setRecommending(false)) + return [] + } +} + // 记忆服务钩子 - 重构版 export const useMemoryService = () => { const dispatch = useAppDispatch() @@ -174,6 +336,8 @@ export const useMemoryService = () => { const isActive = useAppSelector((state) => state.memory?.isActive || false) const autoAnalyze = useAppSelector((state) => state.memory?.autoAnalyze || false) const analyzeModel = useAppSelector((state) => state.memory?.analyzeModel || null) + const contextualRecommendationEnabled = useAppSelector((state) => state.memory?.contextualRecommendationEnabled || false) + const autoRecommendMemories = useAppSelector((state) => state.memory?.autoRecommendMemories || false) // 使用 useCallback 定义分析函数,但减少依赖项 // 增加可选的 topicId 参数,允许分析指定的话题 @@ -559,8 +723,85 @@ ${newConversation} // 依赖项只包含决定是否启动定时器的设置 }, [isActive, autoAnalyze, analyzeModel]) - // 返回分析函数和记忆访问函数,以便在其他组件中使用 - return { analyzeAndAddMemories, recordMemoryAccess } + // 获取上下文感知记忆推荐 + const getContextualRecommendations = useCallback( + async (messages: Message[], topicId: string, limit: number = 5) => { + if (!contextualRecommendationEnabled) { + console.log('[ContextualMemory] Contextual recommendation is not enabled') + return [] + } + + return await getContextualMemoryRecommendations(messages, topicId, limit) + }, + [contextualRecommendationEnabled] + ) + + // 获取主题相关记忆 + const getTopicRecommendations = useCallback( + async (topicId: string, limit: number = 10) => { + if (!contextualRecommendationEnabled) { + console.log('[ContextualMemory] Contextual recommendation is not enabled') + return [] + } + + return await getTopicRelatedMemories(topicId, limit) + }, + [contextualRecommendationEnabled] + ) + + // 获取AI增强记忆推荐 + const getAIRecommendations = useCallback( + async (messages: Message[], limit: number = 5) => { + if (!contextualRecommendationEnabled) { + console.log('[ContextualMemory] Contextual recommendation is not enabled') + return [] + } + + return await getAIEnhancedMemoryRecommendations(messages, limit) + }, + [contextualRecommendationEnabled] + ) + + // 清除当前记忆推荐 + const clearRecommendations = useCallback(() => { + dispatch(clearCurrentRecommendations()) + }, [dispatch]) + + // 自动记忆推荐定时器 + useEffect(() => { + if (!contextualRecommendationEnabled || !autoRecommendMemories) { + return + } + + console.log('[ContextualMemory] Setting up auto recommendation timer...') + + // 每5分钟自动推荐一次记忆 + const intervalId = setInterval(() => { + const state = store.getState() + const currentTopicId = state.messages.currentTopic?.id + const messages = currentTopicId ? state.messages.messagesByTopic?.[currentTopicId] || [] : [] + + if (currentTopicId && messages.length > 0) { + console.log('[ContextualMemory] Auto recommendation triggered') + getContextualRecommendations(messages, currentTopicId) + } + }, 5 * 60 * 1000) // 5分钟 + + return () => { + console.log('[ContextualMemory] Clearing auto recommendation timer') + clearInterval(intervalId) + } + }, [contextualRecommendationEnabled, autoRecommendMemories, getContextualRecommendations]) + + // 返回分析函数、记忆访问函数和记忆推荐函数,以便在其他组件中使用 + return { + analyzeAndAddMemories, + recordMemoryAccess, + getContextualRecommendations, + getTopicRecommendations, + getAIRecommendations, + clearRecommendations + } } // 手动添加短记忆 @@ -722,7 +963,7 @@ ${newConversation} // 首先尝试匹配带有数字或短横线的列表项 const listItemRegex = /(?:^|\n)(?:\d+\.\s*|\-\s*)(.+?)(?=\n\d+\.\s*|\n\-\s*|\n\n|$)/gs - let match + let match: RegExpExecArray | null while ((match = listItemRegex.exec(result)) !== null) { if (match[1] && match[1].trim()) { extractedLines.push(match[1].trim()) @@ -832,13 +1073,24 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => { const state = store.getState() // Use imported store // 确保 state.memory 存在,如果不存在则提供默认值 - const { isActive, memories, memoryLists, shortMemoryActive, shortMemories, priorityManagementEnabled } = state.memory || { + const { + isActive, + memories, + memoryLists, + shortMemoryActive, + shortMemories, + priorityManagementEnabled, + contextualRecommendationEnabled, + currentRecommendations + } = state.memory || { isActive: false, memories: [], memoryLists: [], shortMemoryActive: false, shortMemories: [], - priorityManagementEnabled: false + priorityManagementEnabled: false, + contextualRecommendationEnabled: false, + currentRecommendations: [] } // 获取当前话题ID @@ -857,6 +1109,50 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => { let result = systemPrompt let hasContent = false + // 处理上下文感知记忆推荐 + if (contextualRecommendationEnabled && currentRecommendations && currentRecommendations.length > 0) { + // 获取推荐记忆的详细信息 + const recommendedMemories: Array<{content: string, source: string, reason: string}> = [] + + // 处理每个推荐记忆 + for (const recommendation of currentRecommendations) { + // 根据来源查找记忆 + let memory: any = null + if (recommendation.source === 'long-term') { + memory = memories.find(m => m.id === recommendation.memoryId) + } else if (recommendation.source === 'short-term') { + memory = shortMemories.find(m => m.id === recommendation.memoryId) + } + + if (memory) { + recommendedMemories.push({ + content: memory.content, + source: recommendation.source === 'long-term' ? '长期记忆' : '短期记忆', + reason: recommendation.matchReason || '与当前对话相关' + }) + + // 记录访问 + store.dispatch(accessMemory({ + id: memory.id, + isShortMemory: recommendation.source === 'short-term' + })) + } + } + + if (recommendedMemories.length > 0) { + // 构建推荐记忆提示词 + const recommendedMemoryPrompt = recommendedMemories + .map((memory, index) => `${index + 1}. ${memory.content} (来源: ${memory.source}, 原因: ${memory.reason})`) + .join('\n') + + console.log('[Memory] Contextual memory recommendations:', recommendedMemoryPrompt) + + // 添加推荐记忆到提示词 + result = `${result}\n\n当前对话的相关记忆(按相关性排序):\n${recommendedMemoryPrompt}` + hasContent = true + } + } + // 处理短记忆 if (shortMemoryActive && shortMemories && shortMemories.length > 0 && currentTopicId) { // 获取当前话题的短记忆 diff --git a/src/renderer/src/store/memory.ts b/src/renderer/src/store/memory.ts index 756220636d..1a5c46266c 100644 --- a/src/renderer/src/store/memory.ts +++ b/src/renderer/src/store/memory.ts @@ -76,6 +76,14 @@ export interface UserInterest { lastUpdated: string // 上次更新时间 } +// 记忆推荐结果接口 +export interface MemoryRecommendation { + memoryId: string + relevanceScore: number + source: 'long-term' | 'short-term' + matchReason?: string +} + export interface MemoryState { memoryLists: MemoryList[] // 记忆列表 memories: Memory[] // 所有记忆项 @@ -110,6 +118,14 @@ export interface MemoryState { freshnessEnabled: boolean // 是否启用记忆鲜度评估 decayRate: number // 记忆衰减速率(0-1) lastPriorityUpdate: number // 上次优先级更新时间 + + // 上下文感知记忆推荐相关 + contextualRecommendationEnabled: boolean // 是否启用上下文感知记忆推荐 + autoRecommendMemories: boolean // 是否自动推荐记忆 + recommendationThreshold: number // 推荐阈值(0-1) + currentRecommendations: MemoryRecommendation[] // 当前的记忆推荐 + isRecommending: boolean // 是否正在推荐记忆 + lastRecommendTime: number | null // 上次推荐时间 } // 创建默认记忆列表 @@ -167,7 +183,15 @@ const initialState: MemoryState = { decayEnabled: true, // 默认启用记忆衰减功能 freshnessEnabled: true, // 默认启用记忆鲜度评估 decayRate: 0.05, // 默认衰减速率,每天减少5% - lastPriorityUpdate: Date.now() // 初始化为当前时间 + lastPriorityUpdate: Date.now(), // 初始化为当前时间 + + // 上下文感知记忆推荐相关 + contextualRecommendationEnabled: true, // 默认启用上下文感知记忆推荐 + autoRecommendMemories: true, // 默认自动推荐记忆 + recommendationThreshold: 0.7, // 默认推荐阈值 + currentRecommendations: [], // 初始化空的推荐列表 + isRecommending: false, // 初始化为非推荐状态 + lastRecommendTime: null // 初始化为空 } const memorySlice = createSlice({ @@ -685,6 +709,37 @@ const memorySlice = createSlice({ memory.lastAccessedAt = now } } + }, + + // 设置上下文感知记忆推荐是否启用 + setContextualRecommendationEnabled: (state, action: PayloadAction) => { + state.contextualRecommendationEnabled = action.payload + }, + + // 设置是否自动推荐记忆 + setAutoRecommendMemories: (state, action: PayloadAction) => { + state.autoRecommendMemories = action.payload + }, + + // 设置推荐阈值 + setRecommendationThreshold: (state, action: PayloadAction) => { + state.recommendationThreshold = action.payload + }, + + // 更新当前的记忆推荐 + updateCurrentRecommendations: (state, action: PayloadAction) => { + state.currentRecommendations = action.payload + state.lastRecommendTime = Date.now() + }, + + // 设置是否正在推荐记忆 + setRecommending: (state, action: PayloadAction) => { + state.isRecommending = action.payload + }, + + // 清除当前的记忆推荐 + clearCurrentRecommendations: (state) => { + state.currentRecommendations = [] } }, extraReducers: (builder) => { @@ -695,6 +750,23 @@ const memorySlice = createSlice({ state.memoryLists = action.payload.memoryLists || state.memoryLists state.memories = action.payload.memories || state.memories state.shortMemories = action.payload.shortMemories || state.shortMemories + + // 更新模型选择 + if (action.payload.analyzeModel) { + state.analyzeModel = action.payload.analyzeModel + console.log('[Memory Reducer] Loaded analyze model:', action.payload.analyzeModel) + } + + if (action.payload.shortMemoryAnalyzeModel) { + state.shortMemoryAnalyzeModel = action.payload.shortMemoryAnalyzeModel + console.log('[Memory Reducer] Loaded short memory analyze model:', action.payload.shortMemoryAnalyzeModel) + } + + if (action.payload.vectorizeModel) { + state.vectorizeModel = action.payload.vectorizeModel + console.log('[Memory Reducer] Loaded vectorize model:', action.payload.vectorizeModel) + } + log.info('Memory data loaded into state') } }) @@ -747,7 +819,15 @@ export const { setDecayRate, updateMemoryPriorities, updateMemoryFreshness, - accessMemory + accessMemory, + + // 上下文感知记忆推荐相关的action + setContextualRecommendationEnabled, + setAutoRecommendMemories, + setRecommendationThreshold, + updateCurrentRecommendations, + setRecommending, + clearCurrentRecommendations } = memorySlice.actions // 加载记忆数据的异步 thunk