修复了一些bug

This commit is contained in:
1600822305 2025-04-14 17:55:25 +08:00
parent 51fc167b2a
commit 370ee60537
24 changed files with 2377 additions and 584 deletions

View File

@ -157,5 +157,10 @@ export enum IpcChannel {
// Memory File Storage
Memory_LoadData = 'memory:load-data',
Memory_SaveData = 'memory:save-data'
Memory_SaveData = 'memory:save-data',
Memory_DeleteShortMemoryById = 'memory:delete-short-memory-by-id',
// Long-term Memory File Storage
LongTermMemory_LoadData = 'long-term-memory:load-data',
LongTermMemory_SaveData = 'long-term-memory:save-data'
}

View File

@ -26,6 +26,7 @@ import { searchService } from './services/SearchService'
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
import { TrayService } from './services/TrayService'
import { windowService } from './services/WindowService'
import { memoryFileService } from './services/MemoryFileService'
import { getResourcePath } from './utils'
import { decrypt, encrypt } from './utils/aes'
import { getConfigDir, getFilesDir } from './utils/file'
@ -306,4 +307,21 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
ipcMain.handle(IpcChannel.SearchWindow_OpenUrl, async (_, uid: string, url: string) => {
return await searchService.openUrlInSearchWindow(uid, url)
})
// memory
ipcMain.handle(IpcChannel.Memory_LoadData, async () => {
return await memoryFileService.loadData()
})
ipcMain.handle(IpcChannel.Memory_SaveData, async (_, data, forceOverwrite = false) => {
return await memoryFileService.saveData(data, forceOverwrite)
})
ipcMain.handle(IpcChannel.Memory_DeleteShortMemoryById, async (_, id) => {
return await memoryFileService.deleteShortMemoryById(id)
})
ipcMain.handle(IpcChannel.LongTermMemory_LoadData, async () => {
return await memoryFileService.loadLongTermData()
})
ipcMain.handle(IpcChannel.LongTermMemory_SaveData, async (_, data, forceOverwrite = false) => {
return await memoryFileService.saveLongTermData(data, forceOverwrite)
})
}

View File

@ -1,126 +1,305 @@
import { promises as fs } from 'fs'
import path from 'path'
import { getConfigDir } from '../utils/file'
import { IpcChannel } from '@shared/IpcChannel'
import { ipcMain } from 'electron'
import log from 'electron-log'
// 定义记忆文件路径
const memoryDataPath = path.join(getConfigDir(), 'memory-data.json')
// 定义长期记忆文件路径
const longTermMemoryDataPath = path.join(getConfigDir(), 'long-term-memory-data.json')
export class MemoryFileService {
constructor() {
this.registerIpcHandlers()
}
private registerIpcHandlers() {
// 读取记忆数据
ipcMain.handle(IpcChannel.Memory_LoadData, async () => {
async loadData() {
try {
// 确保配置目录存在
const configDir = path.dirname(memoryDataPath)
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')
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
await fs.mkdir(configDir, { recursive: true })
} catch (mkdirError) {
log.warn('Failed to create config directory, it may already exist:', mkdirError)
}
})
// 保存记忆数据
ipcMain.handle(IpcChannel.Memory_SaveData, async (_, data) => {
// 检查文件是否存在
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)
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
}],
shortMemories: [],
analyzeModel: 'gpt-3.5-turbo',
shortMemoryAnalyzeModel: 'gpt-3.5-turbo',
historicalContextAnalyzeModel: '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')
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
}
}
async saveData(data: any, forceOverwrite: boolean = false) {
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)
}
// 如果强制覆盖,直接使用传入的数据
if (forceOverwrite) {
log.info('Force overwrite enabled for short memory data, using provided data directly')
// 确保数据包含必要的字段
const defaultData = {
memoryLists: [],
shortMemories: [],
analyzeModel: '',
shortMemoryAnalyzeModel: '',
historicalContextAnalyzeModel: '',
vectorizeModel: ''
}
// 尝试读取现有数据并合并
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 completeData = { ...defaultData, ...data }
// 合并数据,注意数组的处理
const mergedData = { ...existingData }
// 保存数据
await fs.writeFile(memoryDataPath, JSON.stringify(completeData, null, 2))
log.info('Memory data saved successfully (force overwrite)')
return true
}
// 处理每个属性
Object.entries(data).forEach(([key, value]) => {
// 如果是数组属性,需要特殊处理
if (Array.isArray(value) && Array.isArray(mergedData[key])) {
// 对于 memories 和 shortMemories需要合并而不是覆盖
if (key === 'memories' || key === 'shortMemories') {
// 创建一个集合来跟踪已存在的记忆ID
const existingIds = new Set(mergedData[key].map(item => item.id))
// 尝试读取现有数据并合并
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)
// 如果文件不存在或读取失败,使用空对象
}
// 将新记忆添加到现有记忆中,避免重复
value.forEach(item => {
if (item.id && !existingIds.has(item.id)) {
mergedData[key].push(item)
existingIds.add(item.id)
}
})
} else {
// 其他数组属性,使用新值
mergedData[key] = value
}
// 合并数据,注意数组的处理
const mergedData = { ...existingData }
// 处理每个属性
Object.entries(data).forEach(([key, value]) => {
// 如果是数组属性,需要特殊处理
if (Array.isArray(value) && Array.isArray(mergedData[key])) {
// 对于 shortMemories 和 memories直接使用传入的数组完全替换现有的记忆
if (key === 'shortMemories' || key === 'memories') {
mergedData[key] = value
log.info(`Replacing ${key} array with provided data`)
} else {
// 非数组属性,直接使用新值
// 其他数组属性,使用新值
mergedData[key] = value
}
})
} else {
// 非数组属性,直接使用新值
mergedData[key] = value
}
})
// 保存合并后的数据
await fs.writeFile(memoryDataPath, JSON.stringify(mergedData, null, 2))
log.info('Memory data saved successfully')
// 保存合并后的数据
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)
return false
}
}
async loadLongTermData() {
try {
// 确保配置目录存在
const configDir = path.dirname(longTermMemoryDataPath)
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(longTermMemoryDataPath)
} catch (accessError) {
// 文件不存在,创建默认文件
log.info('Long-term memory data file does not exist, creating default file')
const now = new Date().toISOString()
const defaultData = {
memoryLists: [{
id: 'default',
name: '默认列表',
isActive: true,
createdAt: now,
updatedAt: now
}],
memories: [],
currentListId: 'default',
analyzeModel: 'gpt-3.5-turbo'
}
await fs.writeFile(longTermMemoryDataPath, JSON.stringify(defaultData, null, 2))
return defaultData
}
// 读取文件
const data = await fs.readFile(longTermMemoryDataPath, 'utf-8')
const parsedData = JSON.parse(data)
log.info('Long-term memory data loaded successfully')
return parsedData
} catch (error) {
log.error('Failed to load long-term memory data:', error)
return null
}
}
async saveLongTermData(data: any, forceOverwrite: boolean = false) {
try {
// 确保配置目录存在
const configDir = path.dirname(longTermMemoryDataPath)
try {
await fs.mkdir(configDir, { recursive: true })
} catch (mkdirError) {
log.warn('Failed to create config directory, it may already exist:', mkdirError)
}
// 如果强制覆盖,直接使用传入的数据
if (forceOverwrite) {
log.info('Force overwrite enabled, using provided data directly')
// 确保数据包含必要的字段
const defaultData = {
memoryLists: [],
memories: [],
currentListId: '',
analyzeModel: ''
}
// 合并默认数据和传入的数据,确保数据结构完整
const completeData = { ...defaultData, ...data }
// 保存数据
await fs.writeFile(longTermMemoryDataPath, JSON.stringify(completeData, null, 2))
log.info('Long-term memory data saved successfully (force overwrite)')
return true
} catch (error) {
log.error('Failed to save memory data:', error)
}
// 尝试读取现有数据并合并
let existingData = {}
try {
await fs.access(longTermMemoryDataPath)
const fileContent = await fs.readFile(longTermMemoryDataPath, 'utf-8')
existingData = JSON.parse(fileContent)
log.info('Existing long-term memory data loaded for merging')
} catch (readError) {
log.warn('No existing long-term memory data found or failed to read:', readError)
// 如果文件不存在或读取失败,使用空对象
}
// 合并数据,注意数组的处理
const mergedData = { ...existingData }
// 处理每个属性
Object.entries(data).forEach(([key, value]) => {
// 如果是数组属性,需要特殊处理
if (Array.isArray(value) && Array.isArray(mergedData[key])) {
// 对于 memories 和 shortMemories直接使用传入的数组完全替换现有的记忆
if (key === 'memories' || key === 'shortMemories') {
mergedData[key] = value
log.info(`Replacing ${key} array with provided data`)
} else {
// 其他数组属性,使用新值
mergedData[key] = value
}
} else {
// 非数组属性,直接使用新值
mergedData[key] = value
}
})
// 保存合并后的数据
await fs.writeFile(longTermMemoryDataPath, JSON.stringify(mergedData, null, 2))
log.info('Long-term memory data saved successfully')
return true
} catch (error) {
log.error('Failed to save long-term memory data:', error)
return false
}
}
/**
* ID的短期记忆
* @param id ID
* @returns
*/
async deleteShortMemoryById(id: string) {
try {
// 检查文件是否存在
try {
await fs.access(memoryDataPath)
} catch (accessError) {
log.error('Memory data file does not exist, cannot delete memory')
return false
}
})
// 读取文件
const fileContent = await fs.readFile(memoryDataPath, 'utf-8')
const data = JSON.parse(fileContent)
// 检查shortMemories数组是否存在
if (!data.shortMemories || !Array.isArray(data.shortMemories)) {
log.error('No shortMemories array found in memory data file')
return false
}
// 过滤掉要删除的记忆
const originalLength = data.shortMemories.length
data.shortMemories = data.shortMemories.filter((memory: any) => memory.id !== id)
// 如果长度没变,说明没有找到要删除的记忆
if (data.shortMemories.length === originalLength) {
log.warn(`Short memory with ID ${id} not found, nothing to delete`)
return false
}
// 写回文件
await fs.writeFile(memoryDataPath, JSON.stringify(data, null, 2))
log.info(`Successfully deleted short memory with ID ${id}`)
return true
} catch (error) {
log.error('Failed to delete short memory:', error)
return false
}
}
private registerIpcHandlers() {
// 注册处理函数已移至ipc.ts文件中
// 这里不需要重复注册
}
}
// 创建单例实例
// 创建并导出MemoryFileService实例
export const memoryFileService = new MemoryFileService()

View File

@ -192,7 +192,10 @@ declare global {
}
memory: {
loadData: () => Promise<any>
saveData: (data: any) => Promise<boolean>
saveData: (data: any, forceOverwrite?: boolean) => Promise<boolean>
deleteShortMemoryById: (id: string) => Promise<boolean>
loadLongTermData: () => Promise<any>
saveLongTermData: (data: any, forceOverwrite?: boolean) => Promise<boolean>
}
}
}

View File

@ -180,7 +180,10 @@ const api = {
},
memory: {
loadData: () => ipcRenderer.invoke(IpcChannel.Memory_LoadData),
saveData: (data: any) => ipcRenderer.invoke(IpcChannel.Memory_SaveData, data)
saveData: (data: any) => ipcRenderer.invoke(IpcChannel.Memory_SaveData, data),
deleteShortMemoryById: (id: string) => ipcRenderer.invoke(IpcChannel.Memory_DeleteShortMemoryById, id),
loadLongTermData: () => ipcRenderer.invoke(IpcChannel.LongTermMemory_LoadData),
saveLongTermData: (data: any, forceOverwrite: boolean = false) => ipcRenderer.invoke(IpcChannel.LongTermMemory_SaveData, data, forceOverwrite)
}
}

View File

@ -1,7 +1,27 @@
import { useMemoryService } from '@renderer/services/MemoryService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store'
import { clearShortMemories, loadMemoryData } from '@renderer/store/memory'
import {
clearShortMemories,
loadMemoryData,
loadLongTermMemoryData,
setCurrentMemoryList,
setMemoryActive,
setShortMemoryActive,
setAutoAnalyze,
setAdaptiveAnalysisEnabled,
setAnalysisFrequency,
setAnalysisDepth,
setInterestTrackingEnabled,
setMonitoringEnabled,
setPriorityManagementEnabled,
setDecayEnabled,
setFreshnessEnabled,
setDecayRate,
setContextualRecommendationEnabled,
setAutoRecommendMemories,
setRecommendationThreshold
} from '@renderer/store/memory'
import { FC, ReactNode, useEffect, useRef } from 'react'
interface MemoryProviderProps {
@ -38,20 +58,82 @@ const MemoryProvider: FC<MemoryProviderProps> = ({ children }) => {
// 添加一个 ref 来存储上次分析时的消息数量
const lastAnalyzedCountRef = useRef(0)
// 在组件挂载时加载记忆数据
// 在组件挂载时加载记忆数据和设置
useEffect(() => {
console.log('[MemoryProvider] Loading memory data from file')
// 使用Redux Thunk加载记忆数据
// 使用Redux Thunk加载短期记忆数据
dispatch(loadMemoryData())
.then((result) => {
if (result.payload) {
console.log('[MemoryProvider] Memory data loaded successfully via Redux Thunk')
console.log('[MemoryProvider] Short-term memory data loaded successfully via Redux Thunk')
// 更新所有设置
const data = result.payload
// 基本设置
if (data.isActive !== undefined) dispatch(setMemoryActive(data.isActive))
if (data.shortMemoryActive !== undefined) dispatch(setShortMemoryActive(data.shortMemoryActive))
if (data.autoAnalyze !== undefined) dispatch(setAutoAnalyze(data.autoAnalyze))
// 自适应分析相关
if (data.adaptiveAnalysisEnabled !== undefined) dispatch(setAdaptiveAnalysisEnabled(data.adaptiveAnalysisEnabled))
if (data.analysisFrequency !== undefined) dispatch(setAnalysisFrequency(data.analysisFrequency))
if (data.analysisDepth !== undefined) dispatch(setAnalysisDepth(data.analysisDepth))
// 用户关注点相关
if (data.interestTrackingEnabled !== undefined) dispatch(setInterestTrackingEnabled(data.interestTrackingEnabled))
// 性能监控相关
if (data.monitoringEnabled !== undefined) dispatch(setMonitoringEnabled(data.monitoringEnabled))
// 智能优先级与时效性管理相关
if (data.priorityManagementEnabled !== undefined) dispatch(setPriorityManagementEnabled(data.priorityManagementEnabled))
if (data.decayEnabled !== undefined) dispatch(setDecayEnabled(data.decayEnabled))
if (data.freshnessEnabled !== undefined) dispatch(setFreshnessEnabled(data.freshnessEnabled))
if (data.decayRate !== undefined) dispatch(setDecayRate(data.decayRate))
// 上下文感知记忆推荐相关
if (data.contextualRecommendationEnabled !== undefined) dispatch(setContextualRecommendationEnabled(data.contextualRecommendationEnabled))
if (data.autoRecommendMemories !== undefined) dispatch(setAutoRecommendMemories(data.autoRecommendMemories))
if (data.recommendationThreshold !== undefined) dispatch(setRecommendationThreshold(data.recommendationThreshold))
console.log('[MemoryProvider] Memory settings loaded successfully')
} else {
console.log('[MemoryProvider] No memory data loaded or loading failed')
console.log('[MemoryProvider] No short-term memory data loaded or loading failed')
}
})
.catch(error => {
console.error('[MemoryProvider] Error loading memory data:', error)
console.error('[MemoryProvider] Error loading short-term memory data:', error)
})
// 使用Redux Thunk加载长期记忆数据
dispatch(loadLongTermMemoryData())
.then((result) => {
if (result.payload) {
console.log('[MemoryProvider] Long-term memory data loaded successfully via Redux Thunk')
// 确保在长期记忆数据加载后,检查并设置当前记忆列表
setTimeout(() => {
const state = store.getState().memory
if (!state.currentListId && state.memoryLists && state.memoryLists.length > 0) {
// 先尝试找到一个isActive为true的列表
const activeList = state.memoryLists.find(list => list.isActive)
if (activeList) {
console.log('[MemoryProvider] Auto-selecting active memory list:', activeList.name)
dispatch(setCurrentMemoryList(activeList.id))
} else {
// 如果没有激活的列表,使用第一个列表
console.log('[MemoryProvider] Auto-selecting first memory list:', state.memoryLists[0].name)
dispatch(setCurrentMemoryList(state.memoryLists[0].id))
}
}
}, 500) // 添加一个小延迟,确保状态已更新
} else {
console.log('[MemoryProvider] No long-term memory data loaded or loading failed')
}
})
.catch(error => {
console.error('[MemoryProvider] Error loading long-term memory data:', error)
})
}, [dispatch])
@ -100,6 +182,43 @@ const MemoryProvider: FC<MemoryProviderProps> = ({ children }) => {
previousTopicRef.current = currentTopic || null
}, [currentTopic, shortMemoryActive, dispatch])
// 监控记忆列表变化,确保总是有一个选中的记忆列表
useEffect(() => {
// 立即检查一次
const checkAndSetMemoryList = () => {
const state = store.getState().memory
if (state.memoryLists && state.memoryLists.length > 0) {
// 如果没有选中的记忆列表,或者选中的列表不存在
if (!state.currentListId || !state.memoryLists.some(list => list.id === state.currentListId)) {
// 先尝试找到一个isActive为true的列表
const activeList = state.memoryLists.find(list => list.isActive)
if (activeList) {
console.log('[MemoryProvider] Setting active memory list:', activeList.name)
dispatch(setCurrentMemoryList(activeList.id))
} else if (state.memoryLists.length > 0) {
// 如果没有激活的列表,使用第一个列表
console.log('[MemoryProvider] Setting first memory list:', state.memoryLists[0].name)
dispatch(setCurrentMemoryList(state.memoryLists[0].id))
}
}
}
}
// 立即检查一次
checkAndSetMemoryList()
// 设置定时器每秒检查一次持续5秒
const intervalId = setInterval(checkAndSetMemoryList, 1000)
const timeoutId = setTimeout(() => {
clearInterval(intervalId)
}, 5000)
return () => {
clearInterval(intervalId)
clearTimeout(timeoutId)
}
}, [dispatch])
return <>{children}</>
}

View File

@ -5,8 +5,9 @@ import { addShortMemoryItem, analyzeAndAddShortMemories } from '@renderer/servic
import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store'
import { deleteShortMemory } from '@renderer/store/memory'
import { Button, Card, Col, Empty, Input, List, Modal, Row, Statistic, Tooltip } from 'antd'
import { useState } from 'react'
import { Button, Card, Col, Empty, Input, List, Modal, Row, Statistic, Tooltip, message } from 'antd'
import { useState, useCallback } from 'react'
import _ from 'lodash'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -47,16 +48,16 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
const [newMemoryContent, setNewMemoryContent] = useState('')
const [isAnalyzing, setIsAnalyzing] = useState(false)
// 添加新的短记忆
const handleAddMemory = () => {
// 添加新的短记忆 - 使用防抖减少频繁更新
const handleAddMemory = useCallback(_.debounce(() => {
if (newMemoryContent.trim() && topicId) {
addShortMemoryItem(newMemoryContent.trim(), topicId)
setNewMemoryContent('') // 清空输入框
}
}
}, 300), [newMemoryContent, topicId])
// 手动分析对话内容
const handleAnalyzeConversation = async () => {
// 手动分析对话内容 - 使用节流避免频繁分析操作
const handleAnalyzeConversation = useCallback(_.throttle(async () => {
if (!topicId || !shortMemoryActive) return
setIsAnalyzing(true)
@ -84,13 +85,43 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
} finally {
setIsAnalyzing(false)
}
}
}, 1000), [topicId, shortMemoryActive, t])
// 删除短记忆 - 直接删除无需确认
const handleDeleteMemory = (id: string) => {
// 直接删除记忆,无需确认对话框
// 删除短记忆 - 直接删除无需确认,使用节流避免频繁删除操作
const handleDeleteMemory = useCallback(_.throttle(async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredShortMemories = state.shortMemories.filter(memory => memory.id !== id)
// 执行删除操作
dispatch(deleteShortMemory(id))
}
// 直接使用 window.api.memory.saveData 方法保存过滤后的列表
try {
// 加载当前文件数据
const currentData = await window.api.memory.loadData()
// 替换 shortMemories 数组
const newData = {
...currentData,
shortMemories: filteredShortMemories
}
// 使用 true 参数强制覆盖文件
const result = await window.api.memory.saveData(newData, true)
if (result) {
console.log(`[ShortMemoryPopup] Successfully deleted short memory with ID ${id}`)
message.success(t('settings.memory.deleteSuccess') || '删除成功')
} else {
console.error(`[ShortMemoryPopup] Failed to delete short memory with ID ${id}`)
message.error(t('settings.memory.deleteError') || '删除失败')
}
} catch (error) {
console.error('[ShortMemoryPopup] Failed to delete short memory:', error)
message.error(t('settings.memory.deleteError') || '删除失败')
}
}, 500), [dispatch, t])
const onClose = () => {
setOpen(false)
@ -122,11 +153,11 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
<ButtonGroup>
<Button
type="primary"
onClick={handleAddMemory}
onClick={() => handleAddMemory()}
disabled={!shortMemoryActive || !newMemoryContent.trim() || !topicId}>
{t('settings.memory.addShortMemory')}
</Button>
<Button onClick={handleAnalyzeConversation} loading={isAnalyzing} disabled={!shortMemoryActive || !topicId}>
<Button onClick={() => handleAnalyzeConversation()} loading={isAnalyzing} disabled={!shortMemoryActive || !topicId}>
{t('settings.memory.analyzeConversation') || '分析对话'}
</Button>
</ButtonGroup>

View File

@ -1025,6 +1025,13 @@
"launch.title": "Launch",
"launch.totray": "Minimize to Tray on Launch",
"memory": {
"historicalContext": {
"title": "Historical Dialog Context",
"description": "Allow AI to automatically reference historical dialogs when needed, to provide more coherent answers.",
"enable": "Enable Historical Dialog Context",
"enableTip": "When enabled, AI will automatically analyze and reference historical dialogs when needed, to provide more coherent answers",
"analyzeModelTip": "Select the model used for historical dialog context analysis, it's recommended to choose a model with faster response"
},
"title": "Memory Function",
"description": "Manage AI assistant's long-term memory, automatically analyze conversations and extract important information",
"enableMemory": "Enable Memory Function",
@ -1048,6 +1055,14 @@
"startingAnalysis": "Starting analysis...",
"cannotAnalyze": "Cannot analyze, please check settings",
"resetAnalyzingState": "Reset Analysis State",
"resetLongTermMemory": "Reset Analysis Markers",
"resetLongTermMemorySuccess": "Long-term memory analysis markers reset",
"resetLongTermMemoryNoChange": "No analysis markers to reset",
"resetLongTermMemoryError": "Failed to reset long-term memory analysis markers",
"saveAllSettings": "Save All Settings",
"saveAllSettingsDescription": "Save all memory function settings to file to ensure they persist after application restart.",
"saveAllSettingsSuccess": "All settings saved successfully",
"saveAllSettingsError": "Failed to save settings",
"analyzeConversation": "Analyze Conversation",
"shortMemoryAnalysisSuccess": "Analysis Successful",
"shortMemoryAnalysisSuccessContent": "Successfully extracted and added important information to short-term memory",

View File

@ -1025,6 +1025,13 @@
"launch.title": "启动",
"launch.totray": "启动时最小化到托盘",
"memory": {
"historicalContext": {
"title": "历史对话上下文",
"description": "允许AI在需要时自动引用历史对话以提供更连贯的回答。",
"enable": "启用历史对话上下文",
"enableTip": "启用后AI会在需要时自动分析并引用历史对话以提供更连贯的回答",
"analyzeModelTip": "选择用于历史对话上下文分析的模型,建议选择响应较快的模型"
},
"title": "记忆功能",
"description": "管理AI助手的长期记忆自动分析对话并提取重要信息",
"enableMemory": "启用记忆功能",
@ -1053,6 +1060,14 @@
"startingAnalysis": "开始分析...",
"cannotAnalyze": "无法分析,请检查设置",
"resetAnalyzingState": "重置分析状态",
"resetLongTermMemory": "重置分析标记",
"resetLongTermMemorySuccess": "长期记忆分析标记已重置",
"resetLongTermMemoryNoChange": "没有需要重置的分析标记",
"resetLongTermMemoryError": "重置长期记忆分析标记失败",
"saveAllSettings": "保存所有设置",
"saveAllSettingsDescription": "将所有记忆功能的设置保存到文件中,确保应用重启后设置仍然生效。",
"saveAllSettingsSuccess": "所有设置已成功保存",
"saveAllSettingsError": "保存设置失败",
"analyzeConversation": "分析对话",
"shortMemoryAnalysisSuccess": "分析成功",
"shortMemoryAnalysisSuccessContent": "已成功提取并添加重要信息到短期记忆",
@ -1181,9 +1196,13 @@
"noCurrentTopic": "请先选择一个对话话题",
"confirmDelete": "确认删除",
"confirmDeleteContent": "确定要删除这条短期记忆吗?",
"confirmDeleteAll": "确认删除全部",
"confirmDeleteAllContent": "确定要删除该话题下的所有短期记忆吗?",
"delete": "删除",
"cancel": "取消",
"allTopics": "所有话题",
"noTopics": "没有话题"
"noTopics": "没有话题",
"shortMemoriesByTopic": "按话题分组的短期记忆"
},
"mcp": {
"actions": "操作",

View File

@ -1,61 +1,210 @@
import { DeleteOutlined } from '@ant-design/icons'
import { DeleteOutlined, ClearOutlined } from '@ant-design/icons'
import { TopicManager } from '@renderer/hooks/useTopic'
import { addShortMemoryItem } from '@renderer/services/MemoryService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store'
import { deleteShortMemory, setShortMemoryActive, ShortMemory } from '@renderer/store/memory' // Import ShortMemory from here
import { Topic } from '@renderer/types' // Remove ShortMemory import from here
import { Button, Collapse, Empty, Input, List, Switch, Tooltip, Typography } from 'antd'
import { useEffect, useState } from 'react'
import { deleteShortMemory } from '@renderer/store/memory'
import { Button, Collapse, Empty, List, Modal, Pagination, Tooltip, Typography } from 'antd'
import { useEffect, useState, useCallback, memo } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
const { Title } = Typography
// 不再需要确认对话框
// const { Panel } = Collapse // Panel is no longer used
// 定义话题和记忆的接口
interface TopicWithMemories {
topic: {
id: string
name: string
assistantId: string
createdAt: string
updatedAt: string
messages: any[]
}
memories: ShortMemory[]
currentPage?: number // 当前页码
}
const HeaderContainer = styled.div`
// 短期记忆接口
interface ShortMemory {
id: string
content: string
topicId: string
createdAt: string
updatedAt?: string // 可选属性
}
// 记忆项组件的属性
interface MemoryItemProps {
memory: ShortMemory
onDelete: (id: string) => void
t: any
index: number // 添加索引属性,用于显示序号
}
// 样式组件
const LoadingContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 200px;
width: 100%;
color: var(--color-text-2);
`
const StyledCollapse = styled(Collapse)`
width: 100%;
background-color: transparent;
border: none;
.ant-collapse-item {
margin-bottom: 8px;
border: 1px solid var(--color-border);
border-radius: 4px;
overflow: hidden;
}
.ant-collapse-header {
background-color: var(--color-bg-2);
padding: 8px 16px !important;
position: relative;
}
/* 确保折叠图标不会遮挡内容 */
.ant-collapse-expand-icon {
margin-right: 8px;
}
.ant-collapse-content {
border-top: 1px solid var(--color-border);
}
.ant-collapse-content-box {
padding: 4px 0 !important; /* 减少上下内边距保持左右为0 */
}
`
const CollapseHeader = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
width: 100%;
padding-right: 24px; /* 为删除按钮留出空间 */
/* 左侧内容区域,包含话题名称和记忆数量 */
> span {
margin-right: auto;
display: flex;
align-items: center;
}
/* 删除按钮样式 */
.ant-btn {
margin-left: 8px;
}
`
const InputContainer = styled.div`
margin-bottom: 16px;
`
const LoadingContainer = styled.div`
const MemoryCount = styled.span`
background-color: var(--color-primary);
color: white;
border-radius: 10px;
padding: 0 8px;
font-size: 12px;
margin-left: 8px;
min-width: 24px;
text-align: center;
padding: 20px 0;
display: inline-block;
z-index: 1; /* 确保计数显示在最上层 */
`
const AddButton = styled(Button)`
margin-top: 8px;
const MemoryContent = styled.div`
word-break: break-word;
font-size: 14px;
line-height: 1.6;
margin-bottom: 4px;
padding: 4px 0;
`
interface TopicWithMemories {
topic: Topic
memories: ShortMemory[]
}
const PaginationContainer = styled.div`
display: flex;
justify-content: center;
padding: 12px 0;
border-top: 1px solid var(--color-border);
`
const AnimatedListItem = styled(List.Item)`
transition: all 0.3s ease;
padding: 8px 24px; /* 增加左右内边距,减少上下内边距 */
margin: 4px 0; /* 减少上下外边距 */
border-bottom: 1px solid var(--color-border);
&:last-child {
border-bottom: none;
}
&.deleting {
opacity: 0;
transform: translateX(100%);
}
/* 增加内容区域的内边距 */
.ant-list-item-meta {
padding-left: 24px;
}
/* 调整内容区域的标题和描述文字间距 */
.ant-list-item-meta-title {
margin-bottom: 4px; /* 减少标题和描述之间的间距 */
}
.ant-list-item-meta-description {
padding-left: 4px;
}
`
// 记忆项组件
const MemoryItem = memo(({ memory, onDelete, t, index }: MemoryItemProps) => {
const [isDeleting, setIsDeleting] = useState(false);
const handleDelete = async () => {
setIsDeleting(true);
// 添加小延迟,让动画有时间播放
setTimeout(() => {
onDelete(memory.id);
}, 300);
};
return (
<AnimatedListItem
className={isDeleting ? 'deleting' : ''}
actions={[
<Tooltip title={t('settings.memory.delete')} key="delete">
<Button
icon={<DeleteOutlined />}
onClick={handleDelete}
type="text"
danger
/>
</Tooltip>
]}
>
<List.Item.Meta
title={<MemoryContent><strong>{index + 1}. </strong>{memory.content}</MemoryContent>}
description={new Date(memory.createdAt).toLocaleString()}
/>
</AnimatedListItem>
)
})
// 主组件
const CollapsibleShortMemoryManager = () => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
// 获取短记忆状态
const shortMemoryActive = useAppSelector((state) => state.memory?.shortMemoryActive || false)
// 获取短期记忆
const shortMemories = useAppSelector((state) => state.memory?.shortMemories || [])
// 获取当前话题ID
const currentTopicId = useAppSelector((state) => state.messages?.currentTopic?.id)
// 添加短记忆的状态
const [newMemoryContent, setNewMemoryContent] = useState('')
// 话题列表和话题记忆映射
const [topicsWithMemories, setTopicsWithMemories] = useState<TopicWithMemories[]>([])
// 本地状态
const [loading, setLoading] = useState(true)
const [topicsWithMemories, setTopicsWithMemories] = useState<TopicWithMemories[]>([])
const [activeKeys, setActiveKeys] = useState<string[]>([])
// 加载所有话题和对应的短期记忆
useEffect(() => {
@ -118,7 +267,8 @@ const CollapsibleShortMemoryManager = () => {
topicsMemories.push({
topic: topicInfo,
memories: topicMemories
memories: topicMemories,
currentPage: 1 // 初始化为第一页
})
}
}
@ -152,142 +302,190 @@ const CollapsibleShortMemoryManager = () => {
setTopicsWithMemories([])
setLoading(false)
}
}, [shortMemories])
}, [shortMemories.length])
// 切换短记忆功能激活状态
const handleToggleActive = (checked: boolean) => {
dispatch(setShortMemoryActive(checked))
// 处理折叠面板变化
const handleCollapseChange = (keys: string | string[]) => {
setActiveKeys(Array.isArray(keys) ? keys : [keys])
}
// 添加新的短记忆
const handleAddMemory = () => {
if (newMemoryContent.trim() && currentTopicId) {
addShortMemoryItem(newMemoryContent.trim(), currentTopicId)
setNewMemoryContent('') // 清空输入框
}
}
// 处理分页变化
const handlePageChange = useCallback((page: number, topicId: string) => {
setTopicsWithMemories(prev =>
prev.map(item =>
item.topic.id === topicId
? { ...item, currentPage: page }
: item
)
);
}, [])
// 删除话题下的所有短期记忆
const handleDeleteTopicMemories = useCallback(async (topicId: string) => {
// 显示确认对话框
Modal.confirm({
title: t('settings.memory.confirmDeleteAll'),
content: t('settings.memory.confirmDeleteAllContent'),
okText: t('settings.memory.delete'),
cancelText: t('settings.memory.cancel'),
onOk: async () => {
// 获取该话题的所有记忆
const state = store.getState().memory;
const topicMemories = state.shortMemories.filter(memory => memory.topicId === topicId);
const memoryIds = topicMemories.map(memory => memory.id);
// 过滤掉要删除的记忆
const filteredShortMemories = state.shortMemories.filter(memory => memory.topicId !== topicId);
// 更新本地状态
setTopicsWithMemories(prev => prev.filter(item => item.topic.id !== topicId));
// 更新 Redux store
for (const id of memoryIds) {
dispatch(deleteShortMemory(id));
}
// 保存到本地存储
try {
const currentData = await window.api.memory.loadData();
const newData = {
...currentData,
shortMemories: filteredShortMemories
};
const result = await window.api.memory.saveData(newData, true);
if (result) {
console.log(`[CollapsibleShortMemoryManager] Successfully deleted all memories for topic ${topicId}`);
} else {
console.error(`[CollapsibleShortMemoryManager] Failed to delete all memories for topic ${topicId}`);
}
} catch (error) {
console.error('[CollapsibleShortMemoryManager] Failed to delete all memories:', error);
}
}
});
}, [dispatch, t]);
// 删除短记忆 - 直接删除无需确认
const handleDeleteMemory = (id: string) => {
// 直接删除记忆,无需确认对话框
const handleDeleteMemory = useCallback(async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredShortMemories = state.shortMemories.filter(memory => memory.id !== id)
// 在本地更新topicsWithMemories避免触发useEffect
setTopicsWithMemories(prev => {
return prev.map(item => {
// 如果该话题包含要删除的记忆,则更新该话题的记忆列表
if (item.memories.some(memory => memory.id === id)) {
return {
...item,
memories: item.memories.filter(memory => memory.id !== id)
}
}
return item
}).filter(item => item.memories.length > 0) // 移除没有记忆的话题
})
// 执行删除操作
dispatch(deleteShortMemory(id))
}
// 直接使用 window.api.memory.saveData 方法保存过滤后的列表
try {
// 加载当前文件数据
const currentData = await window.api.memory.loadData()
// 替换 shortMemories 数组
const newData = {
...currentData,
shortMemories: filteredShortMemories
}
// 使用 true 参数强制覆盖文件
const result = await window.api.memory.saveData(newData, true)
if (result) {
console.log(`[CollapsibleShortMemoryManager] Successfully deleted short memory with ID ${id}`)
// 使用App组件而不是静态方法避免触发重新渲染
// message.success(t('settings.memory.deleteSuccess') || '删除成功')
} else {
console.error(`[CollapsibleShortMemoryManager] Failed to delete short memory with ID ${id}`)
// message.error(t('settings.memory.deleteError') || '删除失败')
}
} catch (error) {
console.error('[CollapsibleShortMemoryManager] Failed to delete short memory:', error)
// message.error(t('settings.memory.deleteError') || '删除失败')
}
}, [dispatch])
return (
<div className="short-memory-manager">
<HeaderContainer>
<Title level={4}>{t('settings.memory.shortMemory')}</Title>
<Tooltip title={t('settings.memory.toggleShortMemoryActive')}>
<Switch checked={shortMemoryActive} onChange={handleToggleActive} />
</Tooltip>
</HeaderContainer>
<div>
<Typography.Title level={4}>{t('settings.memory.shortMemoriesByTopic') || '按话题分组的短期记忆'}</Typography.Title>
<InputContainer>
<Input.TextArea
value={newMemoryContent}
onChange={(e) => setNewMemoryContent(e.target.value)}
placeholder={t('settings.memory.addShortMemoryPlaceholder')}
autoSize={{ minRows: 2, maxRows: 4 }}
disabled={!shortMemoryActive || !currentTopicId}
/>
<AddButton
type="primary"
onClick={handleAddMemory}
disabled={!shortMemoryActive || !newMemoryContent.trim() || !currentTopicId}>
{t('settings.memory.addShortMemory')}
</AddButton>
</InputContainer>
<div className="short-memories-list">
{loading ? (
<LoadingContainer>{t('settings.memory.loading') || '加载中...'}</LoadingContainer>
) : topicsWithMemories.length > 0 ? (
<StyledCollapse
defaultActiveKey={[currentTopicId || '']}
items={topicsWithMemories.map(({ topic, memories }) => ({
key: topic.id,
label: (
<CollapseHeader>
<span>{topic.name}</span>
{loading ? (
<LoadingContainer>{t('settings.memory.loading') || '加载中...'}</LoadingContainer>
) : topicsWithMemories.length > 0 ? (
<StyledCollapse
activeKey={activeKeys}
onChange={handleCollapseChange}
items={topicsWithMemories.map(({ topic, memories, currentPage }) => ({
key: topic.id,
label: (
<CollapseHeader>
<span>
{topic.name}
<MemoryCount>{memories.length}</MemoryCount>
</CollapseHeader>
),
children: (
</span>
<Tooltip title={t('settings.memory.confirmDeleteAll')}>
<Button
icon={<ClearOutlined />}
onClick={(e) => {
e.stopPropagation(); // 阻止事件冒泡,避免触发折叠面板的展开/收起
handleDeleteTopicMemories(topic.id);
}}
type="text"
danger
size="small"
/>
</Tooltip>
</CollapseHeader>
),
children: (
<div>
<List
itemLayout="horizontal"
dataSource={memories}
renderItem={(memory) => (
<List.Item
actions={[
<Tooltip title={t('settings.memory.delete')} key="delete">
<Button
icon={<DeleteOutlined />}
onClick={() => handleDeleteMemory(memory.id)}
type="text"
danger
/>
</Tooltip>
]}>
<List.Item.Meta
title={<MemoryContent>{memory.content}</MemoryContent>}
description={new Date(memory.createdAt).toLocaleString()}
/>
</List.Item>
dataSource={memories.slice((currentPage ? currentPage - 1 : 0) * 15, (currentPage ? currentPage - 1 : 0) * 15 + 15)}
style={{ padding: '4px 0' }}
renderItem={(memory, index) => (
<MemoryItem
key={memory.id}
memory={memory}
onDelete={handleDeleteMemory}
t={t}
index={(currentPage ? currentPage - 1 : 0) * 15 + index}
/>
)}
/>
)
}))}
/>
) : (
<Empty
description={!currentTopicId ? t('settings.memory.noCurrentTopic') : t('settings.memory.noShortMemories')}
/>
)}
</div>
{memories.length > 15 && (
<PaginationContainer>
<Pagination
current={currentPage || 1}
onChange={(page) => handlePageChange(page, topic.id)}
total={memories.length}
pageSize={15}
size="small"
showSizeChanger={false}
/>
</PaginationContainer>
)}
</div>
)
}))}
/>
) : (
<Empty description={t('settings.memory.noShortMemories') || '没有短期记忆'} />
)}
</div>
)
}
const StyledCollapse = styled(Collapse)`
background-color: transparent;
border: none;
.ant-collapse-item {
border: 1px solid var(--color-border);
border-radius: 8px !important;
margin-bottom: 8px;
overflow: hidden;
}
.ant-collapse-header {
background-color: var(--color-background-soft);
padding: 8px 16px !important;
}
.ant-collapse-content {
border-top: 1px solid var(--color-border);
}
`
const CollapseHeader = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
`
const MemoryCount = styled.span`
background-color: var(--color-primary);
color: white;
border-radius: 10px;
padding: 0 8px;
font-size: 12px;
min-width: 20px;
text-align: center;
`
const MemoryContent = styled.div`
word-break: break-word;
`
export default CollapsibleShortMemoryManager

View File

@ -1,6 +1,7 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import {
saveMemoryData,
setContextualRecommendationEnabled,
setAutoRecommendMemories,
setRecommendationThreshold,
@ -24,73 +25,97 @@ const SliderContainer = styled.div`
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) => {
const handleContextualRecommendationToggle = async (checked: boolean) => {
dispatch(setContextualRecommendationEnabled(checked))
}
const handleAutoRecommendToggle = (checked: boolean) => {
dispatch(setAutoRecommendMemories(checked))
}
// 处理推荐阈值变化
const handleThresholdChange = (value: number | null) => {
if (value !== null) {
dispatch(setRecommendationThreshold(value))
// 保存设置
try {
await dispatch(saveMemoryData({ contextualRecommendationEnabled: checked })).unwrap()
console.log('[ContextualRecommendationSettings] Contextual recommendation enabled setting saved:', checked)
} catch (error) {
console.error('[ContextualRecommendationSettings] Failed to save contextual recommendation enabled setting:', error)
}
}
const handleAutoRecommendToggle = async (checked: boolean) => {
dispatch(setAutoRecommendMemories(checked))
// 保存设置
try {
await dispatch(saveMemoryData({ autoRecommendMemories: checked })).unwrap()
console.log('[ContextualRecommendationSettings] Auto recommend memories setting saved:', checked)
} catch (error) {
console.error('[ContextualRecommendationSettings] Failed to save auto recommend memories setting:', error)
}
}
// 处理推荐阈值变化
const handleThresholdChange = async (value: number | null) => {
if (value !== null) {
dispatch(setRecommendationThreshold(value))
// 保存设置
try {
await dispatch(saveMemoryData({ recommendationThreshold: value })).unwrap()
console.log('[ContextualRecommendationSettings] Recommendation threshold setting saved:', value)
} catch (error) {
console.error('[ContextualRecommendationSettings] Failed to save recommendation threshold setting:', error)
}
}
}
// 清除当前推荐
const handleClearRecommendations = () => {
dispatch(clearCurrentRecommendations())
}
return (
<SettingGroup>
<SettingTitle>{t('settings.memory.contextualRecommendation.title') || '上下文感知记忆推荐'}</SettingTitle>
<SettingHelpText>
{t('settings.memory.contextualRecommendation.description') ||
{t('settings.memory.contextualRecommendation.description') ||
'根据当前对话上下文智能推荐相关记忆提高AI回复的相关性和连贯性。'}
</SettingHelpText>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.contextualRecommendation.enable') || '启用上下文感知记忆推荐'}
<Tooltip title={t('settings.memory.contextualRecommendation.enableTip') ||
<Tooltip title={t('settings.memory.contextualRecommendation.enableTip') ||
'启用后,系统将根据当前对话上下文自动推荐相关记忆'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch checked={contextualRecommendationEnabled} onChange={handleContextualRecommendationToggle} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>
{t('settings.memory.contextualRecommendation.autoRecommend') || '自动推荐记忆'}
<Tooltip title={t('settings.memory.contextualRecommendation.autoRecommendTip') ||
<Tooltip title={t('settings.memory.contextualRecommendation.autoRecommendTip') ||
'启用后,系统将定期自动分析当前对话并推荐相关记忆'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch
checked={autoRecommendMemories}
onChange={handleAutoRecommendToggle}
disabled={!contextualRecommendationEnabled}
<Switch
checked={autoRecommendMemories}
onChange={handleAutoRecommendToggle}
disabled={!contextualRecommendationEnabled}
/>
</SettingRow>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.contextualRecommendation.threshold') || '推荐阈值'}
<Tooltip title={t('settings.memory.contextualRecommendation.thresholdTip') ||
<Tooltip title={t('settings.memory.contextualRecommendation.thresholdTip') ||
'设置记忆推荐的相似度阈值,值越高要求越严格'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
@ -118,17 +143,17 @@ const ContextualRecommendationSettings: FC = () => {
/>
</div>
</SettingRow>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.contextualRecommendation.clearRecommendations') || '清除当前推荐'}
<Tooltip title={t('settings.memory.contextualRecommendation.clearRecommendationsTip') ||
<Tooltip title={t('settings.memory.contextualRecommendation.clearRecommendationsTip') ||
'清除当前的记忆推荐列表'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Button
onClick={handleClearRecommendations}
<Button
onClick={handleClearRecommendations}
disabled={!contextualRecommendationEnabled}
>
{t('settings.memory.contextualRecommendation.clear') || '清除'}

View File

@ -0,0 +1,111 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import { useProviders } from '@renderer/hooks/useProvider'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { saveMemoryData, setHistoricalContextAnalyzeModel } from '@renderer/store/memory'
import { setEnableHistoricalContext } from '@renderer/store/settings'
import { Button, Switch, Tooltip } from 'antd'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
const HistoricalContextSettings: FC = () => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const { providers } = useProviders()
// 获取相关状态
const enableHistoricalContext = useAppSelector((state) => state.settings.enableHistoricalContext)
const historicalContextAnalyzeModel = useAppSelector((state) => state.memory.historicalContextAnalyzeModel)
// 处理开关状态变化
const handleHistoricalContextToggle = (checked: boolean) => {
dispatch(setEnableHistoricalContext(checked))
}
// 处理模型选择变化
const handleModelChange = async (modelId: string) => {
dispatch(setHistoricalContextAnalyzeModel(modelId))
console.log('[HistoricalContextSettings] Historical context analyze model set:', modelId)
// 使用Redux Thunk保存到JSON文件
try {
await dispatch(saveMemoryData({ historicalContextAnalyzeModel: modelId })).unwrap()
console.log('[HistoricalContextSettings] Historical context analyze model saved to file successfully:', modelId)
} catch (error) {
console.error('[HistoricalContextSettings] Failed to save historical context analyze model to file:', error)
}
}
// 获取当前选中模型的名称
const getSelectedModelName = () => {
if (!historicalContextAnalyzeModel) return ''
// 遍历所有服务商的模型找到匹配的模型
for (const provider of Object.values(providers)) {
const model = provider.models.find(m => m.id === historicalContextAnalyzeModel)
if (model) {
return `${model.name} | ${provider.name}`
}
}
return historicalContextAnalyzeModel
}
return (
<SettingGroup>
<SettingTitle>{t('settings.memory.historicalContext.title') || '历史对话上下文'}</SettingTitle>
<SettingHelpText>
{t('settings.memory.historicalContext.description') ||
'允许AI在需要时自动引用历史对话以提供更连贯的回答。'}
</SettingHelpText>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.historicalContext.enable') || '启用历史对话上下文'}
<Tooltip title={t('settings.memory.historicalContext.enableTip') ||
'启用后AI会在需要时自动分析并引用历史对话以提供更连贯的回答'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch checked={enableHistoricalContext} onChange={handleHistoricalContextToggle} />
</SettingRow>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.analyzeModel') || '分析模型'}
<Tooltip title={t('settings.memory.historicalContext.analyzeModelTip') ||
'选择用于历史对话上下文分析的模型,建议选择响应较快的模型'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Button
onClick={async () => {
// 找到当前选中的模型对象
let currentModel: { id: string; provider: string; name: string; group: string } | undefined
if (historicalContextAnalyzeModel) {
for (const provider of Object.values(providers)) {
const model = provider.models.find(m => m.id === historicalContextAnalyzeModel)
if (model) {
currentModel = model
break
}
}
}
const selectedModel = await SelectModelPopup.show({ model: currentModel })
if (selectedModel) {
handleModelChange(selectedModel.id)
}
}}
style={{ width: 300 }}
>
{historicalContextAnalyzeModel ? getSelectedModelName() : t('settings.memory.selectModel') || '选择模型'}
</Button>
</SettingRow>
</SettingGroup>
)
}
export default HistoricalContextSettings

View File

@ -144,19 +144,27 @@ const MemoryDeduplicationPanel: React.FC<MemoryDeduplicationPanelProps> = ({
Modal.confirm({
title: t(`${translationPrefix}.confirmApply`),
content: t(`${translationPrefix}.confirmApplyContent`),
onOk: () => {
if (applyResults) {
// 使用自定义的应用函数
applyResults(deduplicationResult)
} else {
// 使用默认的应用函数
applyDeduplicationResult(deduplicationResult, true, isShortMemory)
onOk: async () => {
try {
if (applyResults) {
// 使用自定义的应用函数
applyResults(deduplicationResult)
} else {
// 使用默认的应用函数
await applyDeduplicationResult(deduplicationResult, true, isShortMemory)
}
setDeduplicationResult(null)
Modal.success({
title: t(`${translationPrefix}.applySuccess`),
content: t(`${translationPrefix}.applySuccessContent`)
})
} catch (error) {
console.error('[Memory Deduplication Panel] Error applying deduplication result:', error)
Modal.error({
title: t(`${translationPrefix}.applyError`) || '应用失败',
content: t(`${translationPrefix}.applyErrorContent`) || '应用去重结果时发生错误,请重试'
})
}
setDeduplicationResult(null)
Modal.success({
title: t(`${translationPrefix}.applySuccess`),
content: t(`${translationPrefix}.applySuccessContent`)
})
}
})
}

View File

@ -1,12 +1,14 @@
import { DeleteOutlined, EditOutlined, ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store'
import {
addMemoryList,
deleteMemoryList,
editMemoryList,
MemoryList,
setCurrentMemoryList,
toggleMemoryListActive
toggleMemoryListActive,
saveLongTermMemoryData
} from '@renderer/store/memory'
import { Button, Empty, Input, List, Modal, Switch, Tooltip, Typography } from 'antd'
import React, { useState } from 'react'
@ -46,14 +48,14 @@ const MemoryListManager: React.FC<MemoryListManagerProps> = ({ onSelectList }) =
}
// 处理模态框确认
const handleOk = () => {
const handleOk = async () => {
if (!newListName.trim()) {
return // 名称不能为空
}
if (editingList) {
// 编辑现有列表
dispatch(
await dispatch(
editMemoryList({
id: editingList.id,
name: newListName,
@ -62,7 +64,7 @@ const MemoryListManager: React.FC<MemoryListManagerProps> = ({ onSelectList }) =
)
} else {
// 添加新列表
dispatch(
await dispatch(
addMemoryList({
name: newListName,
description: newListDescription,
@ -71,6 +73,18 @@ const MemoryListManager: React.FC<MemoryListManagerProps> = ({ onSelectList }) =
)
}
// 保存到长期记忆文件
try {
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memoryLists: state.memoryLists,
currentListId: state.currentListId
})).unwrap()
console.log('[MemoryListManager] Memory lists saved to file after edit')
} catch (error) {
console.error('[MemoryListManager] Failed to save memory lists after edit:', error)
}
setIsModalVisible(false)
setNewListName('')
setNewListDescription('')
@ -94,23 +108,59 @@ const MemoryListManager: React.FC<MemoryListManagerProps> = ({ onSelectList }) =
okText: t('common.delete'),
okType: 'danger',
cancelText: t('common.cancel'),
onOk() {
async onOk() {
dispatch(deleteMemoryList(list.id))
// 保存到长期记忆文件
try {
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memoryLists: state.memoryLists,
currentListId: state.currentListId
})).unwrap()
console.log('[MemoryListManager] Memory lists saved to file after delete')
} catch (error) {
console.error('[MemoryListManager] Failed to save memory lists after delete:', error)
}
}
})
}
// 切换列表激活状态
const handleToggleActive = (list: MemoryList, checked: boolean) => {
const handleToggleActive = async (list: MemoryList, checked: boolean) => {
dispatch(toggleMemoryListActive({ id: list.id, isActive: checked }))
// 保存到长期记忆文件
try {
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memoryLists: state.memoryLists,
currentListId: state.currentListId
})).unwrap()
console.log('[MemoryListManager] Memory lists saved to file after toggle active')
} catch (error) {
console.error('[MemoryListManager] Failed to save memory lists after toggle active:', error)
}
}
// 选择列表
const handleSelectList = (listId: string) => {
const handleSelectList = async (listId: string) => {
dispatch(setCurrentMemoryList(listId))
if (onSelectList) {
onSelectList(listId)
}
// 保存到长期记忆文件
try {
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memoryLists: state.memoryLists,
currentListId: state.currentListId
})).unwrap()
console.log('[MemoryListManager] Memory lists saved to file after select list')
} catch (error) {
console.error('[MemoryListManager] Failed to save memory lists after select list:', error)
}
}
return (

View File

@ -1,6 +1,7 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import {
saveMemoryData,
setDecayEnabled,
setDecayRate,
setFreshnessEnabled,
@ -25,78 +26,110 @@ const SliderContainer = styled.div`
const PriorityManagementSettings: FC = () => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
// 获取相关状态
const priorityManagementEnabled = useAppSelector((state) => state.memory.priorityManagementEnabled)
const decayEnabled = useAppSelector((state) => state.memory.decayEnabled)
const freshnessEnabled = useAppSelector((state) => state.memory.freshnessEnabled)
const decayRate = useAppSelector((state) => state.memory.decayRate)
// 处理开关状态变化
const handlePriorityManagementToggle = (checked: boolean) => {
const handlePriorityManagementToggle = async (checked: boolean) => {
dispatch(setPriorityManagementEnabled(checked))
}
const handleDecayToggle = (checked: boolean) => {
dispatch(setDecayEnabled(checked))
}
const handleFreshnessToggle = (checked: boolean) => {
dispatch(setFreshnessEnabled(checked))
}
// 处理衰减率变化
const handleDecayRateChange = (value: number | null) => {
if (value !== null) {
dispatch(setDecayRate(value))
// 保存设置
try {
await dispatch(saveMemoryData({ priorityManagementEnabled: checked })).unwrap()
console.log('[PriorityManagementSettings] Priority management enabled setting saved:', checked)
} catch (error) {
console.error('[PriorityManagementSettings] Failed to save priority management enabled setting:', error)
}
}
const handleDecayToggle = async (checked: boolean) => {
dispatch(setDecayEnabled(checked))
// 保存设置
try {
await dispatch(saveMemoryData({ decayEnabled: checked })).unwrap()
console.log('[PriorityManagementSettings] Decay enabled setting saved:', checked)
} catch (error) {
console.error('[PriorityManagementSettings] Failed to save decay enabled setting:', error)
}
}
const handleFreshnessToggle = async (checked: boolean) => {
dispatch(setFreshnessEnabled(checked))
// 保存设置
try {
await dispatch(saveMemoryData({ freshnessEnabled: checked })).unwrap()
console.log('[PriorityManagementSettings] Freshness enabled setting saved:', checked)
} catch (error) {
console.error('[PriorityManagementSettings] Failed to save freshness enabled setting:', error)
}
}
// 处理衰减率变化
const handleDecayRateChange = async (value: number | null) => {
if (value !== null) {
dispatch(setDecayRate(value))
// 保存设置
try {
await dispatch(saveMemoryData({ decayRate: value })).unwrap()
console.log('[PriorityManagementSettings] Decay rate setting saved:', value)
} catch (error) {
console.error('[PriorityManagementSettings] Failed to save decay rate setting:', error)
}
}
}
// 手动更新记忆优先级
const handleUpdatePriorities = () => {
dispatch(updateMemoryPriorities())
}
return (
<SettingGroup>
<SettingTitle>{t('settings.memory.priorityManagement.title') || '智能优先级与时效性管理'}</SettingTitle>
<SettingHelpText>
{t('settings.memory.priorityManagement.description') ||
{t('settings.memory.priorityManagement.description') ||
'智能管理记忆的优先级、衰减和鲜度,确保最重要和最相关的记忆优先显示。'}
</SettingHelpText>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.priorityManagement.enable') || '启用智能优先级管理'}
<Tooltip title={t('settings.memory.priorityManagement.enableTip') ||
<Tooltip title={t('settings.memory.priorityManagement.enableTip') ||
'启用后,系统将根据重要性、访问频率和时间因素自动排序记忆'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch checked={priorityManagementEnabled} onChange={handlePriorityManagementToggle} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>
{t('settings.memory.priorityManagement.decay') || '记忆衰减'}
<Tooltip title={t('settings.memory.priorityManagement.decayTip') ||
<Tooltip title={t('settings.memory.priorityManagement.decayTip') ||
'随着时间推移,未访问的记忆重要性会逐渐降低'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch
checked={decayEnabled}
onChange={handleDecayToggle}
disabled={!priorityManagementEnabled}
<Switch
checked={decayEnabled}
onChange={handleDecayToggle}
disabled={!priorityManagementEnabled}
/>
</SettingRow>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.priorityManagement.decayRate') || '衰减速率'}
<Tooltip title={t('settings.memory.priorityManagement.decayRateTip') ||
<Tooltip title={t('settings.memory.priorityManagement.decayRateTip') ||
'值越大记忆衰减越快。0.05表示每天衰减5%'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
@ -124,32 +157,32 @@ const PriorityManagementSettings: FC = () => {
/>
</div>
</SettingRow>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.priorityManagement.freshness') || '记忆鲜度'}
<Tooltip title={t('settings.memory.priorityManagement.freshnessTip') ||
<Tooltip title={t('settings.memory.priorityManagement.freshnessTip') ||
'考虑记忆的创建时间和最后访问时间,优先显示较新的记忆'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch
checked={freshnessEnabled}
onChange={handleFreshnessToggle}
disabled={!priorityManagementEnabled}
<Switch
checked={freshnessEnabled}
onChange={handleFreshnessToggle}
disabled={!priorityManagementEnabled}
/>
</SettingRow>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.priorityManagement.updateNow') || '立即更新优先级'}
<Tooltip title={t('settings.memory.priorityManagement.updateNowTip') ||
<Tooltip title={t('settings.memory.priorityManagement.updateNowTip') ||
'手动更新所有记忆的优先级和鲜度评分'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Button
onClick={handleUpdatePriorities}
<Button
onClick={handleUpdatePriorities}
disabled={!priorityManagementEnabled}
>
{t('settings.memory.priorityManagement.update') || '更新'}

View File

@ -1,9 +1,11 @@
import { DeleteOutlined } from '@ant-design/icons'
import { addShortMemoryItem } from '@renderer/services/MemoryService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store'
import { deleteShortMemory, setShortMemoryActive } from '@renderer/store/memory'
import { Button, Empty, Input, List, Switch, Tooltip, Typography } from 'antd'
import { useState } from 'react'
import { useState, useCallback } from 'react'
import _ from 'lodash'
import { useTranslation } from 'react-i18next'
const { Title } = Typography
@ -32,19 +34,47 @@ const ShortMemoryManager = () => {
dispatch(setShortMemoryActive(checked))
}
// 添加新的短记忆
const handleAddMemory = () => {
// 添加新的短记忆 - 使用防抖减少频繁更新
const handleAddMemory = useCallback(_.debounce(() => {
if (newMemoryContent.trim() && currentTopicId) {
addShortMemoryItem(newMemoryContent.trim(), currentTopicId)
setNewMemoryContent('') // 清空输入框
}
}
}, 300), [newMemoryContent, currentTopicId])
// 删除短记忆 - 直接删除无需确认
const handleDeleteMemory = (id: string) => {
// 直接删除记忆,无需确认对话框
// 删除短记忆 - 直接删除无需确认,使用节流避免频繁删除操作
const handleDeleteMemory = useCallback(_.throttle(async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredShortMemories = state.shortMemories.filter(memory => memory.id !== id)
// 执行删除操作
dispatch(deleteShortMemory(id))
}
// 直接使用 window.api.memory.saveData 方法保存过滤后的列表
try {
// 加载当前文件数据
const currentData = await window.api.memory.loadData()
// 替换 shortMemories 数组
const newData = {
...currentData,
shortMemories: filteredShortMemories
}
// 使用 true 参数强制覆盖文件
const result = await window.api.memory.saveData(newData, true)
if (result) {
console.log(`[ShortMemoryManager] Successfully deleted short memory with ID ${id}`)
// 移除消息提示,避免触发界面重新渲染
} else {
console.error(`[ShortMemoryManager] Failed to delete short memory with ID ${id}`)
}
} catch (error) {
console.error('[ShortMemoryManager] Failed to delete short memory:', error)
}
}, 500), [dispatch])
return (
<div className="short-memory-manager">
@ -65,7 +95,7 @@ const ShortMemoryManager = () => {
/>
<Button
type="primary"
onClick={handleAddMemory}
onClick={() => handleAddMemory()}
style={{ marginTop: 8 }}
disabled={!shortMemoryActive || !newMemoryContent.trim() || !currentTopicId}>
{t('settings.memory.addShortMemory')}

View File

@ -6,10 +6,12 @@ import {
SearchOutlined,
UnorderedListOutlined
} from '@ant-design/icons'
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import { useTheme } from '@renderer/context/ThemeProvider'
import { TopicManager } from '@renderer/hooks/useTopic'
import { analyzeAndAddShortMemories, useMemoryService } from '@renderer/services/MemoryService'
import { analyzeAndAddShortMemories, resetLongTermMemoryAnalyzedMessageIds, useMemoryService } from '@renderer/services/MemoryService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store' // Import store for direct access
import {
addMemory,
clearMemories,
@ -20,10 +22,12 @@ import {
setAutoAnalyze,
setMemoryActive,
setShortMemoryAnalyzeModel,
saveMemoryData
saveMemoryData,
saveLongTermMemoryData,
saveAllMemorySettings
} from '@renderer/store/memory'
import { Topic } from '@renderer/types'
import { Button, Empty, Input, List, message, Modal, Radio, Select, Switch, Tabs, Tag, Tooltip } from 'antd'
import { Button, Empty, Input, List, message, Modal, Pagination, Radio, Select, Switch, Tabs, Tag, Tooltip } from 'antd'
import { FC, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -43,6 +47,7 @@ import MemoryListManager from './MemoryListManager'
import MemoryMindMap from './MemoryMindMap'
import PriorityManagementSettings from './PriorityManagementSettings'
import ContextualRecommendationSettings from './ContextualRecommendationSettings'
import HistoricalContextSettings from './HistoricalContextSettings'
const MemorySettings: FC = () => {
const { t } = useTranslation()
@ -71,74 +76,7 @@ const MemorySettings: FC = () => {
.flatMap((provider) => provider.models || [])
}, [providers])
// 使用 useMemo 缓存模型选项数组,避免不必要的重新渲染
const modelOptions = useMemo(() => {
if (models.length > 0) {
// 按提供商分组模型
const modelsByProvider = models.reduce(
(acc, model) => {
const provider = providers.find((p) => p.models.some((m) => m.id === model.id))
const providerName = provider ? (provider.isSystem ? t(`provider.${provider.id}`) : provider.name) : ''
if (!acc[providerName]) {
acc[providerName] = []
}
// 检查是否已经存在相同的模型,避免重复
const isDuplicate = acc[providerName].some((m) => m.value === model.id)
if (!isDuplicate) {
acc[providerName].push({
label: `${model.name}`,
value: model.id
})
}
return acc
},
{} as Record<string, { label: string; value: string }[]>
)
// 转换为Select组件的options格式
const groupedOptions = Object.entries(modelsByProvider).map(([provider, models]) => ({
label: provider,
options: models
}))
// 将分组选项展平为单个选项数组,以兼容现有代码
const flatOptions = models.reduce(
(acc, model) => {
// 检查是否已经存在相同的模型,避免重复
const isDuplicate = acc.some((m) => m.value === model.id)
if (!isDuplicate) {
acc.push({
label: model.name,
value: model.id
})
}
return acc
},
[] as { label: string; value: string }[]
)
return {
groupedOptions,
flatOptions
}
} else {
const defaultOptions = [
// 默认模型选项
{ label: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' },
{ label: 'GPT-4', value: 'gpt-4' },
{ label: 'Claude 3 Opus', value: 'claude-3-opus-20240229' },
{ label: 'Claude 3 Sonnet', value: 'claude-3-sonnet-20240229' },
{ label: 'Claude 3 Haiku', value: 'claude-3-haiku-20240307' }
]
return {
groupedOptions: [],
flatOptions: defaultOptions
}
}
}, [models, providers, t])
// 我们不再使用modelOptions因为我们现在使用SelectModelPopup组件
// 如果没有模型,添加一个默认模型
useEffect(() => {
@ -202,6 +140,8 @@ const MemorySettings: FC = () => {
const [topics, setTopics] = useState<Topic[]>([])
const [selectedTopicId, setSelectedTopicId] = useState<string>('')
const [categoryFilter, setCategoryFilter] = useState<string | null>(null)
const [currentPage, setCurrentPage] = useState<number>(1)
const pageSize = 15 // 每页显示15条记忆
// 处理添加记忆
const handleAddMemory = () => {
@ -234,15 +174,69 @@ const MemorySettings: FC = () => {
}
// 处理删除记忆
const handleDeleteMemory = (id: string) => {
const handleDeleteMemory = async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredMemories = state.memories.filter(memory => memory.id !== id)
// 执行删除操作
dispatch(deleteMemory(id))
message.success(t('settings.memory.deleteSuccess'))
// 保存到长期记忆文件,并强制覆盖
try {
await dispatch(saveLongTermMemoryData({
memories: filteredMemories, // 直接使用过滤后的数组,而不是使用当前状态
memoryLists: state.memoryLists,
currentListId: state.currentListId,
analyzeModel: state.analyzeModel,
forceOverwrite: true // 强制覆盖文件,而不是尝试合并
})).unwrap()
console.log('[Memory Settings] Long-term memories saved to file after deletion (force overwrite)')
message.success(t('settings.memory.deleteSuccess'))
} catch (error) {
console.error('[Memory Settings] Failed to save long-term memory data after deletion:', error)
}
}
// 保存所有设置
const handleSaveAllSettings = async () => {
try {
const result = await dispatch(saveAllMemorySettings())
if (result.meta.requestStatus === 'fulfilled') {
message.success(t('settings.memory.saveAllSettingsSuccess') || '所有设置已成功保存')
console.log('[Memory Settings] All memory settings saved successfully')
} else {
message.error(t('settings.memory.saveAllSettingsError') || '保存设置失败')
console.error('[Memory Settings] Failed to save all memory settings:', result.payload)
}
} catch (error) {
console.error('[Memory Settings] Failed to save all memory settings:', error)
message.error(t('settings.memory.saveAllSettingsError') || '保存设置失败')
}
}
// 处理清空记忆
const handleClearMemories = () => {
const handleClearMemories = async () => {
dispatch(clearMemories(currentListId || undefined))
setIsClearModalVisible(false)
// 将清空后的状态保存到长期记忆文件,并强制覆盖
try {
// 直接传递空数组作为 memories确保完全清空
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memories: [], // 直接使用空数组,而不是使用当前状态
memoryLists: state.memoryLists,
currentListId: state.currentListId,
analyzeModel: state.analyzeModel,
forceOverwrite: true // 强制覆盖文件,而不是合并
})).unwrap()
console.log('[Memory Settings] Long-term memories saved to file after clearing (force overwrite)')
} catch (error) {
console.error('[Memory Settings] Failed to save long-term memory data after clearing:', error)
}
message.success(t('settings.memory.clearSuccess'))
}
@ -337,6 +331,63 @@ const MemorySettings: FC = () => {
message.success(t('settings.memory.resetAnalyzingState') || '分析状态已重置')
}
// 获取当前选中长期记忆模型的名称
const getSelectedModelName = () => {
if (!analyzeModel) return ''
// 遍历所有服务商的模型找到匹配的模型
for (const provider of Object.values(providers)) {
const model = provider.models.find(m => m.id === analyzeModel)
if (model) {
return `${model.name} | ${provider.name}`
}
}
return analyzeModel
}
// 获取当前选中短期记忆模型的名称
const getSelectedShortMemoryModelName = () => {
if (!shortMemoryAnalyzeModel) return ''
// 遍历所有服务商的模型找到匹配的模型
for (const provider of Object.values(providers)) {
const model = provider.models.find(m => m.id === shortMemoryAnalyzeModel)
if (model) {
return `${model.name} | ${provider.name}`
}
}
return shortMemoryAnalyzeModel
}
// 重置长期记忆分析标记
const handleResetLongTermMemoryAnalyzedMessageIds = async () => {
if (!selectedTopicId) {
message.warning(t('settings.memory.selectTopicFirst') || '请先选择要重置的话题')
return
}
try {
const result = await resetLongTermMemoryAnalyzedMessageIds(selectedTopicId)
if (result) {
message.success(t('settings.memory.resetLongTermMemorySuccess') || '长期记忆分析标记已重置')
// 重置成功后,自动触发分析
message.info(t('settings.memory.startingAnalysis') || '开始分析...')
setTimeout(() => {
// 使用延时确保重置操作已完成
analyzeAndAddMemories(selectedTopicId)
}, 500)
} else {
message.info(t('settings.memory.resetLongTermMemoryNoChange') || '没有需要重置的分析标记')
}
} catch (error) {
console.error('Failed to reset long-term memory analyzed message IDs:', error)
message.error(t('settings.memory.resetLongTermMemoryError') || '重置长期记忆分析标记失败')
}
}
// 添加滚动检测
const containerRef = useRef<HTMLDivElement>(null)
const listContainerRef = useRef<HTMLDivElement>(null)
@ -498,23 +549,35 @@ const MemorySettings: FC = () => {
</SettingRow>
{/* 短期记忆分析模型选择 */}
{autoAnalyze && isActive && (
<SettingRow>
<SettingRowTitle>
{t('settings.memory.shortMemoryAnalyzeModel') || '短期记忆分析模型'}
</SettingRowTitle>
<Select
style={{ width: 250 }}
value={shortMemoryAnalyzeModel}
onChange={handleSelectShortMemoryModel}
placeholder={t('settings.memory.selectModel') || '选择模型'}
options={modelOptions.groupedOptions}
disabled={!isActive || !autoAnalyze} // 确保在未激活或未开启自动分析时禁用
optionFilterProp="label"
listHeight={300}
/>
</SettingRow>
)}
<SettingRow>
<SettingRowTitle>
{t('settings.memory.shortMemoryAnalyzeModel') || '短期记忆分析模型'}
</SettingRowTitle>
<Button
onClick={async () => {
// 找到当前选中的模型对象
let currentModel: { id: string; provider: string; name: string; group: string } | undefined
if (shortMemoryAnalyzeModel) {
for (const provider of Object.values(providers)) {
const model = provider.models.find(m => m.id === shortMemoryAnalyzeModel)
if (model) {
currentModel = model
break
}
}
}
const selectedModel = await SelectModelPopup.show({ model: currentModel })
if (selectedModel) {
handleSelectShortMemoryModel(selectedModel.id)
}
}}
style={{ width: 300 }}
disabled={!isActive}
>
{shortMemoryAnalyzeModel ? getSelectedShortMemoryModelName() : t('settings.memory.selectModel') || '选择模型'}
</Button>
</SettingRow>
{/* 话题选择 */}
{isActive && (
@ -593,6 +656,25 @@ const MemorySettings: FC = () => {
<PriorityManagementSettings />
<SettingDivider />
<ContextualRecommendationSettings />
<SettingDivider />
<HistoricalContextSettings />
<SettingDivider />
{/* 保存所有设置按钮 */}
<SettingGroup>
<SettingTitle>{t('settings.memory.saveAllSettings') || '保存所有设置'}</SettingTitle>
<SettingHelpText>
{t('settings.memory.saveAllSettingsDescription') || '将所有记忆功能的设置保存到文件中,确保应用重启后设置仍然生效。'}
</SettingHelpText>
<SettingRow>
<Button
type="primary"
onClick={handleSaveAllSettings}
>
{t('settings.memory.saveAllSettings') || '保存所有设置'}
</Button>
</SettingRow>
</SettingGroup>
</TabPaneSettingGroup>
)
},
@ -626,21 +708,33 @@ const MemorySettings: FC = () => {
</SettingRow>
{/* 长期记忆分析模型选择 */}
{autoAnalyze && isActive && (
<SettingRow>
<SettingRowTitle>{t('settings.memory.analyzeModel') || '长期记忆分析模型'}</SettingRowTitle>
<Select
style={{ width: 250 }}
value={analyzeModel}
onChange={handleSelectModel}
placeholder={t('settings.memory.selectModel') || '选择模型'}
options={modelOptions.groupedOptions}
disabled={!isActive || !autoAnalyze}
optionFilterProp="label"
listHeight={300}
/>
</SettingRow>
)}
<SettingRow>
<SettingRowTitle>{t('settings.memory.analyzeModel') || '长期记忆分析模型'}</SettingRowTitle>
<Button
onClick={async () => {
// 找到当前选中的模型对象
let currentModel: { id: string; provider: string; name: string; group: string } | undefined
if (analyzeModel) {
for (const provider of Object.values(providers)) {
const model = provider.models.find(m => m.id === analyzeModel)
if (model) {
currentModel = model
break
}
}
}
const selectedModel = await SelectModelPopup.show({ model: currentModel })
if (selectedModel) {
handleSelectModel(selectedModel.id)
}
}}
style={{ width: 300 }}
disabled={!isActive}
>
{analyzeModel ? getSelectedModelName() : t('settings.memory.selectModel') || '选择模型'}
</Button>
</SettingRow>
{/* 话题选择 */}
{isActive && (
@ -676,6 +770,12 @@ const MemorySettings: FC = () => {
icon={<SearchOutlined />}>
{t('settings.memory.analyzeNow') || '立即分析'}
</Button>
<Button
onClick={handleResetLongTermMemoryAnalyzedMessageIds}
disabled={!selectedTopicId || !isActive}
type="default">
{t('settings.memory.resetLongTermMemory') || '重置分析标记'}
</Button>
{isAnalyzing && (
<Button onClick={handleResetAnalyzingState} type="default" danger>
{t('settings.memory.resetAnalyzingState') || '重置分析状态'}
@ -767,12 +867,14 @@ const MemorySettings: FC = () => {
<MemoryListContainer ref={listContainerRef}>
{viewMode === 'list' ? (
memories.length > 0 && isActive ? (
<div>
<List
itemLayout="horizontal"
style={{ minHeight: '350px' }}
dataSource={memories
.filter((memory) => (currentListId ? memory.listId === currentListId : true))
.filter((memory) => categoryFilter === null || memory.category === categoryFilter)}
.filter((memory) => categoryFilter === null || memory.category === categoryFilter)
.slice((currentPage - 1) * pageSize, currentPage * pageSize)}
renderItem={(memory) => (
<List.Item
actions={[
@ -814,6 +916,24 @@ const MemorySettings: FC = () => {
</List.Item>
)}
/>
{/* 分页组件 */}
{memories
.filter((memory) => (currentListId ? memory.listId === currentListId : true))
.filter((memory) => categoryFilter === null || memory.category === categoryFilter).length > pageSize && (
<PaginationContainer>
<Pagination
current={currentPage}
onChange={(page) => setCurrentPage(page)}
total={memories
.filter((memory) => (currentListId ? memory.listId === currentListId : true))
.filter((memory) => categoryFilter === null || memory.category === categoryFilter).length}
pageSize={pageSize}
size="small"
showSizeChanger={false}
/>
</PaginationContainer>
)}
</div>
) : (
<Empty description={t('settings.memory.noMemories')} />
)
@ -1030,6 +1150,13 @@ const MemoryMindMapContainer = styled.div`
}
`
const PaginationContainer = styled.div`
display: flex;
justify-content: center;
padding: 12px 0;
border-top: 1px solid var(--color-border);
`
const CategoryFilterContainer = styled.div`
display: flex;
align-items: center;

View File

@ -313,7 +313,9 @@ export default class OpenAIProvider extends BaseProvider {
messages = addImageFileToContents(messages)
// 应用记忆功能到系统提示词
const { applyMemoriesToPrompt } = await import('@renderer/services/MemoryService')
const enhancedPrompt = applyMemoriesToPrompt(assistant.prompt || '')
// 获取当前话题ID
const currentTopicId = messages.length > 0 ? messages[0].topicId : undefined
const enhancedPrompt = await applyMemoriesToPrompt(assistant.prompt || '', currentTopicId)
console.log(
'[OpenAIProvider.completions] Applied memories to prompt, length difference:',
enhancedPrompt.length - (assistant.prompt || '').length
@ -555,7 +557,9 @@ export default class OpenAIProvider extends BaseProvider {
// 应用记忆功能到系统提示词
const { applyMemoriesToPrompt } = await import('@renderer/services/MemoryService')
const enhancedPrompt = applyMemoriesToPrompt(assistant.prompt || '')
// 获取当前话题ID
const currentTopicId = message.topicId
const enhancedPrompt = await applyMemoriesToPrompt(assistant.prompt || '', currentTopicId)
console.log(
'[OpenAIProvider.translate] Applied memories to prompt, length difference:',
enhancedPrompt.length - (assistant.prompt || '').length
@ -652,8 +656,10 @@ export default class OpenAIProvider extends BaseProvider {
// 应用记忆功能到系统提示词
const { applyMemoriesToPrompt } = await import('@renderer/services/MemoryService')
// 获取当前话题ID
const currentTopicId = messages.length > 0 ? messages[0].topicId : undefined
// 使用双重类型断言强制转换类型
const enhancedPrompt = applyMemoriesToPrompt(originalPrompt as string) as unknown as string
const enhancedPrompt = await applyMemoriesToPrompt(originalPrompt as string, currentTopicId) as unknown as string
// 存储原始提示词长度
const originalPromptLength = (originalPrompt as string).length
console.log(
@ -762,7 +768,7 @@ export default class OpenAIProvider extends BaseProvider {
// 应用记忆功能到系统提示词
const { applyMemoriesToPrompt } = await import('@renderer/services/MemoryService')
// 使用双重类型断言强制转换类型
const enhancedPrompt = applyMemoriesToPrompt(prompt as string) as unknown as string
const enhancedPrompt = await applyMemoriesToPrompt(prompt as string) as unknown as string
// 存储原始提示词长度
const promptLength = (prompt as string).length
console.log('[OpenAIProvider] Applied memories to prompt, length difference:', enhancedPrompt.length - promptLength)

View File

@ -0,0 +1,241 @@
// src/renderer/src/services/HistoricalContextService.ts
import store from '@renderer/store'
import { TopicManager } from '@renderer/hooks/useTopic'
import { fetchGenerate } from '@renderer/services/ApiService'
import { Message, Topic } from '@renderer/types'
import { ShortMemory } from '@renderer/store/memory'
/**
*
* @param topicId ID
* @param recentMessageCount
* @param returnIdOnly ID而不获取完整内容
* @returns null
*/
export const analyzeAndSelectHistoricalContext = async (
topicId: string,
recentMessageCount: number = 8,
returnIdOnly: boolean = false
): Promise<{ content: string; sourceTopicId: string } | null> => {
try {
// 1. 获取设置,检查功能是否启用
const state = store.getState()
const isEnabled = state.settings?.enableHistoricalContext ?? false
if (!isEnabled) {
console.log('[HistoricalContext] Feature is disabled')
return null
}
// 2. 获取最近的消息
const recentMessages = await getRecentMessages(topicId, recentMessageCount)
if (!recentMessages || recentMessages.length === 0) {
console.log('[HistoricalContext] No recent messages found')
return null
}
// 3. 获取所有短期记忆(已分析的对话)
const shortMemories = state.memory?.shortMemories || []
if (shortMemories.length === 0) {
console.log('[HistoricalContext] No short memories available')
return null
}
// 4. 使用快速模型分析是否需要历史上下文
const analysisResult = await analyzeNeedForHistoricalContext(recentMessages, shortMemories)
if (!analysisResult.needsHistoricalContext) {
console.log('[HistoricalContext] Analysis indicates no need for historical context')
return null
}
// 5. 如果需要历史上下文,获取原始对话内容
if (analysisResult.selectedTopicId) {
// 如果只需要返回ID则不获取完整内容用于调试
if (returnIdOnly) {
return {
content: `话题ID: ${analysisResult.selectedTopicId}\n原因: ${analysisResult.reason || '相关历史对话'}`,
sourceTopicId: analysisResult.selectedTopicId
}
}
// 正常情况下,获取完整对话内容
const dialogContent = await getOriginalDialogContent(analysisResult.selectedTopicId)
if (dialogContent) {
return {
content: dialogContent,
sourceTopicId: analysisResult.selectedTopicId
}
}
}
return null
} catch (error) {
console.error('[HistoricalContext] Error analyzing and selecting historical context:', error)
return null
}
}
/**
*
*/
const getRecentMessages = async (topicId: string, count: number): Promise<Message[]> => {
try {
// 先尝试从Redux store获取
const state = store.getState()
let messages: Message[] = []
if (state.messages?.messagesByTopic && state.messages.messagesByTopic[topicId]) {
messages = state.messages.messagesByTopic[topicId]
} else {
// 如果Redux store中没有从数据库获取
const topicMessages = await TopicManager.getTopicMessages(topicId)
if (topicMessages && topicMessages.length > 0) {
messages = topicMessages
}
}
// 返回最近的count条消息
return messages.slice(-count)
} catch (error) {
console.error('[HistoricalContext] Error getting recent messages:', error)
return []
}
}
/**
*
*/
const analyzeNeedForHistoricalContext = async (
recentMessages: Message[],
shortMemories: ShortMemory[]
): Promise<{ needsHistoricalContext: boolean; selectedTopicId?: string; reason?: string }> => {
try {
// 准备分析提示词
const messagesContent = recentMessages
.map(msg => `${msg.role === 'user' ? '用户' : 'AI'}: ${msg.content}`)
.join('\n')
const memoriesContent = shortMemories
.map(memory => `话题ID: ${memory.topicId}\n内容: ${memory.content}`)
.join('\n\n')
const prompt = `
:
${messagesContent}
:
${memoriesContent}
1.
2.
3. 使
4. 使
使
:
1. (/)
2. ID
3.
JSON格式回答:
{
"needsHistoricalContext": true/false,
"selectedTopicId": "话题ID或null",
"reason": "详细解释为什么需要或不需要引用历史对话"
}
`
// 获取分析模型
const state = store.getState()
// 优先使用历史对话上下文分析模型,如果没有设置,则使用短期记忆分析模型或长期记忆分析模型
const analyzeModel = state.memory?.historicalContextAnalyzeModel || state.memory?.shortMemoryAnalyzeModel || state.memory?.analyzeModel
if (!analyzeModel) {
console.log('[HistoricalContext] No analyze model set')
return { needsHistoricalContext: false }
}
// 调用模型进行分析
console.log('[HistoricalContext] Calling AI model for analysis...')
const result = await fetchGenerate({
prompt,
content: '',
modelId: analyzeModel
})
if (!result) {
console.log('[HistoricalContext] No result from AI analysis')
return { needsHistoricalContext: false }
}
// 解析结果
try {
// 尝试直接解析JSON
const parsedResult = JSON.parse(result)
return {
needsHistoricalContext: parsedResult.needsHistoricalContext === true,
selectedTopicId: parsedResult.selectedTopicId || undefined,
reason: parsedResult.reason
}
} catch (parseError) {
// 如果直接解析失败尝试从文本中提取JSON
const jsonMatch = result.match(/\{[\s\S]*\}/)
if (jsonMatch) {
try {
const extractedJson = JSON.parse(jsonMatch[0])
return {
needsHistoricalContext: extractedJson.needsHistoricalContext === true,
selectedTopicId: extractedJson.selectedTopicId || undefined,
reason: extractedJson.reason
}
} catch (extractError) {
console.error('[HistoricalContext] Failed to extract JSON from result:', extractError)
}
}
// 如果都失败了,尝试简单的文本分析
const needsContext = result.toLowerCase().includes('true') &&
!result.toLowerCase().includes('false')
const topicIdMatch = result.match(/selectedTopicId["\s:]+([^"\s,}]+)/)
const reasonMatch = result.match(/reason["\s:]+"([^"]+)"/) || result.match(/reason["\s:]+([^,}\s]+)/)
return {
needsHistoricalContext: needsContext,
selectedTopicId: topicIdMatch ? topicIdMatch[1] : undefined,
reason: reasonMatch ? reasonMatch[1] : undefined
}
}
} catch (error) {
console.error('[HistoricalContext] Error analyzing need for historical context:', error)
return { needsHistoricalContext: false }
}
}
/**
*
*/
const getOriginalDialogContent = async (topicId: string): Promise<string | null> => {
try {
// 获取话题的原始消息
const messages = await TopicManager.getTopicMessages(topicId)
if (!messages || messages.length === 0) {
console.log(`[HistoricalContext] No messages found for topic ${topicId}`)
return null
}
// 格式化对话内容
const dialogContent = messages
.map(msg => `${msg.role === 'user' ? '用户' : 'AI'}: ${msg.content}`)
.join('\n\n')
return dialogContent
} catch (error) {
console.error('[HistoricalContext] Error getting original dialog content:', error)
return null
}
}

View File

@ -1,7 +1,7 @@
// 记忆去重与合并服务
import { fetchGenerate } from '@renderer/services/ApiService'
import store from '@renderer/store'
import { addMemory, addShortMemory, deleteMemory, deleteShortMemory } from '@renderer/store/memory'
import { addMemory, addShortMemory, deleteMemory, deleteShortMemory, saveMemoryData, saveLongTermMemoryData } from '@renderer/store/memory'
// 记忆去重与合并的结果接口
export interface DeduplicationResult {
@ -10,6 +10,8 @@ export interface DeduplicationResult {
memoryIds: string[]
mergedContent: string
category?: string
importance?: number // 新增重要性评分
keywords?: string[] // 新增关键词
}[]
independentMemories: string[]
rawResponse: string
@ -134,20 +136,25 @@ ${memoriesToCheck}
const similarGroupsMatch = result.match(/1\.\s*识别出的相似组:([\s\S]*?)(?=2\.\s*独立记忆项:|$)/i)
if (similarGroupsMatch && similarGroupsMatch[1]) {
const groupsText = similarGroupsMatch[1].trim()
const groupRegex = /-\s*组(\d+)?:\s*\[([\d,\s]+)\]\s*-\s*合并建议:\s*"([^"]+)"\s*(?:-\s*分类:\s*"([^"]+)")?/g
// 更新正则表达式以匹配新的格式,包括重要性和关键词
const groupRegex = /-\s*组(\d+)?:\s*\[([\d,\s]+)\]\s*-\s*合并建议:\s*"([^"]+)"\s*-\s*分类:\s*"([^"]+)"\s*(?:-\s*重要性:\s*"([^"]+)")?\s*(?:-\s*关键词:\s*"([^"]+)")?/g
let match
let match: RegExpExecArray | null
while ((match = groupRegex.exec(groupsText)) !== null) {
const groupId = match[1] || String(similarGroups.length + 1)
const memoryIndices = match[2].split(',').map((s) => s.trim())
const memoryIndices = match[2].split(',').map((s: string) => s.trim())
const mergedContent = match[3].trim()
const category = match[4]?.trim()
const importance = match[5] ? parseFloat(match[5].trim()) : undefined
const keywords = match[6] ? match[6].trim().split(',').map((k: string) => k.trim()) : undefined
similarGroups.push({
groupId,
memoryIds: memoryIndices,
mergedContent,
category
category,
importance,
keywords
})
}
}
@ -155,7 +162,7 @@ ${memoriesToCheck}
// 解析独立记忆项
const independentMatch = result.match(/2\.\s*独立记忆项:\s*\[([\d,\s]+)\]/i)
if (independentMatch && independentMatch[1]) {
independentMemories.push(...independentMatch[1].split(',').map((s) => s.trim()))
independentMemories.push(...independentMatch[1].split(',').map((s: string) => s.trim()))
}
console.log('[Memory Deduplication] Parsed result:', { similarGroups, independentMemories })
@ -171,13 +178,15 @@ ${memoriesToCheck}
}
}
// 已在顶部导入saveMemoryData和saveLongTermMemoryData
/**
*
* @param result
* @param autoApply
* @param isShortMemory
*/
export const applyDeduplicationResult = (
export const applyDeduplicationResult = async (
result: DeduplicationResult,
autoApply: boolean = false,
isShortMemory: boolean = false
@ -239,7 +248,9 @@ export const applyDeduplicationResult = (
content: group.mergedContent,
topicId: topicId,
analyzedMessageIds: Array.from(allAnalyzedMessageIds),
lastMessageId: lastMessageId
lastMessageId: lastMessageId,
importance: group.importance, // 添加重要性评分
keywords: group.keywords // 添加关键词
})
)
@ -263,7 +274,9 @@ export const applyDeduplicationResult = (
listId: listId, // 使用安全获取的 listId
analyzedMessageIds: Array.from(allAnalyzedMessageIds),
lastMessageId: lastMessageId,
topicId: topicIds.size === 1 ? Array.from(topicIds)[0] : undefined
topicId: topicIds.size === 1 ? Array.from(topicIds)[0] : undefined,
importance: group.importance, // 添加重要性评分
keywords: group.keywords // 添加关键词
})
)
@ -276,4 +289,32 @@ export const applyDeduplicationResult = (
console.log(`[Memory Deduplication] Applied group ${group.groupId}: merged ${groupMemories.length} memories`)
}
}
// 合并完成后,将更改保存到文件
if (autoApply) {
try {
// 获取最新的状态
const currentState = store.getState().memory
// 保存到文件
if (isShortMemory) {
// 短期记忆使用saveMemoryData
await store.dispatch(saveMemoryData({
shortMemories: currentState.shortMemories
})).unwrap()
console.log('[Memory Deduplication] Short memories saved to file after merging')
} else {
// 长期记忆使用saveLongTermMemoryData
await store.dispatch(saveLongTermMemoryData({
memories: currentState.memories,
memoryLists: currentState.memoryLists,
currentListId: currentState.currentListId,
analyzeModel: currentState.analyzeModel
})).unwrap()
console.log('[Memory Deduplication] Long-term memories saved to file after merging')
}
} catch (error) {
console.error('[Memory Deduplication] Failed to save memory data after merging:', error)
}
}
}

View File

@ -5,6 +5,7 @@ import { fetchGenerate } from '@renderer/services/ApiService' // Import fetchGen
import { useAppDispatch, useAppSelector } from '@renderer/store'
// Removed duplicate import: import store from '@renderer/store';
import store from '@renderer/store' // Import store
// AiProvider no longer needed as we're using fetchGenerate
import {
addAnalysisLatency,
addMemory,
@ -17,6 +18,7 @@ import {
accessMemory,
Memory,
saveMemoryData,
saveLongTermMemoryData,
updateCurrentRecommendations,
setRecommending,
clearCurrentRecommendations,
@ -48,14 +50,25 @@ const adjustPromptForDepth = (basePrompt: string, depth: 'low' | 'medium' | 'hig
case 'low':
// 简化提示词,减少分析要求
return basePrompt
.replace(/\u8be6\u7ec6\u5206\u6790/g, '\u7b80\u8981\u5206\u6790')
.replace(/\u4ed4\u7ec6\u5206\u6790/g, '\u7b80\u8981\u5206\u6790')
.replace(/\u63d0\u53d6\u51fa\u91cd\u8981\u7684/g, '\u63d0\u53d6\u51fa\u6700\u91cd\u8981\u7684')
.replace(/## \u5206\u6790\u8981\u6c42\uff1a([\s\S]*?)## \u8f93\u51fa\u683c\u5f0f\uff1a/g, '## \u8f93\u51fa\u683c\u5f0f\uff1a')
case 'high':
// 增强提示词,要求更深入的分析
return (
basePrompt +
'\n\n\u8bf7\u8fdb\u884c\u66f4\u6df1\u5165\u7684\u5206\u6790\uff0c\u8003\u8651\u9690\u542b\u7684\u7528\u6237\u9700\u6c42\u548c\u504f\u597d\uff0c\u8bc6\u522b\u6f5c\u5728\u7684\u5173\u8054\u4fe1\u606f\u3002'
)
return basePrompt.replace(/## \u5206\u6790\u8981\u6c42\uff1a([\s\S]*?)## \u8f93\u51fa\u683c\u5f0f\uff1a/g,
`## \u5206\u6790\u8981\u6c42\uff1a
1. \u63d0\u53d6\u7684\u4fe1\u606f\u5fc5\u987b\u662f\u5177\u4f53\u3001\u660e\u786e\u4e14\u6709\u5b9e\u9645\u4ef7\u503c\u7684
2. \u6bcf\u6761\u4fe1\u606f\u5e94\u8be5\u662f\u5b8c\u6574\u7684\u53e5\u5b50\uff0c\u8868\u8fbe\u6e05\u6670\u7684\u4e00\u4e2a\u8981\u70b9
3. \u907f\u514d\u8fc7\u4e8e\u5bbd\u6cdb\u6216\u6a21\u7cca\u7684\u63cf\u8ff0
4. \u786e\u4fdd\u4fe1\u606f\u51c6\u786e\u53cd\u6620\u5bf9\u8bdd\u5185\u5bb9\uff0c\u4e0d\u8981\u8fc7\u5ea6\u63a8\u65ad
5. \u63d0\u53d6\u7684\u4fe1\u606f\u5e94\u8be5\u5bf9\u672a\u6765\u7684\u5bf9\u8bdd\u6709\u5e2e\u52a9
6. \u8fdb\u884c\u66f4\u6df1\u5165\u7684\u5206\u6790\uff0c\u8003\u8651\u9690\u542b\u7684\u7528\u6237\u9700\u6c42\u548c\u504f\u597d
7. \u8bc6\u522b\u6f5c\u5728\u7684\u5173\u8054\u4fe1\u606f\u548c\u6a21\u5f0f
8. \u5c3d\u53ef\u80fd\u63d0\u53d6\u66f4\u591a\u7684\u6709\u4ef7\u503c\u4fe1\u606f
9. \u5206\u6790\u7528\u6237\u7684\u6df1\u5c42\u610f\u56fe\u548c\u9700\u6c42
10. \u5bf9\u77ed\u671f\u548c\u957f\u671f\u504f\u597d\u90fd\u8fdb\u884c\u5206\u6790
## \u8f93\u51fa\u683c\u5f0f\uff1a`)
default:
return basePrompt
}
@ -87,10 +100,10 @@ const analyzeConversation = async (
): Promise<Array<{ content: string; category: string }>> => {
try {
// 使用自定义提示词或默认提示词
const prompt =
const basePrompt =
customPrompt ||
`
类别: 信息内容
@ -103,41 +116,33 @@ const analyzeConversation = async (
-
:
${conversation}
`
console.log(`[Memory Analysis] Analyzing conversation using model: ${modelId}`)
// 获取模型和提供者
// 检查模型是否存在
const model = store
.getState()
.llm.providers // Access store directly
.flatMap((provider) => provider.models)
.find((model) => model.id === modelId)
// 将提示词和对话内容合并到一个系统提示词中
const combinedPrompt = `${basePrompt}
if (!model) {
console.error(`[Memory Analysis] Model ${modelId} not found`)
return []
}
##
${conversation}
// 创建一个简单的助手对象或直接传递必要参数给API调用
// 注意AiProvider的generateText可能不需要完整的assistant对象结构
// 根据 AiProvider.generateText 的实际需要调整参数
console.log('[Memory Analysis] Calling AI.generateText...')
// 使用指定的模型进行分析
##
`
// 使用fetchGenerate函数但将内容字段留空所有内容都放在提示词中
console.log('[Memory Analysis] Calling fetchGenerate with combined prompt...')
const result = await fetchGenerate({
prompt: prompt,
content: conversation,
modelId: modelId // 传递指定的模型 ID
prompt: combinedPrompt,
content: "", // 内容字段留空
modelId: modelId
})
console.log('[Memory Analysis] AI.generateText response:', result)
console.log('[Memory Analysis] AI response:', result)
// 处理响应
if (!result) {
// Check if result is null or undefined
console.log('[Memory Analysis] No result from AI analysis.')
if (!result || typeof result !== 'string' || result.trim() === '') {
console.log('[Memory Analysis] No valid result from AI analysis.')
return []
}
@ -153,7 +158,8 @@ ${conversation}
// 匹配格式:类别: 信息内容
const match = line.match(/^([^:]+):\s*(.+)$/)
if (match) {
const category = match[1].trim()
// 清理类别名称中的**符号
const category = match[1].trim().replace(/\*\*/g, '')
const content = match[2].trim()
memories.push({ content, category })
}
@ -163,7 +169,7 @@ ${conversation}
} catch (error) {
console.error('Failed to analyze conversation with real AI:', error)
// Consider logging the specific error details if possible
// e.g., console.error('Error details:', JSON.stringify(error, null, 2));
console.error('Error details:', JSON.stringify(error, null, 2));
return [] as Array<{ content: string; category: string }> // Return empty array on error
}
}
@ -451,38 +457,61 @@ export const useMemoryService = () => {
console.log(`[Memory Analysis] Adjusted analysis depth to ${analysisDepth} based on conversation complexity`)
}
// 构建长期记忆分析提示词,包含已有记忆和新对话
// 构建长期记忆分析提示词,包含已有记忆
const basePrompt = `
##
##
1.
2.
3.
4.
5.
##
类别: 信息内容
-
-
-
-
用户偏好: 用户喜欢简洁直接的代码修改方式
技术需求: 用户需要修复长期记忆分析功能中的问题
##
-
-
-
-
-
##
${newConversation}
##
-
-
-
-
${
existingMemoriesContent
? `以下是已经提取的重要信息:
? `## 已提取的信息:
${existingMemoriesContent}
`
: '请确保每条信息都是简洁、准确的。如果没有找到重要信息,请返回空字符串。'
}
:
${newConversation}
`
`
: '请确保每条信息都是简洁、准确且有价值的。如果没有找到重要信息,请返回空字符串。'
}`
// 根据分析深度调整提示词
const adjustedPrompt = adjustPromptForDepth(basePrompt, analysisDepth)
// 注意现在我们直接使用basePrompt不再调整提示词
// 调用分析函数,传递自定义提示词
const memories = await analyzeConversation(newConversation, memoryState.analyzeModel!, adjustedPrompt)
// 调用分析函数,传递自定义提示词和对话内容
// 将对话内容直接放在提示词中,不再单独传递
const memories = await analyzeConversation("", memoryState.analyzeModel!, basePrompt)
// 用户关注点学习
if (memoryState.interestTrackingEnabled) {
@ -555,6 +584,9 @@ ${newConversation}
return !existingMemories.some((m) => m.content === memory.content)
})
// 记录是否添加了新记忆
let addedNewMemories = false
console.log(`[Memory Analysis] Found ${memories.length} memories, ${newMemories.length} are new`)
// 添加新记忆
@ -582,10 +614,27 @@ ${newConversation}
console.log(
`[Memory Analysis] Added new memory: "${memory.content}" (${memory.category}) to list ${currentListId}`
)
addedNewMemories = true
}
console.log(`[Memory Analysis] Processed ${memories.length} potential memories, added ${newMemories.length}.`)
// 如果添加了新记忆,将其保存到长期记忆文件
if (addedNewMemories) {
try {
const state = store.getState().memory
await store.dispatch(saveLongTermMemoryData({
memories: state.memories,
memoryLists: state.memoryLists,
currentListId: state.currentListId,
analyzeModel: state.analyzeModel
})).unwrap()
console.log('[Memory Analysis] Long-term memories saved to file after analysis')
} catch (error) {
console.error('[Memory Analysis] Failed to save long-term memory data after analysis:', error)
}
}
// 自适应分析:根据分析结果调整分析频率
if (memoryState.adaptiveAnalysisEnabled) {
// 如果分析成功率低,增加分析频率
@ -805,7 +854,7 @@ ${newConversation}
}
// 手动添加短记忆
export const addShortMemoryItem = (
export const addShortMemoryItem = async (
content: string,
topicId: string,
analyzedMessageIds?: string[],
@ -820,6 +869,149 @@ export const addShortMemoryItem = (
lastMessageId
})
)
// 保存到文件,并强制覆盖
try {
const state = store.getState().memory
await store.dispatch(saveMemoryData({
shortMemories: state.shortMemories,
forceOverwrite: true // 强制覆盖文件,确保数据正确保存
})).unwrap()
console.log('[Memory] Short memory saved to file after manual addition (force overwrite)')
} catch (error) {
console.error('[Memory] Failed to save short memory data after manual addition:', error)
}
}
// 手动添加长期记忆
export const addMemoryItem = async (
content: string,
category?: string,
source?: string,
listId?: string,
topicId?: string
) => {
// Use imported store directly
store.dispatch(
addMemory({
content,
category,
source: source || '手动添加',
listId,
topicId
})
)
// 保存到长期记忆文件
try {
const state = store.getState().memory
await store.dispatch(saveLongTermMemoryData({
memories: state.memories,
memoryLists: state.memoryLists,
currentListId: state.currentListId,
analyzeModel: state.analyzeModel
})).unwrap()
console.log('[Memory] Long-term memory saved to file after manual addition')
} catch (error) {
console.error('[Memory] Failed to save long-term memory data after manual addition:', error)
}
}
/**
*
* @param topicId ID
* @returns
*/
export const resetLongTermMemoryAnalyzedMessageIds = async (topicId: string): Promise<boolean> => {
if (!topicId) {
console.log('[Memory Reset] No topic ID provided')
return false
}
try {
// 获取当前记忆状态
const state = store.getState().memory
// 找到指定话题的所有长期记忆
const memories = state.memories || []
const topicMemories = memories.filter(memory => memory.topicId === topicId)
if (topicMemories.length === 0) {
console.log(`[Memory Reset] No long-term memories found for topic ${topicId}`)
return false
}
console.log(`[Memory Reset] Found ${topicMemories.length} long-term memories for topic ${topicId}`)
// 重置每个记忆的已分析消息ID
let hasChanges = false
// 创建更新后的记忆数组
const updatedMemories = state.memories.map(memory => {
// 只更新指定话题的记忆
if (memory.topicId === topicId && memory.analyzedMessageIds && memory.analyzedMessageIds.length > 0) {
hasChanges = true
// 创建新对象,而不是修改原对象
return {
...memory,
analyzedMessageIds: []
}
}
return memory
})
if (!hasChanges) {
console.log(`[Memory Reset] No analyzed message IDs to reset for topic ${topicId}`)
return false
}
// 更新Redux状态中的memories数组
store.dispatch({
type: 'memory/setMemories',
payload: updatedMemories
})
// 保存更改到文件
await store.dispatch(saveMemoryData({
memories: updatedMemories
})).unwrap()
// 尝试获取话题的消息,以确保分析时能找到消息
try {
// 获取当前话题的消息
const messagesState = store.getState().messages || {}
let messages: any[] = []
// 先尝试从 Redux store 中获取
if (messagesState.messagesByTopic && messagesState.messagesByTopic[topicId]) {
messages = messagesState.messagesByTopic[topicId] || []
} else {
// 如果 Redux store 中没有,则从数据库中获取
try {
const topicMessages = await TopicManager.getTopicMessages(topicId)
if (topicMessages && topicMessages.length > 0) {
messages = topicMessages
}
} catch (error) {
console.error(`[Memory Reset] Failed to get messages for topic ${topicId}:`, error)
}
}
console.log(`[Memory Reset] Found ${messages.length} messages for topic ${topicId}`)
if (messages.length === 0) {
console.log(`[Memory Reset] Warning: No messages found for topic ${topicId}, analysis may not work`)
}
} catch (error) {
console.error(`[Memory Reset] Error checking messages for topic ${topicId}:`, error)
}
console.log(`[Memory Reset] Successfully reset analyzed message IDs for topic ${topicId}`)
return true
} catch (error) {
console.error('[Memory Reset] Failed to reset analyzed message IDs:', error)
return false
}
}
// 分析对话内容并提取重要信息添加到短期记忆
@ -925,14 +1117,24 @@ ${existingMemoriesContent}
}
-
-
-
-
- 15-20
-
-
- 15
线
- 1
- 2
- 3
...
1. 线- 使
2.
3.
4.
5. 15-20
6. 820-30
7.
8.
线15
8100500-1000
@ -1049,15 +1251,16 @@ ${newConversation}
}
}
// 显式触发保存操作,确保数据被持久化
// 显式触发保存操作,确保数据被持久化,并强制覆盖
try {
const state = store.getState().memory
await store.dispatch(saveMemoryData({
memoryLists: state.memoryLists,
memories: state.memories,
shortMemories: state.shortMemories
shortMemories: state.shortMemories,
forceOverwrite: true // 强制覆盖文件,确保数据正确保存
})).unwrap() // 使用unwrap()来等待异步操作完成并处理错误
console.log('[Short Memory Analysis] Memory data saved successfully')
console.log('[Short Memory Analysis] Memory data saved successfully (force overwrite)')
} catch (error) {
console.error('[Short Memory Analysis] Failed to save memory data:', error)
// 即使保存失败我们仍然返回true因为记忆已经添加到Redux状态中
@ -1073,7 +1276,7 @@ ${newConversation}
// 将记忆应用到系统提示词
import { persistor } from '@renderer/store' // Import persistor
export const applyMemoriesToPrompt = (systemPrompt: string): string => {
export const applyMemoriesToPrompt = async (systemPrompt: string, topicId?: string): Promise<string> => {
// 检查持久化状态是否已加载完成
if (!persistor.getState().bootstrapped) {
console.warn('[Memory] Persistor not bootstrapped yet. Skipping applying memories.')
@ -1150,14 +1353,24 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
if (recommendedMemories.length > 0) {
// 构建推荐记忆提示词
const recommendedMemoryPrompt = recommendedMemories
.map((memory, index) => `${index + 1}. ${memory.content} (来源: ${memory.source}, 原因: ${memory.reason})`)
.join('\n')
// 按重要性排序
recommendedMemories.sort((a, b) => {
const memoryA = memories.find(m => m.content === a.content) || shortMemories.find(m => m.content === a.content)
const memoryB = memories.find(m => m.content === b.content) || shortMemories.find(m => m.content === b.content)
const importanceA = memoryA?.importance || 0.5
const importanceB = memoryB?.importance || 0.5
return importanceB - importanceA
})
// 构建更自然的提示词
const recommendedMemoryPrompt = `在与用户交流时,请考虑以下关于用户的重要信息:\n\n${recommendedMemories
.map((memory) => `- ${memory.content}`)
.join('\n')}`
console.log('[Memory] Contextual memory recommendations:', recommendedMemoryPrompt)
// 添加推荐记忆到提示词
result = `${result}\n\n当前对话的相关记忆(按相关性排序):\n${recommendedMemoryPrompt}`
result = `${result}\n\n${recommendedMemoryPrompt}`
hasContent = true
}
}
@ -1195,11 +1408,19 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
}
if (topicShortMemories.length > 0) {
const shortMemoryPrompt = topicShortMemories.map((memory) => `- ${memory.content}`).join('\n')
// 按重要性排序
topicShortMemories.sort((a, b) => {
const importanceA = a.importance || 0.5
const importanceB = b.importance || 0.5
return importanceB - importanceA
})
// 构建更自然的短期记忆提示词
const shortMemoryPrompt = `关于当前对话,请记住以下重要信息:\n\n${topicShortMemories.map((memory) => `- ${memory.content}`).join('\n')}`
console.log('[Memory] Short memory prompt:', shortMemoryPrompt)
// 添加短记忆到提示词
result = `${result}\n\n当前对话的短期记忆(非常重要):\n${shortMemoryPrompt}`
result = `${result}\n\n${shortMemoryPrompt}`
hasContent = true
}
}
@ -1252,12 +1473,22 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
}
if (activeMemories.length > 0) {
// 按重要性对所有记忆进行排序
activeMemories.sort((a, b) => {
const importanceA = a.importance || 0.5
const importanceB = b.importance || 0.5
return importanceB - importanceA
})
// 按列表分组构建记忆提示词
let memoryPrompt = ''
// 构建更自然的开头
memoryPrompt = `请考虑以下关于用户的重要背景信息:\n\n`
// 如果只有一个激活列表,直接列出记忆
if (activeListIds.length === 1) {
memoryPrompt = activeMemories.map((memory) => `- ${memory.content}`).join('\n')
memoryPrompt += activeMemories.map((memory) => `- ${memory.content}`).join('\n')
} else {
// 如果有多个激活列表,按列表分组
for (const listId of activeListIds) {
@ -1276,7 +1507,7 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
console.log('[Memory] Long-term memory prompt:', memoryPrompt)
// 添加到系统提示词
result = `${result}\n\n用户的长期记忆:\n${memoryPrompt}`
result = `${result}\n\n${memoryPrompt}`
hasContent = true
}
}
@ -1288,5 +1519,20 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
console.log('[Memory] No memories to apply')
}
// 添加历史对话上下文
if (topicId) {
try {
const { analyzeAndSelectHistoricalContext } = await import('./HistoricalContextService')
const historicalContext = await analyzeAndSelectHistoricalContext(topicId)
if (historicalContext) {
console.log('[Memory] Adding historical context from topic:', historicalContext.sourceTopicId)
result = `${result}\n\n以下是之前的相关对话可能对回答当前问题有帮助\n\n${historicalContext.content}`
}
} catch (error) {
console.error('[Memory] Error adding historical context:', error)
}
}
return result
}

View File

@ -1,7 +1,7 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
import { nanoid } from 'nanoid'
import log from 'electron-log'
import store from '@renderer/store'
import store, { RootState } from '@renderer/store'
// 记忆列表接口
export interface MemoryList {
@ -95,6 +95,7 @@ export interface MemoryState {
autoAnalyze: boolean // 是否自动分析
analyzeModel: string | null // 用于长期记忆分析的模型ID
shortMemoryAnalyzeModel: string | null // 用于短期记忆分析的模型ID
historicalContextAnalyzeModel: string | null // 用于历史对话上下文分析的模型ID
vectorizeModel: string | null // 用于向量化的模型ID
lastAnalyzeTime: number | null // 上次分析时间
isAnalyzing: boolean // 是否正在分析
@ -149,6 +150,7 @@ const initialState: MemoryState = {
autoAnalyze: true,
analyzeModel: 'gpt-3.5-turbo', // 设置默认长期记忆分析模型
shortMemoryAnalyzeModel: 'gpt-3.5-turbo', // 设置默认短期记忆分析模型
historicalContextAnalyzeModel: 'gpt-3.5-turbo', // 设置默认历史对话上下文分析模型
vectorizeModel: 'gpt-3.5-turbo', // 设置默认向量化模型
lastAnalyzeTime: null,
isAnalyzing: false,
@ -210,6 +212,8 @@ const memorySlice = createSlice({
analyzedMessageIds?: string[]
lastMessageId?: string
topicId?: string
importance?: number // 新增重要性评分
keywords?: string[] // 新增关键词
}>
) => {
// 确保 memoryLists 存在
@ -232,7 +236,9 @@ const memorySlice = createSlice({
listId: listId,
analyzedMessageIds: action.payload.analyzedMessageIds,
lastMessageId: action.payload.lastMessageId,
topicId: action.payload.topicId
topicId: action.payload.topicId,
importance: action.payload.importance, // 添加重要性评分
keywords: action.payload.keywords // 添加关键词
}
// 确保 memories 存在
@ -291,6 +297,11 @@ const memorySlice = createSlice({
setShortMemoryAnalyzeModel: (state, action: PayloadAction<string | null>) => {
state.shortMemoryAnalyzeModel = action.payload
},
// 设置历史对话上下文分析模型
setHistoricalContextAnalyzeModel: (state, action: PayloadAction<string | null>) => {
state.historicalContextAnalyzeModel = action.payload
},
// 设置向量化模型
setVectorizeModel: (state, action: PayloadAction<string | null>) => {
state.vectorizeModel = action.payload
@ -442,6 +453,8 @@ const memorySlice = createSlice({
topicId: string
analyzedMessageIds?: string[]
lastMessageId?: string
importance?: number // 新增重要性评分
keywords?: string[] // 新增关键词
}>
) => {
const newShortMemory: ShortMemory = {
@ -450,7 +463,9 @@ const memorySlice = createSlice({
createdAt: new Date().toISOString(),
topicId: action.payload.topicId,
analyzedMessageIds: action.payload.analyzedMessageIds,
lastMessageId: action.payload.lastMessageId
lastMessageId: action.payload.lastMessageId,
importance: action.payload.importance, // 添加重要性评分
keywords: action.payload.keywords // 添加关键词
}
// 确保 shortMemories 存在
@ -468,6 +483,46 @@ const memorySlice = createSlice({
state.shortMemories = []
return
}
// 找到要删除的记忆
const memoryToDelete = state.shortMemories.find((memory) => memory.id === action.payload)
// 如果找到了要删除的记忆并且它有分析过的消息ID
if (memoryToDelete && memoryToDelete.analyzedMessageIds && memoryToDelete.analyzedMessageIds.length > 0) {
// 获取要删除的记忆的消息ID
const messageIdsToCheck = new Set(memoryToDelete.analyzedMessageIds)
// 检查其他记忆是否也引用了这些消息ID
// 创建一个映射记录每个消息ID被引用的次数
const messageIdReferences = new Map<string, number>()
// 统计所有记忆中每个消息ID的引用次数
state.shortMemories.forEach(memory => {
if (memory.id !== action.payload && memory.analyzedMessageIds) { // 排除要删除的记忆
memory.analyzedMessageIds.forEach(msgId => {
if (messageIdsToCheck.has(msgId)) { // 只关注要删除的记忆中的消息ID
messageIdReferences.set(msgId, (messageIdReferences.get(msgId) || 0) + 1)
}
})
}
})
// 找出没有被其他记忆引用的消息ID
const unusedMessageIds = Array.from(messageIdsToCheck).filter(msgId => !messageIdReferences.has(msgId))
if (unusedMessageIds.length > 0) {
console.log(`[Memory] Found ${unusedMessageIds.length} message IDs that are no longer referenced by any memory`)
// 将这些消息ID标记为未分析以便下次分析时重新分析这些消息
// 注意我们不需要显式地清除标记因为分析逻辑会检查消息ID是否在任何记忆的analyzedMessageIds中
// 如果消息ID不再被任何记忆引用它将自动被视为未分析
}
// 记录日志,方便调试
console.log(`[Memory] Deleting short memory with ${messageIdsToCheck.size} analyzed message IDs`)
}
// 删除记忆
state.shortMemories = state.shortMemories.filter((memory) => memory.id !== action.payload)
},
@ -717,6 +772,11 @@ const memorySlice = createSlice({
state.contextualRecommendationEnabled = action.payload
},
// 直接设置记忆数组(用于重置分析标记等操作)
setMemories: (state, action: PayloadAction<Memory[]>) => {
state.memories = action.payload
},
// 设置是否自动推荐记忆
setAutoRecommendMemories: (state, action: PayloadAction<boolean>) => {
state.autoRecommendMemories = action.payload
@ -749,7 +809,6 @@ const memorySlice = createSlice({
if (action.payload) {
// 更新状态中的记忆数据
state.memoryLists = action.payload.memoryLists || state.memoryLists
state.memories = action.payload.memories || state.memories
state.shortMemories = action.payload.shortMemories || state.shortMemories
// 更新模型选择
@ -763,6 +822,46 @@ const memorySlice = createSlice({
console.log('[Memory Reducer] Loaded short memory analyze model:', action.payload.shortMemoryAnalyzeModel)
}
log.info('Short-term memory data loaded into state')
}
})
.addCase(loadLongTermMemoryData.fulfilled, (state, action) => {
if (action.payload) {
// 更新状态中的长期记忆数据
state.memoryLists = action.payload.memoryLists || state.memoryLists
state.memories = action.payload.memories || state.memories
// 更新模型选择
if (action.payload.analyzeModel) {
state.analyzeModel = action.payload.analyzeModel
console.log('[Memory Reducer] Loaded long-term analyze model:', action.payload.analyzeModel)
}
// 自动选择默认的记忆列表
if (!state.currentListId && state.memoryLists && state.memoryLists.length > 0) {
// 先尝试找到一个isActive为true的列表
const activeList = state.memoryLists.find(list => list.isActive)
if (activeList) {
state.currentListId = activeList.id
console.log('[Memory Reducer] Auto-selected active memory list:', activeList.name)
} else {
// 如果没有激活的列表,使用第一个列表
state.currentListId = state.memoryLists[0].id
console.log('[Memory Reducer] Auto-selected first memory list:', state.memoryLists[0].name)
}
}
log.info('Long-term memory data loaded into state')
if (action.payload.historicalContextAnalyzeModel) {
state.historicalContextAnalyzeModel = action.payload.historicalContextAnalyzeModel
console.log('[Memory Reducer] Loaded historical context analyze model:', action.payload.historicalContextAnalyzeModel)
} else {
// 如果文件中没有historicalContextAnalyzeModel使用shortMemoryAnalyzeModel或analyzeModel作为默认值
state.historicalContextAnalyzeModel = state.shortMemoryAnalyzeModel || state.analyzeModel
console.log('[Memory Reducer] Using default model for historical context:', state.historicalContextAnalyzeModel)
}
if (action.payload.vectorizeModel) {
state.vectorizeModel = action.payload.vectorizeModel
console.log('[Memory Reducer] Loaded vectorize model:', action.payload.vectorizeModel)
@ -782,6 +881,7 @@ export const {
setAutoAnalyze,
setAnalyzeModel,
setShortMemoryAnalyzeModel,
setHistoricalContextAnalyzeModel,
setVectorizeModel,
setAnalyzing,
importMemories,
@ -791,6 +891,7 @@ export const {
editMemoryList,
setCurrentMemoryList,
toggleMemoryListActive,
setMemories,
// 短记忆相关的action
addShortMemory,
deleteShortMemory,
@ -850,24 +951,65 @@ export const loadMemoryData = createAsyncThunk(
// 保存记忆数据的异步 thunk
export const saveMemoryData = createAsyncThunk(
'memory/saveData',
async (data: Partial<MemoryState>) => {
async (data: Partial<MemoryState> & { forceOverwrite?: boolean }) => {
const { forceOverwrite, ...memoryData } = data
try {
console.log('[Memory] Saving memory data to file...', Object.keys(data))
// 确保数据完整性
const state = store.getState().memory
const completeData = {
...data,
// 如果没有提供这些字段,则使用当前状态中的值
memoryLists: data.memoryLists || state.memoryLists,
memories: data.memories || state.memories,
shortMemories: data.shortMemories || state.shortMemories,
analyzeModel: data.analyzeModel || state.analyzeModel,
shortMemoryAnalyzeModel: data.shortMemoryAnalyzeModel || state.shortMemoryAnalyzeModel,
vectorizeModel: data.vectorizeModel || state.vectorizeModel
// 如果是强制覆盖模式,直接使用传入的数据,不合并当前状态
if (forceOverwrite) {
console.log('[Memory] Force overwrite mode enabled, using provided data directly')
const result = await window.api.memory.saveData(memoryData, forceOverwrite)
console.log('[Memory] Memory data saved successfully (force overwrite)')
return result
}
const result = await window.api.memory.saveData(completeData)
// 非强制覆盖模式,确保数据完整性
const state = store.getState().memory
// 保存所有设置,而不仅仅是特定字段
// 创建一个包含所有设置的对象
const completeData = {
// 基本设置
isActive: memoryData.isActive !== undefined ? memoryData.isActive : state.isActive,
shortMemoryActive: memoryData.shortMemoryActive !== undefined ? memoryData.shortMemoryActive : state.shortMemoryActive,
autoAnalyze: memoryData.autoAnalyze !== undefined ? memoryData.autoAnalyze : state.autoAnalyze,
// 模型选择
analyzeModel: memoryData.analyzeModel || state.analyzeModel,
shortMemoryAnalyzeModel: memoryData.shortMemoryAnalyzeModel || state.shortMemoryAnalyzeModel,
historicalContextAnalyzeModel: memoryData.historicalContextAnalyzeModel || state.historicalContextAnalyzeModel,
vectorizeModel: memoryData.vectorizeModel || state.vectorizeModel,
// 记忆数据
memoryLists: memoryData.memoryLists || state.memoryLists,
shortMemories: memoryData.shortMemories || state.shortMemories,
currentListId: memoryData.currentListId || state.currentListId,
// 自适应分析相关
adaptiveAnalysisEnabled: memoryData.adaptiveAnalysisEnabled !== undefined ? memoryData.adaptiveAnalysisEnabled : state.adaptiveAnalysisEnabled,
analysisFrequency: memoryData.analysisFrequency !== undefined ? memoryData.analysisFrequency : state.analysisFrequency,
analysisDepth: memoryData.analysisDepth || state.analysisDepth,
// 用户关注点相关
interestTrackingEnabled: memoryData.interestTrackingEnabled !== undefined ? memoryData.interestTrackingEnabled : state.interestTrackingEnabled,
// 性能监控相关
monitoringEnabled: memoryData.monitoringEnabled !== undefined ? memoryData.monitoringEnabled : state.monitoringEnabled,
// 智能优先级与时效性管理相关
priorityManagementEnabled: memoryData.priorityManagementEnabled !== undefined ? memoryData.priorityManagementEnabled : state.priorityManagementEnabled,
decayEnabled: memoryData.decayEnabled !== undefined ? memoryData.decayEnabled : state.decayEnabled,
freshnessEnabled: memoryData.freshnessEnabled !== undefined ? memoryData.freshnessEnabled : state.freshnessEnabled,
decayRate: memoryData.decayRate !== undefined ? memoryData.decayRate : state.decayRate,
// 上下文感知记忆推荐相关
contextualRecommendationEnabled: memoryData.contextualRecommendationEnabled !== undefined ? memoryData.contextualRecommendationEnabled : state.contextualRecommendationEnabled,
autoRecommendMemories: memoryData.autoRecommendMemories !== undefined ? memoryData.autoRecommendMemories : state.autoRecommendMemories,
recommendationThreshold: memoryData.recommendationThreshold !== undefined ? memoryData.recommendationThreshold : state.recommendationThreshold,
}
const result = await window.api.memory.saveData(completeData, forceOverwrite)
console.log('[Memory] Memory data saved successfully')
return result
} catch (error) {
@ -877,6 +1019,142 @@ export const saveMemoryData = createAsyncThunk(
}
)
// 加载长期记忆数据的异步 thunk
export const loadLongTermMemoryData = createAsyncThunk(
'memory/loadLongTermData',
async () => {
try {
console.log('[Long-term Memory] Loading long-term memory data from file...')
const data = await window.api.memory.loadLongTermData()
console.log('[Long-term Memory] Long-term memory data loaded successfully')
return data
} catch (error) {
console.error('[Long-term Memory] Failed to load long-term memory data:', error)
return null
}
}
)
// 保存长期记忆数据的异步 thunk
export const saveLongTermMemoryData = createAsyncThunk(
'memory/saveLongTermData',
async (data: Partial<MemoryState> & { forceOverwrite?: boolean }) => {
const { forceOverwrite, ...memoryData } = data
try {
console.log('[Long-term Memory] Saving long-term memory data to file...', Object.keys(data))
// 如果是强制覆盖模式,直接使用传入的数据,不合并当前状态
if (forceOverwrite) {
console.log('[Long-term Memory] Force overwrite mode enabled, using provided data directly')
const result = await window.api.memory.saveLongTermData(memoryData, forceOverwrite)
console.log('[Long-term Memory] Long-term memory data saved successfully (force overwrite)')
return result
}
// 非强制覆盖模式,确保数据完整性
const state = store.getState().memory
// 保存所有设置,而不仅仅是特定字段
// 创建一个包含所有设置的对象
const completeData = {
// 基本设置
isActive: memoryData.isActive !== undefined ? memoryData.isActive : state.isActive,
autoAnalyze: memoryData.autoAnalyze !== undefined ? memoryData.autoAnalyze : state.autoAnalyze,
// 模型选择
analyzeModel: memoryData.analyzeModel || state.analyzeModel,
// 记忆数据
memoryLists: memoryData.memoryLists || state.memoryLists,
memories: memoryData.memories || state.memories,
currentListId: memoryData.currentListId || state.currentListId,
// 自适应分析相关
adaptiveAnalysisEnabled: memoryData.adaptiveAnalysisEnabled !== undefined ? memoryData.adaptiveAnalysisEnabled : state.adaptiveAnalysisEnabled,
analysisFrequency: memoryData.analysisFrequency !== undefined ? memoryData.analysisFrequency : state.analysisFrequency,
analysisDepth: memoryData.analysisDepth || state.analysisDepth,
// 用户关注点相关
interestTrackingEnabled: memoryData.interestTrackingEnabled !== undefined ? memoryData.interestTrackingEnabled : state.interestTrackingEnabled,
// 性能监控相关
monitoringEnabled: memoryData.monitoringEnabled !== undefined ? memoryData.monitoringEnabled : state.monitoringEnabled,
// 智能优先级与时效性管理相关
priorityManagementEnabled: memoryData.priorityManagementEnabled !== undefined ? memoryData.priorityManagementEnabled : state.priorityManagementEnabled,
decayEnabled: memoryData.decayEnabled !== undefined ? memoryData.decayEnabled : state.decayEnabled,
freshnessEnabled: memoryData.freshnessEnabled !== undefined ? memoryData.freshnessEnabled : state.freshnessEnabled,
decayRate: memoryData.decayRate !== undefined ? memoryData.decayRate : state.decayRate,
// 上下文感知记忆推荐相关
contextualRecommendationEnabled: memoryData.contextualRecommendationEnabled !== undefined ? memoryData.contextualRecommendationEnabled : state.contextualRecommendationEnabled,
autoRecommendMemories: memoryData.autoRecommendMemories !== undefined ? memoryData.autoRecommendMemories : state.autoRecommendMemories,
recommendationThreshold: memoryData.recommendationThreshold !== undefined ? memoryData.recommendationThreshold : state.recommendationThreshold,
}
const result = await window.api.memory.saveLongTermData(completeData, forceOverwrite)
console.log('[Long-term Memory] Long-term memory data saved successfully')
return result
} catch (error) {
console.error('[Long-term Memory] Failed to save long-term memory data:', error)
return false
}
}
)
// 保存所有记忆设置的函数
export const saveAllMemorySettings = createAsyncThunk(
'memory/saveAllSettings',
async (_, { dispatch, getState }) => {
try {
const state = (getState() as RootState).memory
// 创建一个包含所有设置的对象,但不包含记忆内容和记忆列表
const settings = {
// 基本设置
isActive: state.isActive,
shortMemoryActive: state.shortMemoryActive,
autoAnalyze: state.autoAnalyze,
// 模型选择
analyzeModel: state.analyzeModel,
shortMemoryAnalyzeModel: state.shortMemoryAnalyzeModel,
historicalContextAnalyzeModel: state.historicalContextAnalyzeModel,
vectorizeModel: state.vectorizeModel,
// 自适应分析相关
adaptiveAnalysisEnabled: state.adaptiveAnalysisEnabled,
analysisFrequency: state.analysisFrequency,
analysisDepth: state.analysisDepth,
// 用户关注点相关
interestTrackingEnabled: state.interestTrackingEnabled,
// 性能监控相关
monitoringEnabled: state.monitoringEnabled,
// 智能优先级与时效性管理相关
priorityManagementEnabled: state.priorityManagementEnabled,
decayEnabled: state.decayEnabled,
freshnessEnabled: state.freshnessEnabled,
decayRate: state.decayRate,
// 上下文感知记忆推荐相关
contextualRecommendationEnabled: state.contextualRecommendationEnabled,
autoRecommendMemories: state.autoRecommendMemories,
recommendationThreshold: state.recommendationThreshold,
}
const result = await dispatch(saveMemoryData(settings)).unwrap()
console.log('[Memory] All memory settings saved successfully')
return result
} catch (error) {
console.error('[Memory] Failed to save all memory settings:', error)
throw error
}
}
)
// Middleware removed to prevent duplicate saves triggered by batch additions.
// Explicit saves should be handled where needed, e.g., at the end of analysis functions.
export default memorySlice.reducer

View File

@ -48,6 +48,7 @@ export interface SettingsState {
clickAssistantToShowTopic: boolean
autoCheckUpdate: boolean
renderInputMessageAsMarkdown: boolean
enableHistoricalContext: boolean // 是否启用历史对话上下文功能
codeShowLineNumbers: boolean
codeCollapsible: boolean
codeWrappable: boolean
@ -153,6 +154,7 @@ export const initialState: SettingsState = {
clickAssistantToShowTopic: true,
autoCheckUpdate: true,
renderInputMessageAsMarkdown: false,
enableHistoricalContext: false, // 默认禁用历史对话上下文功能
codeShowLineNumbers: false,
codeCollapsible: false,
codeWrappable: false,
@ -306,6 +308,10 @@ const settingsSlice = createSlice({
setRenderInputMessageAsMarkdown: (state, action: PayloadAction<boolean>) => {
state.renderInputMessageAsMarkdown = action.payload
},
setEnableHistoricalContext: (state, action: PayloadAction<boolean>) => {
state.enableHistoricalContext = action.payload
},
setClickAssistantToShowTopic: (state, action: PayloadAction<boolean>) => {
state.clickAssistantToShowTopic = action.payload
},
@ -512,6 +518,7 @@ export const {
setPasteLongTextAsFile,
setAutoCheckUpdate,
setRenderInputMessageAsMarkdown,
setEnableHistoricalContext,
setClickAssistantToShowTopic,
setWebdavHost,
setWebdavUser,

View File

@ -170,7 +170,7 @@ export const buildSystemPrompt = async (
// 应用内置记忆功能
console.log('[Prompt] Applying app memories to prompt')
// 直接将用户系统提示词传递给 applyMemoriesToPrompt让它添加记忆
appMemoriesPrompt = applyMemoriesToPrompt(userSystemPrompt)
appMemoriesPrompt = await applyMemoriesToPrompt(userSystemPrompt)
console.log('[Prompt] App memories prompt length:', appMemoriesPrompt.length - userSystemPrompt.length)
} catch (error) {
console.error('Error applying app memories:', error)