mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-25 11:20:07 +08:00
修复了一些bug
This commit is contained in:
parent
51fc167b2a
commit
370ee60537
@ -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'
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
5
src/preload/index.d.ts
vendored
5
src/preload/index.d.ts
vendored
@ -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>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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}</>
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "操作",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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') || '清除'}
|
||||
|
||||
@ -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
|
||||
@ -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`)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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') || '更新'}
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
241
src/renderer/src/services/HistoricalContextService.ts
Normal file
241
src/renderer/src/services/HistoricalContextService.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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. 对于超长对话(超过8万字),必须提供至少20-30条记忆条目
|
||||
7. 对于技术内容,请包含具体的文件名、路径、变量名、函数名等技术细节
|
||||
8. 对于代码相关的对话,请记录关键的代码片段和实现细节
|
||||
|
||||
注意:不要在输出中包含任何解释或其他格式的文本,只输出以短横线开头的记忆条目。如果对话内容简单,可以少于15条,但必须确保完整捕捉所有重要信息
|
||||
|
||||
请记住,您的分析应该非常详细,不要过于简化或概括。对于8万字的对话,100字的总结是远远不够的,应该提供至少500-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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user