新增信息id

This commit is contained in:
1600822305 2025-04-14 23:20:03 +08:00
parent 370ee60537
commit e38b18bb53
35 changed files with 1943 additions and 785 deletions

View File

@ -1,6 +1,6 @@
{
"name": "CherryStudio",
"version": "1.2.2",
"version": "1.2.2-batemo",
"private": true,
"description": "A powerful AI assistant for producer.",
"main": "./out/main/index.js",

View File

@ -1,3 +1,5 @@
import './services/MemoryFileService'
import { electronApp, optimizer } from '@electron-toolkit/utils'
import { replaceDevtoolsFont } from '@main/utils/windowUtil'
import { IpcChannel } from '@shared/IpcChannel'
@ -7,7 +9,6 @@ import Logger from 'electron-log'
import { registerIpc } from './ipc'
import { configManager } from './services/ConfigManager'
import './services/MemoryFileService'
import mcpService from './services/MCPService'
import { CHERRY_STUDIO_PROTOCOL, handleProtocolUrl, registerProtocolClient } from './services/ProtocolClient'
import { registerShortcuts } from './services/ShortcutService'

View File

@ -1,3 +1,5 @@
import './services/MemoryFileService'
import fs from 'node:fs'
import { isMac, isWin } from '@main/constant'
@ -18,7 +20,7 @@ import FileStorage from './services/FileStorage'
import { GeminiService } from './services/GeminiService'
import KnowledgeService from './services/KnowledgeService'
import mcpService from './services/MCPService'
import './services/MemoryFileService'
import { memoryFileService } from './services/MemoryFileService'
import * as NutstoreService from './services/NutstoreService'
import ObsidianVaultService from './services/ObsidianVaultService'
import { ProxyConfig, proxyManager } from './services/ProxyManager'
@ -26,7 +28,6 @@ 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'

View File

@ -8,9 +8,9 @@ import * as fs from 'fs-extra'
import * as path from 'path'
import { createClient, CreateDirectoryOptions, FileStat } from 'webdav'
import { getConfigDir } from '../utils/file'
import WebDav from './WebDav'
import { windowService } from './WindowService'
import { getConfigDir } from '../utils/file'
class BackupManager {
private tempDir = path.join(app.getPath('temp'), 'cherry-studio', 'backup', 'temp')

View File

@ -1,7 +1,8 @@
import log from 'electron-log'
import { promises as fs } from 'fs'
import path from 'path'
import { getConfigDir } from '../utils/file'
import log from 'electron-log'
// 定义记忆文件路径
const memoryDataPath = path.join(getConfigDir(), 'memory-data.json')
@ -30,11 +31,13 @@ export class MemoryFileService {
// 文件不存在,创建默认文件
log.info('Memory data file does not exist, creating default file')
const defaultData = {
memoryLists: [{
id: 'default',
name: '默认列表',
isActive: true
}],
memoryLists: [
{
id: 'default',
name: '默认列表',
isActive: true
}
],
shortMemories: [],
analyzeModel: 'gpt-3.5-turbo',
shortMemoryAnalyzeModel: 'gpt-3.5-turbo',
@ -150,13 +153,15 @@ export class MemoryFileService {
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
}],
memoryLists: [
{
id: 'default',
name: '默认列表',
isActive: true,
createdAt: now,
updatedAt: now
}
],
memories: [],
currentListId: 'default',
analyzeModel: 'gpt-3.5-turbo'

View File

@ -183,7 +183,8 @@ const api = {
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)
saveLongTermData: (data: any, forceOverwrite: boolean = false) =>
ipcRenderer.invoke(IpcChannel.LongTermMemory_SaveData, data, forceOverwrite)
}
}

View File

@ -1,6 +1,7 @@
.markdown {
color: var(--color-text);
line-height: 1.6;
-webkit-user-select: text;
user-select: text;
word-break: break-word;

View File

@ -3,24 +3,24 @@ import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store'
import {
clearShortMemories,
loadMemoryData,
loadLongTermMemoryData,
setCurrentMemoryList,
setMemoryActive,
setShortMemoryActive,
setAutoAnalyze,
loadMemoryData,
setAdaptiveAnalysisEnabled,
setAnalysisFrequency,
setAnalysisDepth,
setAnalysisFrequency,
setAutoAnalyze,
setAutoRecommendMemories,
setContextualRecommendationEnabled,
setCurrentMemoryList,
setDecayEnabled,
setDecayRate,
setFreshnessEnabled,
setInterestTrackingEnabled,
setMemoryActive,
setMonitoringEnabled,
setPriorityManagementEnabled,
setDecayEnabled,
setFreshnessEnabled,
setDecayRate,
setContextualRecommendationEnabled,
setAutoRecommendMemories,
setRecommendationThreshold
setRecommendationThreshold,
setShortMemoryActive
} from '@renderer/store/memory'
import { FC, ReactNode, useEffect, useRef } from 'react'
@ -76,33 +76,38 @@ const MemoryProvider: FC<MemoryProviderProps> = ({ children }) => {
if (data.autoAnalyze !== undefined) dispatch(setAutoAnalyze(data.autoAnalyze))
// 自适应分析相关
if (data.adaptiveAnalysisEnabled !== undefined) dispatch(setAdaptiveAnalysisEnabled(data.adaptiveAnalysisEnabled))
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.interestTrackingEnabled !== undefined)
dispatch(setInterestTrackingEnabled(data.interestTrackingEnabled))
// 性能监控相关
if (data.monitoringEnabled !== undefined) dispatch(setMonitoringEnabled(data.monitoringEnabled))
// 智能优先级与时效性管理相关
if (data.priorityManagementEnabled !== undefined) dispatch(setPriorityManagementEnabled(data.priorityManagementEnabled))
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.contextualRecommendationEnabled !== undefined)
dispatch(setContextualRecommendationEnabled(data.contextualRecommendationEnabled))
if (data.autoRecommendMemories !== undefined) dispatch(setAutoRecommendMemories(data.autoRecommendMemories))
if (data.recommendationThreshold !== undefined) dispatch(setRecommendationThreshold(data.recommendationThreshold))
if (data.recommendationThreshold !== undefined)
dispatch(setRecommendationThreshold(data.recommendationThreshold))
console.log('[MemoryProvider] Memory settings loaded successfully')
} else {
console.log('[MemoryProvider] No short-term memory data loaded or loading failed')
}
})
.catch(error => {
.catch((error) => {
console.error('[MemoryProvider] Error loading short-term memory data:', error)
})
@ -117,7 +122,7 @@ const MemoryProvider: FC<MemoryProviderProps> = ({ children }) => {
const state = store.getState().memory
if (!state.currentListId && state.memoryLists && state.memoryLists.length > 0) {
// 先尝试找到一个isActive为true的列表
const activeList = state.memoryLists.find(list => list.isActive)
const activeList = state.memoryLists.find((list) => list.isActive)
if (activeList) {
console.log('[MemoryProvider] Auto-selecting active memory list:', activeList.name)
dispatch(setCurrentMemoryList(activeList.id))
@ -132,7 +137,7 @@ const MemoryProvider: FC<MemoryProviderProps> = ({ children }) => {
console.log('[MemoryProvider] No long-term memory data loaded or loading failed')
}
})
.catch(error => {
.catch((error) => {
console.error('[MemoryProvider] Error loading long-term memory data:', error)
})
}, [dispatch])
@ -189,9 +194,9 @@ const MemoryProvider: FC<MemoryProviderProps> = ({ children }) => {
const state = store.getState().memory
if (state.memoryLists && state.memoryLists.length > 0) {
// 如果没有选中的记忆列表,或者选中的列表不存在
if (!state.currentListId || !state.memoryLists.some(list => list.id === state.currentListId)) {
if (!state.currentListId || !state.memoryLists.some((list) => list.id === state.currentListId)) {
// 先尝试找到一个isActive为true的列表
const activeList = state.memoryLists.find(list => list.isActive)
const activeList = state.memoryLists.find((list) => list.isActive)
if (activeList) {
console.log('[MemoryProvider] Setting active memory list:', activeList.name)
dispatch(setCurrentMemoryList(activeList.id))

View File

@ -5,9 +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, message } from 'antd'
import { useState, useCallback } from 'react'
import { Button, Card, Col, Empty, Input, List, message, Modal, Row, Statistic, Tooltip } from 'antd'
import _ from 'lodash'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -49,79 +49,88 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
const [isAnalyzing, setIsAnalyzing] = useState(false)
// 添加新的短记忆 - 使用防抖减少频繁更新
const handleAddMemory = useCallback(_.debounce(() => {
if (newMemoryContent.trim() && topicId) {
addShortMemoryItem(newMemoryContent.trim(), topicId)
setNewMemoryContent('') // 清空输入框
}
}, 300), [newMemoryContent, topicId])
const handleAddMemory = useCallback(
_.debounce(() => {
if (newMemoryContent.trim() && topicId) {
addShortMemoryItem(newMemoryContent.trim(), topicId)
setNewMemoryContent('') // 清空输入框
}
}, 300),
[newMemoryContent, topicId]
)
// 手动分析对话内容 - 使用节流避免频繁分析操作
const handleAnalyzeConversation = useCallback(_.throttle(async () => {
if (!topicId || !shortMemoryActive) return
const handleAnalyzeConversation = useCallback(
_.throttle(async () => {
if (!topicId || !shortMemoryActive) return
setIsAnalyzing(true)
try {
const result = await analyzeAndAddShortMemories(topicId)
if (result) {
// 如果有新的短期记忆被添加
Modal.success({
title: t('settings.memory.shortMemoryAnalysisSuccess') || '分析成功',
content: t('settings.memory.shortMemoryAnalysisSuccessContent') || '已成功提取并添加重要信息到短期记忆'
})
} else {
// 如果没有新的短期记忆被添加
Modal.info({
title: t('settings.memory.shortMemoryAnalysisNoNew') || '无新信息',
content: t('settings.memory.shortMemoryAnalysisNoNewContent') || '未发现新的重要信息或所有信息已存在'
setIsAnalyzing(true)
try {
const result = await analyzeAndAddShortMemories(topicId)
if (result) {
// 如果有新的短期记忆被添加
Modal.success({
title: t('settings.memory.shortMemoryAnalysisSuccess') || '分析成功',
content: t('settings.memory.shortMemoryAnalysisSuccessContent') || '已成功提取并添加重要信息到短期记忆'
})
} else {
// 如果没有新的短期记忆被添加
Modal.info({
title: t('settings.memory.shortMemoryAnalysisNoNew') || '无新信息',
content: t('settings.memory.shortMemoryAnalysisNoNewContent') || '未发现新的重要信息或所有信息已存在'
})
}
} catch (error) {
console.error('Failed to analyze conversation:', error)
Modal.error({
title: t('settings.memory.shortMemoryAnalysisError') || '分析失败',
content: t('settings.memory.shortMemoryAnalysisErrorContent') || '分析对话内容时出错'
})
} finally {
setIsAnalyzing(false)
}
} catch (error) {
console.error('Failed to analyze conversation:', error)
Modal.error({
title: t('settings.memory.shortMemoryAnalysisError') || '分析失败',
content: t('settings.memory.shortMemoryAnalysisErrorContent') || '分析对话内容时出错'
})
} finally {
setIsAnalyzing(false)
}
}, 1000), [topicId, shortMemoryActive, t])
}, 1000),
[topicId, shortMemoryActive, t]
)
// 删除短记忆 - 直接删除无需确认,使用节流避免频繁删除操作
const handleDeleteMemory = useCallback(_.throttle(async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredShortMemories = state.shortMemories.filter(memory => memory.id !== id)
const handleDeleteMemory = useCallback(
_.throttle(async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredShortMemories = state.shortMemories.filter((memory) => memory.id !== id)
// 执行删除操作
dispatch(deleteShortMemory(id))
// 执行删除操作
dispatch(deleteShortMemory(id))
// 直接使用 window.api.memory.saveData 方法保存过滤后的列表
try {
// 加载当前文件数据
const currentData = await window.api.memory.loadData()
// 直接使用 window.api.memory.saveData 方法保存过滤后的列表
try {
// 加载当前文件数据
const currentData = await window.api.memory.loadData()
// 替换 shortMemories 数组
const newData = {
...currentData,
shortMemories: filteredShortMemories
}
// 替换 shortMemories 数组
const newData = {
...currentData,
shortMemories: filteredShortMemories
}
// 使用 true 参数强制覆盖文件
const result = await window.api.memory.saveData(newData, true)
// 使用 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}`)
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') || '删除失败')
}
} catch (error) {
console.error('[ShortMemoryPopup] Failed to delete short memory:', error)
message.error(t('settings.memory.deleteError') || '删除失败')
}
}, 500), [dispatch, t])
}, 500),
[dispatch, t]
)
const onClose = () => {
setOpen(false)
@ -157,7 +166,10 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
disabled={!shortMemoryActive || !newMemoryContent.trim() || !topicId}>
{t('settings.memory.addShortMemory')}
</Button>
<Button onClick={() => handleAnalyzeConversation()} loading={isAnalyzing} disabled={!shortMemoryActive || !topicId}>
<Button
onClick={() => handleAnalyzeConversation()}
loading={isAnalyzing}
disabled={!shortMemoryActive || !topicId}>
{t('settings.memory.analyzeConversation') || '分析对话'}
</Button>
</ButtonGroup>

View File

@ -498,6 +498,12 @@
"copied": "Copied!",
"copy.failed": "Copy failed",
"copy.success": "Copied!",
"copy_id": "Copy Message ID",
"id_copied": "Message ID copied",
"id_found": "Original message found",
"reference": "Reference message",
"reference.error": "Failed to find original message",
"referenced_message": "Referenced Message",
"error.chunk_overlap_too_large": "Chunk overlap cannot be greater than chunk size",
"error.dimension_too_large": "Content size is too large",
"error.enter.api.host": "Please enter your API host first",
@ -1055,6 +1061,8 @@
"startingAnalysis": "Starting analysis...",
"cannotAnalyze": "Cannot analyze, please check settings",
"resetAnalyzingState": "Reset Analysis State",
"filterSensitiveInfo": "Filter Sensitive Information",
"filterSensitiveInfoTip": "When enabled, memory function will not extract API keys, passwords, or other sensitive information",
"resetLongTermMemory": "Reset Analysis Markers",
"resetLongTermMemorySuccess": "Long-term memory analysis markers reset",
"resetLongTermMemoryNoChange": "No analysis markers to reset",

View File

@ -498,6 +498,12 @@
"copied": "已复制",
"copy.failed": "复制失败",
"copy.success": "复制成功",
"copy_id": "复制消息ID",
"id_copied": "消息ID已复制",
"id_found": "已找到原始消息",
"reference": "引用消息",
"reference.error": "无法找到原始消息",
"referenced_message": "引用的消息",
"error.chunk_overlap_too_large": "分段重叠不能大于分段大小",
"error.dimension_too_large": "内容尺寸过大",
"error.enter.api.host": "请输入您的 API 地址",
@ -1060,6 +1066,8 @@
"startingAnalysis": "开始分析...",
"cannotAnalyze": "无法分析,请检查设置",
"resetAnalyzingState": "重置分析状态",
"filterSensitiveInfo": "过滤敏感信息",
"filterSensitiveInfoTip": "启用后记忆功能将不会提取API密钥、密码等敏感信息",
"resetLongTermMemory": "重置分析标记",
"resetLongTermMemorySuccess": "长期记忆分析标记已重置",
"resetLongTermMemoryNoChange": "没有需要重置的分析标记",

View File

@ -27,7 +27,7 @@ import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
import { addAssistantMessagesToTopic, getDefaultTopic } from '@renderer/services/AssistantService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import FileManager from '@renderer/services/FileManager'
import { checkRateLimit, getUserMessage } from '@renderer/services/MessagesService'
import { checkRateLimit, findMessageById, getUserMessage } from '@renderer/services/MessagesService'
import { getModelUniqId } from '@renderer/services/ModelService'
import { estimateMessageUsage, estimateTextTokens as estimateTxtTokens } from '@renderer/services/TokenService'
import { translateText } from '@renderer/services/TranslateService'
@ -180,9 +180,135 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE)
try {
// 检查用户输入是否包含消息ID
const uuidRegex = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i
// 从文本中提取所有消息ID
const matches = text.match(new RegExp(uuidRegex, 'g'))
// 如果只有ID且没有其他内容则直接查找原始消息
if (matches && matches.length > 0 && text.trim() === matches.join(' ')) {
try {
// 创建引用消息
const userMessage = getUserMessage({ assistant, topic, type: 'text', content: '' })
userMessage.referencedMessages = []
// 处理所有匹配到的ID
let foundAnyMessage = false
for (const messageId of matches) {
console.log(`[引用消息] 尝试查找消息ID: ${messageId}`)
const originalMessage = await findMessageById(messageId)
if (originalMessage) {
userMessage.referencedMessages.push({
id: originalMessage.id,
content: originalMessage.content,
role: originalMessage.role,
createdAt: originalMessage.createdAt
})
foundAnyMessage = true
console.log(`[引用消息] 找到消息ID: ${messageId}`)
} else {
console.log(`[引用消息] 未找到消息ID: ${messageId}`)
}
}
if (foundAnyMessage) {
// 发送引用消息
userMessage.usage = await estimateMessageUsage(userMessage)
currentMessageId.current = userMessage.id
dispatch(
_sendMessage(userMessage, assistant, topic, {
mentions: mentionModels
})
)
// 清空输入框
setText('')
setFiles([])
setTimeout(() => setText(''), 500)
setTimeout(() => resizeTextArea(), 0)
setExpend(false)
window.message.success({
content:
t('message.ids_found', { count: userMessage.referencedMessages.length }) ||
`已找到${userMessage.referencedMessages.length}条原始消息`,
key: 'message-id-found'
})
return
} else {
window.message.error({
content: t('message.id_not_found') || '未找到原始消息',
key: 'message-id-not-found'
})
}
} catch (error) {
console.error(`[引用消息] 查找消息ID时出错:`, error)
window.message.error({ content: t('message.id_error') || '查找原始消息时出错', key: 'message-id-error' })
}
}
// 如果不是单独的ID或者没有找到原始消息则正常发送消息
// 先检查消息内容是否包含消息ID如果是则将其替换为空字符串
let messageContent = text
// 如果消息内容包含消息ID则将其替换为空字符串
if (matches && matches.length > 0) {
// 检查是否是纯消息ID
const isOnlyUUID = text.trim() === matches[0]
if (isOnlyUUID) {
messageContent = ''
} else {
// 如果消息内容包含消息ID则将消息ID替换为空字符串
for (const match of matches) {
messageContent = messageContent.replace(match, '')
}
// 去除多余的空格
messageContent = messageContent.replace(/\s+/g, ' ').trim()
}
}
// Dispatch the sendMessage action with all options
const uploadedFiles = await FileManager.uploadFiles(files)
const userMessage = getUserMessage({ assistant, topic, type: 'text', content: text })
const userMessage = getUserMessage({ assistant, topic, type: 'text', content: messageContent })
// 如果消息内容包含消息ID则添加引用
if (matches && matches.length > 0) {
try {
// 初始化引用消息数组
userMessage.referencedMessages = []
// 处理所有匹配到的ID
for (const messageId of matches) {
console.log(`[引用消息] 尝试查找消息ID作为引用: ${messageId}`)
const originalMessage = await findMessageById(messageId)
if (originalMessage) {
userMessage.referencedMessages.push({
id: originalMessage.id,
content: originalMessage.content,
role: originalMessage.role,
createdAt: originalMessage.createdAt
})
console.log(`[引用消息] 找到消息ID作为引用: ${messageId}`)
} else {
console.log(`[引用消息] 未找到消息ID作为引用: ${messageId}`)
}
}
// 如果找到了引用消息,显示成功提示
if (userMessage.referencedMessages.length > 0) {
window.message.success({
content:
t('message.ids_found', { count: userMessage.referencedMessages.length }) ||
`已找到${userMessage.referencedMessages.length}条原始消息`,
key: 'message-id-found'
})
}
} catch (error) {
console.error(`[引用消息] 查找消息ID作为引用时出错:`, error)
}
}
if (uploadedFiles) {
userMessage.files = uploadedFiles
@ -388,6 +514,20 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
const isEnterPressed = event.keyCode == 13
// 检查是否是消息ID格式
if (isEnterPressed && !event.shiftKey) {
const uuidRegex = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i
const currentText = text.trim()
const isUUID = uuidRegex.test(currentText) && currentText.length === 36
if (isUUID) {
// 如果是消息ID格式则不显示ID在对话中
event.preventDefault()
sendMessage()
return
}
}
// 按下Tab键自动选中${xxx}
if (event.key === 'Tab' && inputFocus) {
event.preventDefault()
@ -536,7 +676,19 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newText = e.target.value
setText(newText)
// 检查是否包含UUID格式的消息ID
const uuidRegex = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i
const matches = newText.match(new RegExp(uuidRegex, 'g'))
// 如果输入的内容只是一个UUID不更新文本框内容直接处理引用
if (matches && matches.length === 1 && newText.trim() === matches[0]) {
// 不立即更新文本框,等待用户按下回车键时再处理
setText(newText)
} else {
// 正常更新文本框内容
setText(newText)
}
const textArea = textareaRef.current?.resizableTextArea?.textArea
const cursorPosition = textArea?.selectionStart ?? 0
@ -559,8 +711,41 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
async (event: ClipboardEvent) => {
const clipboardText = event.clipboardData?.getData('text')
if (clipboardText) {
// Prioritize the text when pasting.
// handled by the default event
// 检查粘贴的内容是否是消息ID
const uuidRegex = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i
const isUUID = uuidRegex.test(clipboardText.trim()) && clipboardText.trim().length === 36
if (isUUID) {
// 如果是消息ID则阻止默认粘贴行为自定义处理
event.preventDefault()
// 获取当前文本框的内容和光标位置
const textArea = textareaRef.current?.resizableTextArea?.textArea
if (textArea) {
const currentText = textArea.value
const cursorPosition = textArea.selectionStart
const cursorEnd = textArea.selectionEnd
// 如果有选中文本,则替换选中文本;否则在光标位置插入
const newText =
currentText.substring(0, cursorPosition) + clipboardText.trim() + currentText.substring(cursorEnd)
setText(newText)
// 将光标移到插入的ID后面
const newCursorPosition = cursorPosition + clipboardText.trim().length
setTimeout(() => {
if (textArea) {
textArea.focus()
textArea.setSelectionRange(newCursorPosition, newCursorPosition)
}
}, 0)
} else {
// 如果无法获取textArea则直接设置文本
setText(clipboardText.trim())
}
}
// 其他文本内容由默认事件处理
} else {
for (const file of event.clipboardData?.files || []) {
event.preventDefault()

View File

@ -61,18 +61,18 @@ const Markdown: FC<Props> = ({ message }) => {
think: (props: any) => {
// 将think标签内容渲染为带样式的div
return (
<div className="thinking-content" style={{
backgroundColor: 'rgba(0, 0, 0, 0.05)',
padding: '10px 15px',
borderRadius: '8px',
marginBottom: '15px',
borderLeft: '3px solid var(--color-primary)',
fontStyle: 'italic',
color: 'var(--color-text-2)'
}}>
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>
:
</div>
<div
className="thinking-content"
style={{
backgroundColor: 'rgba(0, 0, 0, 0.05)',
padding: '10px 15px',
borderRadius: '8px',
marginBottom: '15px',
borderLeft: '3px solid var(--color-primary)',
fontStyle: 'italic',
color: 'var(--color-text-2)'
}}>
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>:</div>
{props.children}
</div>
)

View File

@ -8,6 +8,7 @@ import { getModelUniqId } from '@renderer/services/ModelService'
import { Assistant, Message, Topic } from '@renderer/types'
import { classNames } from '@renderer/utils'
import { Divider, Dropdown } from 'antd'
import { ItemType } from 'antd/es/menu/interface'
import { Dispatch, FC, memo, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -65,7 +66,11 @@ const MessageItem: FC<Props> = ({
const handleContextMenu = useCallback((e: React.MouseEvent) => {
e.preventDefault()
const _selectedText = window.getSelection()?.toString()
const _selectedText = window.getSelection()?.toString() || ''
// 无论是否选中文本,都设置上下文菜单位置
setContextMenuPosition({ x: e.clientX, y: e.clientY })
if (_selectedText) {
const quotedText =
_selectedText
@ -73,8 +78,10 @@ const MessageItem: FC<Props> = ({
.map((line) => `> ${line}`)
.join('\n') + '\n-------------'
setSelectedQuoteText(quotedText)
setContextMenuPosition({ x: e.clientX, y: e.clientY })
setSelectedText(_selectedText)
} else {
setSelectedQuoteText('')
setSelectedText('')
}
}, [])
@ -134,7 +141,7 @@ const MessageItem: FC<Props> = ({
{contextMenuPosition && (
<Dropdown
overlayStyle={{ left: contextMenuPosition.x, top: contextMenuPosition.y, zIndex: 1000 }}
menu={{ items: getContextMenuItems(t, selectedQuoteText, selectedText) }}
menu={{ items: getContextMenuItems(t, selectedQuoteText, selectedText, message) }}
open={true}
trigger={['contextMenu']}>
<div />
@ -181,23 +188,46 @@ const getMessageBackground = (isBubbleStyle: boolean, isAssistantMessage: boolea
: undefined
}
const getContextMenuItems = (t: (key: string) => string, selectedQuoteText: string, selectedText: string) => [
{
key: 'copy',
label: t('common.copy'),
onClick: () => {
navigator.clipboard.writeText(selectedText)
window.message.success({ content: t('message.copied'), key: 'copy-message' })
}
},
{
key: 'quote',
label: t('chat.message.quote'),
onClick: () => {
EventEmitter.emit(EVENT_NAMES.QUOTE_TEXT, selectedQuoteText)
}
const getContextMenuItems = (
t: (key: string) => string,
selectedQuoteText: string,
selectedText: string,
message: Message
): ItemType[] => {
const items: ItemType[] = []
// 只有在选中文本时,才添加复制和引用选项
if (selectedText) {
items.push({
key: 'copy',
label: t('common.copy'),
onClick: () => {
navigator.clipboard.writeText(selectedText)
window.message.success({ content: t('message.copied'), key: 'copy-message' })
}
})
items.push({
key: 'quote',
label: t('chat.message.quote'),
onClick: () => {
EventEmitter.emit(EVENT_NAMES.QUOTE_TEXT, selectedQuoteText)
}
})
}
]
// 添加复制消息ID选项但不显示ID
items.push({
key: 'copy_id',
label: t('message.copy_id') || '复制消息ID',
onClick: () => {
navigator.clipboard.writeText(message.id)
window.message.success({ content: t('message.id_copied') || '消息ID已复制', key: 'copy-message-id' })
}
})
return items
}
const MessageContainer = styled.div`
display: flex;

View File

@ -4,7 +4,7 @@ import { getModelUniqId } from '@renderer/services/ModelService'
import { Message, Model } from '@renderer/types'
import { getBriefInfo } from '@renderer/utils'
import { withMessageThought } from '@renderer/utils/formats'
import { Divider, Flex } from 'antd'
import { Collapse, Divider, Flex } from 'antd'
import { clone } from 'lodash'
import React, { Fragment, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
@ -203,8 +203,100 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
<Flex gap="8px" wrap style={{ marginBottom: 10 }}>
{message.mentions?.map((model) => <MentionTag key={getModelUniqId(model)}>{'@' + model.name}</MentionTag>)}
</Flex>
<MessageThought message={message} />
<MessageTools message={message} />
{message.referencedMessages && message.referencedMessages.length > 0 && (
<div>
{message.referencedMessages.map((refMsg, index) => (
<Collapse
key={refMsg.id}
className="reference-collapse"
defaultActiveKey={['1']}
size="small"
items={[
{
key: '1',
label: (
<div className="reference-header-label">
<span className="reference-title">
{t('message.referenced_message')}{' '}
{message.referencedMessages && message.referencedMessages.length > 1
? `(${index + 1}/${message.referencedMessages.length})`
: ''}
</span>
<span className="reference-role">{refMsg.role === 'user' ? t('common.you') : 'AI'}</span>
</div>
),
extra: (
<span
className="reference-id"
onClick={(e) => {
e.stopPropagation()
navigator.clipboard.writeText(refMsg.id)
window.message.success({
content: t('message.id_copied') || '消息ID已复制',
key: 'copy-reference-id'
})
}}>
ID: {refMsg.id}
</span>
),
children: (
<div className="reference-content">
<div className="reference-text">{refMsg.content}</div>
<div className="reference-bottom-spacing"></div>
</div>
)
}
]}
/>
))}
</div>
)}
{/* 兼容旧版本的referencedMessage */}
{!message.referencedMessages && (message as any).referencedMessage && (
<Collapse
className="reference-collapse"
defaultActiveKey={['1']}
size="small"
items={[
{
key: '1',
label: (
<div className="reference-header-label">
<span className="reference-title">{t('message.referenced_message')}</span>
<span className="reference-role">
{(message as any).referencedMessage.role === 'user' ? t('common.you') : 'AI'}
</span>
</div>
),
extra: (
<span
className="reference-id"
onClick={(e) => {
e.stopPropagation()
navigator.clipboard.writeText((message as any).referencedMessage.id)
window.message.success({
content: t('message.id_copied') || '消息ID已复制',
key: 'copy-reference-id'
})
}}>
ID: {(message as any).referencedMessage.id}
</span>
),
children: (
<div className="reference-content">
<div className="reference-text">{(message as any).referencedMessage.content}</div>
<div className="reference-bottom-spacing"></div>
</div>
)
}
]}
/>
)}
<div className="message-content-tools">
<MessageThought message={message} />
<MessageTools message={message} />
</div>
<Markdown message={{ ...message, content: processedContent.replace(toolUseRegex, '') }} />
{message.metadata?.generateImage && <MessageImage message={message} />}
{message.translatedContent && (
@ -312,4 +404,132 @@ const SearchEntryPoint = styled.div`
margin: 10px 2px;
`
// 引用消息样式 - 使用全局样式
const referenceStyles = `
.reference-collapse {
margin-bottom: 8px;
border: 1px solid var(--color-border) !important;
border-radius: 8px !important;
overflow: hidden;
background-color: var(--color-bg-1) !important;
.ant-collapse-header {
padding: 2px 8px !important;
background-color: var(--color-bg-2);
border-bottom: 1px solid var(--color-border);
font-size: 10px;
display: flex;
justify-content: space-between;
height: 18px;
min-height: 18px;
line-height: 14px;
}
.ant-collapse-expand-icon {
height: 18px;
line-height: 14px;
padding-top: 0 !important;
margin-top: -2px;
margin-right: 2px;
}
.ant-collapse-header-text {
flex: 0 1 auto;
max-width: 70%;
}
.ant-collapse-extra {
flex: 0 0 auto;
margin-left: 10px;
padding-right: 0;
position: relative;
right: 20px;
}
.reference-header-label {
display: flex;
align-items: center;
gap: 4px;
height: 14px;
line-height: 14px;
}
.reference-title {
font-weight: 500;
color: var(--color-text-1);
font-size: 10px;
}
.reference-role {
color: var(--color-text-2);
font-size: 9px;
}
.reference-id {
color: var(--color-text-3);
font-size: 9px;
cursor: pointer;
padding: 1px 4px;
border-radius: 3px;
transition: background-color 0.2s ease;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
display: inline-block;
&:hover {
background-color: var(--color-bg-3);
color: var(--color-text-2);
}
}
.ant-collapse-extra {
margin-left: auto;
display: flex;
align-items: center;
}
.ant-collapse-content-box {
padding: 12px !important;
padding-top: 8px !important;
padding-bottom: 2px !important;
}
.reference-content {
max-height: 200px;
overflow-y: auto;
padding-bottom: 10px;
.reference-text {
color: var(--color-text-1);
font-size: 14px;
white-space: pre-wrap;
word-break: break-word;
}
.reference-bottom-spacing {
height: 10px;
}
}
}
`
// 将样式添加到文档中
try {
if (typeof document !== 'undefined') {
const styleElement = document.createElement('style')
styleElement.textContent =
referenceStyles +
`
.message-content-tools {
margin-top: 20px;
}
`
document.head.appendChild(styleElement)
}
} catch (error) {
console.error('Failed to add reference styles:', error)
}
export default React.memo(MessageContent)

View File

@ -1,16 +1,93 @@
import { LinkOutlined } from '@ant-design/icons'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { findMessageById } from '@renderer/services/MessagesService'
import { Message } from '@renderer/types'
import { Button, Modal, Tooltip } from 'antd'
import { t } from 'i18next'
import { useState } from 'react'
import styled from 'styled-components'
// 添加引用消息的弹窗组件
const ReferenceModal: React.FC<{ message: Message | null; visible: boolean; onClose: () => void }> = ({
message,
visible,
onClose
}) => {
if (!message) return null
return (
<Modal title={`引用消息`} open={visible} onCancel={onClose} footer={null} width={600}>
<ReferenceContent>
<div className="message-role">{message.role === 'user' ? t('common.you') : 'AI'}</div>
<div className="message-content">{message.content}</div>
<div className="message-time">{new Date(message.createdAt).toLocaleString()}</div>
</ReferenceContent>
</Modal>
)
}
const ReferenceContent = styled.div`
padding: 10px;
.message-role {
font-weight: bold;
margin-bottom: 5px;
}
.message-content {
white-space: pre-wrap;
word-break: break-word;
margin-bottom: 10px;
}
.message-time {
font-size: 12px;
color: var(--color-text-3);
text-align: right;
}
`
const MessgeTokens: React.FC<{ message: Message; isLastMessage: boolean }> = ({ message, isLastMessage }) => {
const { generating } = useRuntime()
const [isModalVisible, setIsModalVisible] = useState(false)
const [referencedMessage, setReferencedMessage] = useState<Message | null>(null)
// 渲染引用消息弹窗
const renderReferenceModal = () => {
return <ReferenceModal message={referencedMessage} visible={isModalVisible} onClose={handleModalClose} />
}
const locateMessage = () => {
EventEmitter.emit(EVENT_NAMES.LOCATE_MESSAGE + ':' + message.id, false)
}
const showReferenceModal = async (e: React.MouseEvent) => {
e.stopPropagation() // 防止触发父元素的点击事件
try {
// 复制ID到剪贴板便于用户手动使用
navigator.clipboard.writeText(message.id)
// 查找原始消息
const originalMessage = await findMessageById(message.id)
if (originalMessage) {
setReferencedMessage(originalMessage)
setIsModalVisible(true)
}
} catch (error) {
console.error('Failed to find referenced message:', error)
window.message.error({
content: t('message.reference.error') || '无法找到原始消息',
key: 'reference-message-error'
})
}
}
const handleModalClose = () => {
setIsModalVisible(false)
}
if (!message.usage) {
return <div />
}
@ -18,7 +95,16 @@ const MessgeTokens: React.FC<{ message: Message; isLastMessage: boolean }> = ({
if (message.role === 'user') {
return (
<MessageMetadata className="message-tokens" onClick={locateMessage}>
Tokens: {message?.usage?.total_tokens}
<span className="tokens">Tokens: {message?.usage?.total_tokens}</span>
<Tooltip title={t('message.reference') || '引用消息'}>
<Button
type="text"
size="small"
icon={<LinkOutlined />}
onClick={showReferenceModal}
className="reference-button"
/>
</Tooltip>
</MessageMetadata>
)
}
@ -47,11 +133,25 @@ const MessgeTokens: React.FC<{ message: Message; isLastMessage: boolean }> = ({
<span className="tokens">
Tokens: {message?.usage?.total_tokens} {message?.usage?.prompt_tokens} {message?.usage?.completion_tokens}
</span>
<Tooltip title={t('message.reference') || '引用消息'}>
<Button
type="text"
size="small"
icon={<LinkOutlined />}
onClick={showReferenceModal}
className="reference-button"
/>
</Tooltip>
</MessageMetadata>
)
}
return null
return (
<>
{renderReferenceModal()}
{null}
</>
)
}
const MessageMetadata = styled.div`
@ -61,6 +161,10 @@ const MessageMetadata = styled.div`
margin: 2px 0;
cursor: pointer;
text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 5px;
.metrics {
display: none;
@ -79,6 +183,26 @@ const MessageMetadata = styled.div`
display: none;
}
}
.reference-button {
padding: 0;
height: 16px;
width: 16px;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-text-2);
opacity: 0.7;
&:hover {
opacity: 1;
color: var(--color-primary);
}
.anticon {
font-size: 12px;
}
}
`
export default MessgeTokens

View File

@ -1,10 +1,10 @@
import { DeleteOutlined, ClearOutlined } from '@ant-design/icons'
import { ClearOutlined, DeleteOutlined } from '@ant-design/icons'
import { TopicManager } from '@renderer/hooks/useTopic'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store'
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 { memo, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -161,32 +161,31 @@ const AnimatedListItem = styled(List.Item)`
// 记忆项组件
const MemoryItem = memo(({ memory, onDelete, t, index }: MemoryItemProps) => {
const [isDeleting, setIsDeleting] = useState(false);
const [isDeleting, setIsDeleting] = useState(false)
const handleDelete = async () => {
setIsDeleting(true);
setIsDeleting(true)
// 添加小延迟,让动画有时间播放
setTimeout(() => {
onDelete(memory.id);
}, 300);
};
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
/>
<Button icon={<DeleteOutlined />} onClick={handleDelete} type="text" danger />
</Tooltip>
]}
>
]}>
<List.Item.Meta
title={<MemoryContent><strong>{index + 1}. </strong>{memory.content}</MemoryContent>}
title={
<MemoryContent>
<strong>{index + 1}. </strong>
{memory.content}
</MemoryContent>
}
description={new Date(memory.createdAt).toLocaleString()}
/>
</AnimatedListItem>
@ -311,115 +310,121 @@ const CollapsibleShortMemoryManager = () => {
// 处理分页变化
const handlePageChange = useCallback((page: number, topicId: string) => {
setTopicsWithMemories(prev =>
prev.map(item =>
item.topic.id === topicId
? { ...item, currentPage: page }
: item
)
);
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 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);
// 过滤掉要删除的记忆
const filteredShortMemories = state.shortMemories.filter((memory) => memory.topicId !== topicId)
// 更新本地状态
setTopicsWithMemories(prev => prev.filter(item => item.topic.id !== 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}`);
// 更新 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)
}
} catch (error) {
console.error('[CollapsibleShortMemoryManager] Failed to delete all memories:', error);
}
}
});
}, [dispatch, t]);
})
},
[dispatch, t]
)
// 删除短记忆 - 直接删除无需确认
const handleDeleteMemory = useCallback(async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredShortMemories = state.shortMemories.filter(memory => memory.id !== id)
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)
}
// 在本地更新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
}
return item
}).filter(item => item.memories.length > 0) // 移除没有记忆的话题
})
// 执行删除操作
dispatch(deleteShortMemory(id))
// 使用 true 参数强制覆盖文件
const result = await window.api.memory.saveData(newData, true)
// 直接使用 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}`)
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') || '删除失败')
}
} catch (error) {
console.error('[CollapsibleShortMemoryManager] Failed to delete short memory:', error)
// message.error(t('settings.memory.deleteError') || '删除失败')
}
}, [dispatch])
},
[dispatch]
)
return (
<div>
<Typography.Title level={4}>{t('settings.memory.shortMemoriesByTopic') || '按话题分组的短期记忆'}</Typography.Title>
<Typography.Title level={4}>
{t('settings.memory.shortMemoriesByTopic') || '按话题分组的短期记忆'}
</Typography.Title>
{loading ? (
<LoadingContainer>{t('settings.memory.loading') || '加载中...'}</LoadingContainer>
@ -439,8 +444,8 @@ const CollapsibleShortMemoryManager = () => {
<Button
icon={<ClearOutlined />}
onClick={(e) => {
e.stopPropagation(); // 阻止事件冒泡,避免触发折叠面板的展开/收起
handleDeleteTopicMemories(topic.id);
e.stopPropagation() // 阻止事件冒泡,避免触发折叠面板的展开/收起
handleDeleteTopicMemories(topic.id)
}}
type="text"
danger
@ -453,7 +458,10 @@ const CollapsibleShortMemoryManager = () => {
<div>
<List
itemLayout="horizontal"
dataSource={memories.slice((currentPage ? currentPage - 1 : 0) * 15, (currentPage ? currentPage - 1 : 0) * 15 + 15)}
dataSource={memories.slice(
(currentPage ? currentPage - 1 : 0) * 15,
(currentPage ? currentPage - 1 : 0) * 15 + 15
)}
style={{ padding: '4px 0' }}
renderItem={(memory, index) => (
<MemoryItem

View File

@ -1,11 +1,11 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import {
clearCurrentRecommendations,
saveMemoryData,
setContextualRecommendationEnabled,
setAutoRecommendMemories,
setRecommendationThreshold,
clearCurrentRecommendations
setContextualRecommendationEnabled,
setRecommendationThreshold
} from '@renderer/store/memory'
import { Button, InputNumber, Slider, Switch, Tooltip } from 'antd'
import { FC } from 'react'
@ -40,7 +40,10 @@ const ContextualRecommendationSettings: FC = () => {
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)
console.error(
'[ContextualRecommendationSettings] Failed to save contextual recommendation enabled setting:',
error
)
}
}
@ -87,8 +90,11 @@ const ContextualRecommendationSettings: FC = () => {
<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>
@ -100,8 +106,11 @@ const ContextualRecommendationSettings: FC = () => {
<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>
@ -115,8 +124,10 @@ const ContextualRecommendationSettings: FC = () => {
<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>
</SettingRowTitle>
@ -147,15 +158,12 @@ const ContextualRecommendationSettings: FC = () => {
<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}
disabled={!contextualRecommendationEnabled}
>
<Button onClick={handleClearRecommendations} disabled={!contextualRecommendationEnabled}>
{t('settings.memory.contextualRecommendation.clear') || '清除'}
</Button>
</SettingRow>

View File

@ -44,7 +44,7 @@ const HistoricalContextSettings: FC = () => {
// 遍历所有服务商的模型找到匹配的模型
for (const provider of Object.values(providers)) {
const model = provider.models.find(m => m.id === historicalContextAnalyzeModel)
const model = provider.models.find((m) => m.id === historicalContextAnalyzeModel)
if (model) {
return `${model.name} | ${provider.name}`
}
@ -57,15 +57,17 @@ const HistoricalContextSettings: FC = () => {
<SettingGroup>
<SettingTitle>{t('settings.memory.historicalContext.title') || '历史对话上下文'}</SettingTitle>
<SettingHelpText>
{t('settings.memory.historicalContext.description') ||
'允许AI在需要时自动引用历史对话以提供更连贯的回答。'}
{t('settings.memory.historicalContext.description') || '允许AI在需要时自动引用历史对话以提供更连贯的回答。'}
</SettingHelpText>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.historicalContext.enable') || '启用历史对话上下文'}
<Tooltip title={t('settings.memory.historicalContext.enableTip') ||
'启用后AI会在需要时自动分析并引用历史对话以提供更连贯的回答'}>
<Tooltip
title={
t('settings.memory.historicalContext.enableTip') ||
'启用后AI会在需要时自动分析并引用历史对话以提供更连贯的回答'
}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
@ -75,8 +77,11 @@ const HistoricalContextSettings: FC = () => {
<SettingRow>
<SettingRowTitle>
{t('settings.memory.analyzeModel') || '分析模型'}
<Tooltip title={t('settings.memory.historicalContext.analyzeModelTip') ||
'选择用于历史对话上下文分析的模型,建议选择响应较快的模型'}>
<Tooltip
title={
t('settings.memory.historicalContext.analyzeModelTip') ||
'选择用于历史对话上下文分析的模型,建议选择响应较快的模型'
}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
@ -86,7 +91,7 @@ const HistoricalContextSettings: FC = () => {
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)
const model = provider.models.find((m) => m.id === historicalContextAnalyzeModel)
if (model) {
currentModel = model
break
@ -99,8 +104,7 @@ const HistoricalContextSettings: FC = () => {
handleModelChange(selectedModel.id)
}
}}
style={{ width: 300 }}
>
style={{ width: 300 }}>
{historicalContextAnalyzeModel ? getSelectedModelName() : t('settings.memory.selectModel') || '选择模型'}
</Button>
</SettingRow>

View File

@ -6,9 +6,9 @@ import {
deleteMemoryList,
editMemoryList,
MemoryList,
saveLongTermMemoryData,
setCurrentMemoryList,
toggleMemoryListActive,
saveLongTermMemoryData
toggleMemoryListActive
} from '@renderer/store/memory'
import { Button, Empty, Input, List, Modal, Switch, Tooltip, Typography } from 'antd'
import React, { useState } from 'react'
@ -76,10 +76,12 @@ const MemoryListManager: React.FC<MemoryListManagerProps> = ({ onSelectList }) =
// 保存到长期记忆文件
try {
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memoryLists: state.memoryLists,
currentListId: state.currentListId
})).unwrap()
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)
@ -114,10 +116,12 @@ const MemoryListManager: React.FC<MemoryListManagerProps> = ({ onSelectList }) =
// 保存到长期记忆文件
try {
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memoryLists: state.memoryLists,
currentListId: state.currentListId
})).unwrap()
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)
@ -133,10 +137,12 @@ const MemoryListManager: React.FC<MemoryListManagerProps> = ({ onSelectList }) =
// 保存到长期记忆文件
try {
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memoryLists: state.memoryLists,
currentListId: state.currentListId
})).unwrap()
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)
@ -153,10 +159,12 @@ const MemoryListManager: React.FC<MemoryListManagerProps> = ({ onSelectList }) =
// 保存到长期记忆文件
try {
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memoryLists: state.memoryLists,
currentListId: state.currentListId
})).unwrap()
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)

View File

@ -101,8 +101,11 @@ const PriorityManagementSettings: FC = () => {
<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>
@ -114,23 +117,19 @@ const PriorityManagementSettings: FC = () => {
<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') ||
'值越大记忆衰减越快。0.05表示每天衰减5%'}>
<Tooltip
title={t('settings.memory.priorityManagement.decayRateTip') || '值越大记忆衰减越快。0.05表示每天衰减5%'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
@ -161,30 +160,25 @@ const PriorityManagementSettings: FC = () => {
<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}
disabled={!priorityManagementEnabled}
>
<Button onClick={handleUpdatePriorities} disabled={!priorityManagementEnabled}>
{t('settings.memory.priorityManagement.update') || '更新'}
</Button>
</SettingRow>

View File

@ -4,8 +4,8 @@ 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, useCallback } from 'react'
import _ from 'lodash'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
const { Title } = Typography
@ -35,46 +35,52 @@ const ShortMemoryManager = () => {
}
// 添加新的短记忆 - 使用防抖减少频繁更新
const handleAddMemory = useCallback(_.debounce(() => {
if (newMemoryContent.trim() && currentTopicId) {
addShortMemoryItem(newMemoryContent.trim(), currentTopicId)
setNewMemoryContent('') // 清空输入框
}
}, 300), [newMemoryContent, currentTopicId])
const handleAddMemory = useCallback(
_.debounce(() => {
if (newMemoryContent.trim() && currentTopicId) {
addShortMemoryItem(newMemoryContent.trim(), currentTopicId)
setNewMemoryContent('') // 清空输入框
}
}, 300),
[newMemoryContent, currentTopicId]
)
// 删除短记忆 - 直接删除无需确认,使用节流避免频繁删除操作
const handleDeleteMemory = useCallback(_.throttle(async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredShortMemories = state.shortMemories.filter(memory => memory.id !== id)
const handleDeleteMemory = useCallback(
_.throttle(async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredShortMemories = state.shortMemories.filter((memory) => memory.id !== id)
// 执行删除操作
dispatch(deleteShortMemory(id))
// 执行删除操作
dispatch(deleteShortMemory(id))
// 直接使用 window.api.memory.saveData 方法保存过滤后的列表
try {
// 加载当前文件数据
const currentData = await window.api.memory.loadData()
// 直接使用 window.api.memory.saveData 方法保存过滤后的列表
try {
// 加载当前文件数据
const currentData = await window.api.memory.loadData()
// 替换 shortMemories 数组
const newData = {
...currentData,
shortMemories: filteredShortMemories
// 替换 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)
}
// 使用 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])
}, 500),
[dispatch]
)
return (
<div className="short-memory-manager">

View File

@ -2,6 +2,7 @@ import {
AppstoreOutlined,
DeleteOutlined,
EditOutlined,
InfoCircleOutlined,
PlusOutlined,
SearchOutlined,
UnorderedListOutlined
@ -9,7 +10,11 @@ import {
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import { useTheme } from '@renderer/context/ThemeProvider'
import { TopicManager } from '@renderer/hooks/useTopic'
import { analyzeAndAddShortMemories, resetLongTermMemoryAnalyzedMessageIds, 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 {
@ -17,14 +22,15 @@ import {
clearMemories,
deleteMemory,
editMemory,
saveAllMemorySettings,
saveLongTermMemoryData,
saveMemoryData,
setAnalyzeModel,
setAnalyzing,
setAutoAnalyze,
setFilterSensitiveInfo,
setMemoryActive,
setShortMemoryAnalyzeModel,
saveMemoryData,
saveLongTermMemoryData,
saveAllMemorySettings
setShortMemoryAnalyzeModel
} from '@renderer/store/memory'
import { Topic } from '@renderer/types'
import { Button, Empty, Input, List, message, Modal, Pagination, Radio, Select, Switch, Tabs, Tag, Tooltip } from 'antd'
@ -42,12 +48,12 @@ import {
SettingTitle
} from '..'
import CollapsibleShortMemoryManager from './CollapsibleShortMemoryManager'
import ContextualRecommendationSettings from './ContextualRecommendationSettings'
import HistoricalContextSettings from './HistoricalContextSettings'
import MemoryDeduplicationPanel from './MemoryDeduplicationPanel'
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()
@ -61,6 +67,7 @@ const MemorySettings: FC = () => {
const currentListId = useAppSelector((state) => state.memory?.currentListId || null)
const isActive = useAppSelector((state) => state.memory?.isActive || false)
const autoAnalyze = useAppSelector((state) => state.memory?.autoAnalyze || false)
const filterSensitiveInfo = useAppSelector((state) => state.memory?.filterSensitiveInfo ?? true) // 默认启用敏感信息过滤
const analyzeModel = useAppSelector((state) => state.memory?.analyzeModel || null)
const shortMemoryAnalyzeModel = useAppSelector((state) => state.memory?.shortMemoryAnalyzeModel || null)
const isAnalyzing = useAppSelector((state) => state.memory?.isAnalyzing || false)
@ -72,7 +79,7 @@ const MemorySettings: FC = () => {
const models = useMemo(() => {
// 只获取已启用的提供商的模型
return providers
.filter(provider => provider.enabled) // 只保留已启用的提供商
.filter((provider) => provider.enabled) // 只保留已启用的提供商
.flatMap((provider) => provider.models || [])
}, [providers])
@ -177,20 +184,22 @@ const MemorySettings: FC = () => {
const handleDeleteMemory = async (id: string) => {
// 先从当前状态中获取要删除的记忆之外的所有记忆
const state = store.getState().memory
const filteredMemories = state.memories.filter(memory => memory.id !== id)
const filteredMemories = state.memories.filter((memory) => memory.id !== id)
// 执行删除操作
dispatch(deleteMemory(id))
// 保存到长期记忆文件,并强制覆盖
try {
await dispatch(saveLongTermMemoryData({
memories: filteredMemories, // 直接使用过滤后的数组,而不是使用当前状态
memoryLists: state.memoryLists,
currentListId: state.currentListId,
analyzeModel: state.analyzeModel,
forceOverwrite: true // 强制覆盖文件,而不是尝试合并
})).unwrap()
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'))
@ -225,13 +234,15 @@ const MemorySettings: FC = () => {
try {
// 直接传递空数组作为 memories确保完全清空
const state = store.getState().memory
await dispatch(saveLongTermMemoryData({
memories: [], // 直接使用空数组,而不是使用当前状态
memoryLists: state.memoryLists,
currentListId: state.currentListId,
analyzeModel: state.analyzeModel,
forceOverwrite: true // 强制覆盖文件,而不是合并
})).unwrap()
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)
@ -250,6 +261,20 @@ const MemorySettings: FC = () => {
dispatch(setAutoAnalyze(checked))
}
// 处理切换敏感信息过滤
const handleToggleFilterSensitiveInfo = async (checked: boolean) => {
dispatch(setFilterSensitiveInfo(checked))
console.log('[Memory Settings] Filter sensitive info set:', checked)
// 使用Redux Thunk保存到JSON文件
try {
await dispatch(saveMemoryData({ filterSensitiveInfo: checked })).unwrap()
console.log('[Memory Settings] Filter sensitive info saved to file successfully:', checked)
} catch (error) {
console.error('[Memory Settings] Failed to save filter sensitive info to file:', error)
}
}
// 处理选择长期记忆分析模型
const handleSelectModel = async (modelId: string) => {
dispatch(setAnalyzeModel(modelId))
@ -337,7 +362,7 @@ const MemorySettings: FC = () => {
// 遍历所有服务商的模型找到匹配的模型
for (const provider of Object.values(providers)) {
const model = provider.models.find(m => m.id === analyzeModel)
const model = provider.models.find((m) => m.id === analyzeModel)
if (model) {
return `${model.name} | ${provider.name}`
}
@ -352,7 +377,7 @@ const MemorySettings: FC = () => {
// 遍历所有服务商的模型找到匹配的模型
for (const provider of Object.values(providers)) {
const model = provider.models.find(m => m.id === shortMemoryAnalyzeModel)
const model = provider.models.find((m) => m.id === shortMemoryAnalyzeModel)
if (model) {
return `${model.name} | ${provider.name}`
}
@ -547,6 +572,23 @@ const MemorySettings: FC = () => {
<SettingRowTitle>{t('settings.memory.enableAutoAnalyze')}</SettingRowTitle>
<Switch checked={autoAnalyze} onChange={handleToggleAutoAnalyze} disabled={!isActive} />
</SettingRow>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.filterSensitiveInfo') || '过滤敏感信息'}
<Tooltip
title={
t('settings.memory.filterSensitiveInfoTip') ||
'启用后记忆功能将不会提取API密钥、密码等敏感信息'
}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch
checked={filterSensitiveInfo}
onChange={handleToggleFilterSensitiveInfo}
disabled={!isActive}
/>
</SettingRow>
{/* 短期记忆分析模型选择 */}
<SettingRow>
@ -559,7 +601,7 @@ const MemorySettings: FC = () => {
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)
const model = provider.models.find((m) => m.id === shortMemoryAnalyzeModel)
if (model) {
currentModel = model
break
@ -573,9 +615,10 @@ const MemorySettings: FC = () => {
}
}}
style={{ width: 300 }}
disabled={!isActive}
>
{shortMemoryAnalyzeModel ? getSelectedShortMemoryModelName() : t('settings.memory.selectModel') || '选择模型'}
disabled={!isActive}>
{shortMemoryAnalyzeModel
? getSelectedShortMemoryModelName()
: t('settings.memory.selectModel') || '选择模型'}
</Button>
</SettingRow>
@ -664,13 +707,11 @@ const MemorySettings: FC = () => {
<SettingGroup>
<SettingTitle>{t('settings.memory.saveAllSettings') || '保存所有设置'}</SettingTitle>
<SettingHelpText>
{t('settings.memory.saveAllSettingsDescription') || '将所有记忆功能的设置保存到文件中,确保应用重启后设置仍然生效。'}
{t('settings.memory.saveAllSettingsDescription') ||
'将所有记忆功能的设置保存到文件中,确保应用重启后设置仍然生效。'}
</SettingHelpText>
<SettingRow>
<Button
type="primary"
onClick={handleSaveAllSettings}
>
<Button type="primary" onClick={handleSaveAllSettings}>
{t('settings.memory.saveAllSettings') || '保存所有设置'}
</Button>
</SettingRow>
@ -706,6 +747,23 @@ const MemorySettings: FC = () => {
<SettingRowTitle>{t('settings.memory.enableAutoAnalyze')}</SettingRowTitle>
<Switch checked={autoAnalyze} onChange={handleToggleAutoAnalyze} disabled={!isActive} />
</SettingRow>
<SettingRow>
<SettingRowTitle>
{t('settings.memory.filterSensitiveInfo') || '过滤敏感信息'}
<Tooltip
title={
t('settings.memory.filterSensitiveInfoTip') ||
'启用后记忆功能将不会提取API密钥、密码等敏感信息'
}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch
checked={filterSensitiveInfo}
onChange={handleToggleFilterSensitiveInfo}
disabled={!isActive}
/>
</SettingRow>
{/* 长期记忆分析模型选择 */}
<SettingRow>
@ -716,7 +774,7 @@ const MemorySettings: FC = () => {
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)
const model = provider.models.find((m) => m.id === analyzeModel)
if (model) {
currentModel = model
break
@ -730,8 +788,7 @@ const MemorySettings: FC = () => {
}
}}
style={{ width: 300 }}
disabled={!isActive}
>
disabled={!isActive}>
{analyzeModel ? getSelectedModelName() : t('settings.memory.selectModel') || '选择模型'}
</Button>
</SettingRow>
@ -868,71 +925,75 @@ const MemorySettings: FC = () => {
{viewMode === 'list' ? (
memories.length > 0 && isActive ? (
<div>
<List
itemLayout="horizontal"
style={{ minHeight: '350px' }}
dataSource={memories
<List
itemLayout="horizontal"
style={{ minHeight: '350px' }}
dataSource={memories
.filter((memory) => (currentListId ? memory.listId === currentListId : true))
.filter((memory) => categoryFilter === null || memory.category === categoryFilter)
.slice((currentPage - 1) * pageSize, currentPage * pageSize)}
renderItem={(memory) => (
<List.Item
actions={[
<Tooltip key="edit" title={t('common.edit')}>
<Button
icon={<EditOutlined />}
type="text"
onClick={() => {
setEditingMemory({ id: memory.id, content: memory.content })
setIsEditModalVisible(true)
}}
disabled={!isActive}
/>
</Tooltip>,
<Tooltip key="delete" title={t('common.delete')}>
<Button
icon={<DeleteOutlined />}
type="text"
danger
onClick={() => handleDeleteMemory(memory.id)}
disabled={!isActive}
/>
</Tooltip>
]}>
<List.Item.Meta
title={
<div>
{memory.category && <TagWithCursor color="blue">{memory.category}</TagWithCursor>}
{memory.content}
</div>
}
description={
<MemoryItemMeta>
<span>{new Date(memory.createdAt).toLocaleString()}</span>
{memory.source && <span>{memory.source}</span>}
</MemoryItemMeta>
}
/>
</List.Item>
)}
/>
{/* 分页组件 */}
{memories
.filter((memory) => (currentListId ? memory.listId === currentListId : true))
.filter((memory) => categoryFilter === null || memory.category === categoryFilter)
.slice((currentPage - 1) * pageSize, currentPage * pageSize)}
renderItem={(memory) => (
<List.Item
actions={[
<Tooltip key="edit" title={t('common.edit')}>
<Button
icon={<EditOutlined />}
type="text"
onClick={() => {
setEditingMemory({ id: memory.id, content: memory.content })
setIsEditModalVisible(true)
}}
disabled={!isActive}
/>
</Tooltip>,
<Tooltip key="delete" title={t('common.delete')}>
<Button
icon={<DeleteOutlined />}
type="text"
danger
onClick={() => handleDeleteMemory(memory.id)}
disabled={!isActive}
/>
</Tooltip>
]}>
<List.Item.Meta
title={
<div>
{memory.category && <TagWithCursor color="blue">{memory.category}</TagWithCursor>}
{memory.content}
</div>
}
description={
<MemoryItemMeta>
<span>{new Date(memory.createdAt).toLocaleString()}</span>
{memory.source && <span>{memory.source}</span>}
</MemoryItemMeta>
.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}
/>
</List.Item>
</PaginationContainer>
)}
/>
{/* 分页组件 */}
{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')} />

View File

@ -502,7 +502,7 @@ export default class AnthropicProvider extends BaseProvider {
// 应用记忆功能到系统提示词
const { applyMemoriesToPrompt } = await import('@renderer/services/MemoryService')
const enhancedPrompt = applyMemoriesToPrompt(prompt)
const enhancedPrompt = await applyMemoriesToPrompt(prompt)
console.log(
'[AnthropicProvider] Applied memories to prompt, length difference:',
enhancedPrompt.length - prompt.length

View File

@ -102,10 +102,22 @@ export default abstract class BaseProvider {
}
public async getMessageContent(message: Message) {
if (isEmpty(message.content)) {
if (isEmpty(message.content) && !message.referencedMessages?.length) {
return message.content
}
// 处理引用消息
if (message.referencedMessages && message.referencedMessages.length > 0) {
const refMsg = message.referencedMessages[0]
const roleText = refMsg.role === 'user' ? '用户' : 'AI'
const referencedContent = `===引用消息开始===\n角色: ${roleText}\n内容: ${refMsg.content}\n===引用消息结束===`
// 如果消息内容为空,则直接返回引用内容
if (isEmpty(message.content.trim())) {
return referencedContent
}
return `${message.content}\n\n${referencedContent}`
}
const webSearchReferences = await this.getWebSearchReferences(message)
if (!isEmpty(webSearchReferences)) {

View File

@ -497,7 +497,7 @@ export default class GeminiProvider extends BaseProvider {
// 应用记忆功能到系统提示词
const { applyMemoriesToPrompt } = await import('@renderer/services/MemoryService')
const enhancedPrompt = applyMemoriesToPrompt(prompt)
const enhancedPrompt = await applyMemoriesToPrompt(prompt)
console.log(
'[GeminiProvider] Applied memories to prompt, length difference:',
enhancedPrompt.length - prompt.length

View File

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

View File

@ -1,11 +1,12 @@
// src/renderer/src/services/ContextualMemoryService.ts
import store from '@renderer/store'
import { vectorService } from './VectorService'
import { addMemoryRetrievalLatency } from '@renderer/store/memory'
import { fetchGenerate } from '@renderer/services/ApiService'
import { Message } from '@renderer/types'
import { TopicManager } from '@renderer/hooks/useTopic'
import { fetchGenerate } from '@renderer/services/ApiService'
import store from '@renderer/store'
import { addMemoryRetrievalLatency } from '@renderer/store/memory'
import { Message } from '@renderer/types'
import { vectorService } from './VectorService'
// 记忆项接口从store/memory.ts导入
interface Memory {
@ -107,7 +108,7 @@ class ContextualMemoryService {
])
// 合并并排序推荐结果
let allRecommendations = [...longTermRecommendations, ...shortTermRecommendations]
const allRecommendations = [...longTermRecommendations, ...shortTermRecommendations]
// 按相关性分数排序
allRecommendations.sort((a, b) => b.relevanceScore - a.relevanceScore)
@ -120,7 +121,9 @@ class ContextualMemoryService {
const latency = endTime - startTime
store.dispatch(addMemoryRetrievalLatency(latency))
console.log(`[ContextualMemory] Found ${limitedRecommendations.length} recommendations in ${latency.toFixed(2)}ms`)
console.log(
`[ContextualMemory] Found ${limitedRecommendations.length} recommendations in ${latency.toFixed(2)}ms`
)
return limitedRecommendations
} catch (error) {
@ -180,7 +183,7 @@ class ContextualMemoryService {
])
// 合并并排序推荐结果
let allRecommendations = [...longTermRecommendations, ...shortTermRecommendations]
const allRecommendations = [...longTermRecommendations, ...shortTermRecommendations]
// 按相关性分数排序
allRecommendations.sort((a, b) => b.relevanceScore - a.relevanceScore)
@ -223,7 +226,7 @@ class ContextualMemoryService {
])
// 合并并排序推荐结果
let allRecommendations = [...longTermRecommendations, ...shortTermRecommendations]
const allRecommendations = [...longTermRecommendations, ...shortTermRecommendations]
// 按相关性分数排序
allRecommendations.sort((a, b) => b.relevanceScore - a.relevanceScore)
@ -274,7 +277,7 @@ class ContextualMemoryService {
}
// 构建对话内容
const conversation = recentMessages.map(msg => `${msg.role || 'user'}: ${msg.content || ''}`).join('\n')
const conversation = recentMessages.map((msg) => `${msg.role || 'user'}: ${msg.content || ''}`).join('\n')
// 构建提示词
const prompt = `
@ -307,8 +310,8 @@ ${conversation}
// 提取关键信息
const keyPoints = result
.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#') && !line.startsWith('-'))
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#') && !line.startsWith('-'))
console.log('[ContextualMemory] Extracted key points:', keyPoints)
@ -331,7 +334,7 @@ ${conversation}
*/
private _buildContextQuery(messages: Message[]): string {
// 提取最近消息的内容
const messageContents = messages.map(msg => msg.content || '').filter(content => content.trim() !== '')
const messageContents = messages.map((msg) => msg.content || '').filter((content) => content.trim() !== '')
// 如果没有有效内容,返回空字符串
if (messageContents.length === 0) {
@ -349,10 +352,7 @@ ${conversation}
* @returns
* @private
*/
private async _getLongTermMemoryRecommendations(
query: string,
topicId?: string
): Promise<MemoryRecommendation[]> {
private async _getLongTermMemoryRecommendations(query: string, topicId?: string): Promise<MemoryRecommendation[]> {
// 获取当前状态
const state = store.getState()
const memoryState = state.memory
@ -363,16 +363,14 @@ ${conversation}
}
// 获取所有激活的记忆列表
const activeListIds = memoryState.memoryLists
.filter(list => list.isActive)
.map(list => list.id)
const activeListIds = memoryState.memoryLists.filter((list) => list.isActive).map((list) => list.id)
if (activeListIds.length === 0) {
return []
}
// 获取激活列表中的记忆
const memories = memoryState.memories.filter(memory => activeListIds.includes(memory.listId))
const memories = memoryState.memories.filter((memory) => activeListIds.includes(memory.listId))
if (memories.length === 0) {
return []
@ -387,7 +385,7 @@ ${conversation}
)
// 转换为推荐格式
const recommendations: MemoryRecommendation[] = results.map(result => ({
const recommendations: MemoryRecommendation[] = results.map((result) => ({
memory: result.memory as Memory,
relevanceScore: result.similarity,
source: 'long-term',
@ -405,10 +403,7 @@ ${conversation}
* @returns
* @private
*/
private async _getShortTermMemoryRecommendations(
query: string,
topicId?: string
): Promise<MemoryRecommendation[]> {
private async _getShortTermMemoryRecommendations(query: string, topicId?: string): Promise<MemoryRecommendation[]> {
// 获取当前状态
const state = store.getState()
const memoryState = state.memory
@ -423,7 +418,7 @@ ${conversation}
// 如果指定了话题ID只获取该话题的短期记忆
if (topicId) {
shortMemories = shortMemories.filter(memory => memory.topicId === topicId)
shortMemories = shortMemories.filter((memory) => memory.topicId === topicId)
}
if (shortMemories.length === 0) {
@ -439,7 +434,7 @@ ${conversation}
)
// 转换为推荐格式
const recommendations: MemoryRecommendation[] = results.map(result => ({
const recommendations: MemoryRecommendation[] = results.map((result) => ({
memory: result.memory as ShortMemory,
relevanceScore: result.similarity,
source: 'short-term',
@ -472,57 +467,57 @@ ${conversation}
const memoryState = state.memory
// 应用多因素排序优化
return recommendations.map(rec => {
const memory = rec.memory
let adjustedScore = rec.relevanceScore
return recommendations
.map((rec) => {
const memory = rec.memory
let adjustedScore = rec.relevanceScore
// 1. 考虑记忆的重要性
if (memory.importance && memoryState.priorityManagementEnabled) {
adjustedScore *= (1 + memory.importance * 0.5) // 重要性最多提升50%的分数
}
// 2. 考虑记忆的鲜度
if (memory.freshness && memoryState.freshnessEnabled) {
adjustedScore *= (1 + memory.freshness * 0.3) // 鲜度最多提升30%的分数
}
// 3. 考虑记忆的衰减因子
if (memory.decayFactor && memoryState.decayEnabled) {
adjustedScore *= memory.decayFactor // 直接应用衰减因子
}
// 4. 如果记忆与当前话题相关,提高分数
if (topicId && memory.topicId === topicId) {
adjustedScore *= 1.2 // 提高20%的分数
}
// 5. 考虑访问频率,常用的记忆可能更相关
if (memory.accessCount && memory.accessCount > 0) {
// 访问次数越多,提升越大,但有上限
const accessBoost = Math.min(memory.accessCount / 10, 0.2) // 最多提升20%
adjustedScore *= (1 + accessBoost)
}
// 6. 考虑关键词匹配
if (memory.keywords && memory.keywords.length > 0) {
const queryLower = query.toLowerCase()
const keywordMatches = memory.keywords.filter(keyword =>
queryLower.includes(keyword.toLowerCase())
).length
if (keywordMatches > 0) {
// 关键词匹配越多,提升越大
const keywordBoost = Math.min(keywordMatches * 0.1, 0.3) // 最多提升30%
adjustedScore *= (1 + keywordBoost)
// 1. 考虑记忆的重要性
if (memory.importance && memoryState.priorityManagementEnabled) {
adjustedScore *= 1 + memory.importance * 0.5 // 重要性最多提升50%的分数
}
}
// 返回调整后的推荐
return {
...rec,
relevanceScore: adjustedScore
}
}).sort((a, b) => b.relevanceScore - a.relevanceScore) // 按调整后的分数重新排序
// 2. 考虑记忆的鲜度
if (memory.freshness && memoryState.freshnessEnabled) {
adjustedScore *= 1 + memory.freshness * 0.3 // 鲜度最多提升30%的分数
}
// 3. 考虑记忆的衰减因子
if (memory.decayFactor && memoryState.decayEnabled) {
adjustedScore *= memory.decayFactor // 直接应用衰减因子
}
// 4. 如果记忆与当前话题相关,提高分数
if (topicId && memory.topicId === topicId) {
adjustedScore *= 1.2 // 提高20%的分数
}
// 5. 考虑访问频率,常用的记忆可能更相关
if (memory.accessCount && memory.accessCount > 0) {
// 访问次数越多,提升越大,但有上限
const accessBoost = Math.min(memory.accessCount / 10, 0.2) // 最多提升20%
adjustedScore *= 1 + accessBoost
}
// 6. 考虑关键词匹配
if (memory.keywords && memory.keywords.length > 0) {
const queryLower = query.toLowerCase()
const keywordMatches = memory.keywords.filter((keyword) => queryLower.includes(keyword.toLowerCase())).length
if (keywordMatches > 0) {
// 关键词匹配越多,提升越大
const keywordBoost = Math.min(keywordMatches * 0.1, 0.3) // 最多提升30%
adjustedScore *= 1 + keywordBoost
}
}
// 返回调整后的推荐
return {
...rec,
relevanceScore: adjustedScore
}
})
.sort((a, b) => b.relevanceScore - a.relevanceScore) // 按调整后的分数重新排序
}
}

View File

@ -1,9 +1,9 @@
// 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 store from '@renderer/store'
import { ShortMemory } from '@renderer/store/memory'
import { Message } from '@renderer/types'
/**
*
@ -112,11 +112,11 @@ const analyzeNeedForHistoricalContext = async (
try {
// 准备分析提示词
const messagesContent = recentMessages
.map(msg => `${msg.role === 'user' ? '用户' : 'AI'}: ${msg.content}`)
.map((msg) => `${msg.role === 'user' ? '用户' : 'AI'}: ${msg.content}`)
.join('\n')
const memoriesContent = shortMemories
.map(memory => `话题ID: ${memory.topicId}\n内容: ${memory.content}`)
.map((memory) => `话题ID: ${memory.topicId}\n内容: ${memory.content}`)
.join('\n\n')
const prompt = `
@ -153,7 +153,8 @@ ${memoriesContent}
// 获取分析模型
const state = store.getState()
// 优先使用历史对话上下文分析模型,如果没有设置,则使用短期记忆分析模型或长期记忆分析模型
const analyzeModel = state.memory?.historicalContextAnalyzeModel || state.memory?.shortMemoryAnalyzeModel || state.memory?.analyzeModel
const analyzeModel =
state.memory?.historicalContextAnalyzeModel || state.memory?.shortMemoryAnalyzeModel || state.memory?.analyzeModel
if (!analyzeModel) {
console.log('[HistoricalContext] No analyze model set')
@ -199,8 +200,7 @@ ${memoriesContent}
}
// 如果都失败了,尝试简单的文本分析
const needsContext = result.toLowerCase().includes('true') &&
!result.toLowerCase().includes('false')
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]+)/)
@ -229,9 +229,7 @@ const getOriginalDialogContent = async (topicId: string): Promise<string | null>
}
// 格式化对话内容
const dialogContent = messages
.map(msg => `${msg.role === 'user' ? '用户' : 'AI'}: ${msg.content}`)
.join('\n\n')
const dialogContent = messages.map((msg) => `${msg.role === 'user' ? '用户' : 'AI'}: ${msg.content}`).join('\n\n')
return dialogContent
} catch (error) {

View File

@ -1,7 +1,14 @@
// 记忆去重与合并服务
import { fetchGenerate } from '@renderer/services/ApiService'
import store from '@renderer/store'
import { addMemory, addShortMemory, deleteMemory, deleteShortMemory, saveMemoryData, saveLongTermMemoryData } from '@renderer/store/memory'
import {
addMemory,
addShortMemory,
deleteMemory,
deleteShortMemory,
saveLongTermMemoryData,
saveMemoryData
} from '@renderer/store/memory'
// 记忆去重与合并的结果接口
export interface DeduplicationResult {
@ -137,7 +144,8 @@ ${memoriesToCheck}
if (similarGroupsMatch && similarGroupsMatch[1]) {
const groupsText = similarGroupsMatch[1].trim()
// 更新正则表达式以匹配新的格式,包括重要性和关键词
const groupRegex = /-\s*组(\d+)?:\s*\[([\d,\s]+)\]\s*-\s*合并建议:\s*"([^"]+)"\s*-\s*分类:\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: RegExpExecArray | null
while ((match = groupRegex.exec(groupsText)) !== null) {
@ -146,7 +154,12 @@ ${memoriesToCheck}
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
const keywords = match[6]
? match[6]
.trim()
.split(',')
.map((k: string) => k.trim())
: undefined
similarGroups.push({
groupId,
@ -299,18 +312,26 @@ export const applyDeduplicationResult = async (
// 保存到文件
if (isShortMemory) {
// 短期记忆使用saveMemoryData
await store.dispatch(saveMemoryData({
shortMemories: currentState.shortMemories
})).unwrap()
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()
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) {

View File

@ -7,26 +7,27 @@ import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store' // Import store
// AiProvider no longer needed as we're using fetchGenerate
import {
accessMemory,
addAnalysisLatency,
addMemory,
addShortMemory,
setAnalyzing,
updateAnalysisStats,
updatePerformanceMetrics,
updateUserInterest,
updateMemoryPriorities,
accessMemory,
Memory,
saveMemoryData,
saveLongTermMemoryData,
updateCurrentRecommendations,
setRecommending,
clearCurrentRecommendations,
MemoryRecommendation
Memory,
MemoryRecommendation,
saveLongTermMemoryData,
saveMemoryData,
setAnalyzing,
setRecommending,
updateAnalysisStats,
updateCurrentRecommendations,
updateMemoryPriorities,
updatePerformanceMetrics,
updateUserInterest
} from '@renderer/store/memory'
import { useCallback, useEffect, useRef } from 'react' // Add useRef back
import { contextualMemoryService } from './ContextualMemoryService' // Import contextual memory service
import { Message } from '@renderer/types' // Import Message type
import { useCallback, useEffect, useRef } from 'react' // Add useRef back
import { contextualMemoryService } from './ContextualMemoryService' // Import contextual memory service
// 计算对话复杂度,用于调整分析深度
const calculateConversationComplexity = (conversation: string): 'low' | 'medium' | 'high' => {
@ -45,6 +46,8 @@ const calculateConversationComplexity = (conversation: string): 'low' | 'medium'
}
// 根据分析深度调整提示词
// 注意:该函数当前未使用,保留供将来可能的功能扩展
/*
const adjustPromptForDepth = (basePrompt: string, depth: 'low' | 'medium' | 'high'): string => {
switch (depth) {
case 'low':
@ -73,6 +76,7 @@ const adjustPromptForDepth = (basePrompt: string, depth: 'low' | 'medium' | 'hig
return basePrompt
}
}
*/
// 提取用户关注点
const extractUserInterests = (conversation: string): string[] => {
@ -99,8 +103,11 @@ const analyzeConversation = async (
customPrompt?: string
): Promise<Array<{ content: string; category: string }>> => {
try {
// 获取当前的过滤敏感信息设置
const filterSensitiveInfo = store.getState().memory?.filterSensitiveInfo ?? true
// 使用自定义提示词或默认提示词
const basePrompt =
let basePrompt =
customPrompt ||
`
@ -117,6 +124,23 @@ const analyzeConversation = async (
`
// 如果启用了敏感信息过滤,添加相关指令
if (filterSensitiveInfo) {
basePrompt += `
##
- API密钥访
-
-
-
-
-
`
}
console.log(`[Memory Analysis] Analyzing conversation using model: ${modelId}`)
// 将提示词和对话内容合并到一个系统提示词中
@ -134,7 +158,7 @@ ${conversation}
console.log('[Memory Analysis] Calling fetchGenerate with combined prompt...')
const result = await fetchGenerate({
prompt: combinedPrompt,
content: "", // 内容字段留空
content: '', // 内容字段留空
modelId: modelId
})
@ -169,7 +193,7 @@ ${conversation}
} catch (error) {
console.error('Failed to analyze conversation with real AI:', error)
// Consider logging the specific error details if possible
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
}
}
@ -205,14 +229,10 @@ export const getContextualMemoryRecommendations = async (
store.dispatch(setRecommending(true))
// 调用上下文感知记忆服务获取推荐
const recommendations = await contextualMemoryService.getContextualMemoryRecommendations(
messages,
topicId,
limit
)
const recommendations = await contextualMemoryService.getContextualMemoryRecommendations(messages, topicId, limit)
// 转换为Redux状态中的推荐格式
const memoryRecommendations: MemoryRecommendation[] = recommendations.map(rec => ({
const memoryRecommendations: MemoryRecommendation[] = recommendations.map((rec) => ({
memoryId: rec.memory.id,
relevanceScore: rec.relevanceScore,
source: rec.source,
@ -239,10 +259,7 @@ export const getContextualMemoryRecommendations = async (
* @param limit -
* @returns
*/
export const getTopicRelatedMemories = async (
topicId: string,
limit: number = 10
): Promise<MemoryRecommendation[]> => {
export const getTopicRelatedMemories = async (topicId: string, limit: number = 10): Promise<MemoryRecommendation[]> => {
try {
// 获取当前状态
const state = store.getState().memory
@ -257,13 +274,10 @@ export const getTopicRelatedMemories = async (
store.dispatch(setRecommending(true))
// 调用上下文感知记忆服务获取推荐
const recommendations = await contextualMemoryService.getTopicRelatedMemories(
topicId,
limit
)
const recommendations = await contextualMemoryService.getTopicRelatedMemories(topicId, limit)
// 转换为Redux状态中的推荐格式
const memoryRecommendations: MemoryRecommendation[] = recommendations.map(rec => ({
const memoryRecommendations: MemoryRecommendation[] = recommendations.map((rec) => ({
memoryId: rec.memory.id,
relevanceScore: rec.relevanceScore,
source: rec.source,
@ -308,13 +322,10 @@ export const getAIEnhancedMemoryRecommendations = async (
store.dispatch(setRecommending(true))
// 调用上下文感知记忆服务获取推荐
const recommendations = await contextualMemoryService.getAIEnhancedMemoryRecommendations(
messages,
limit
)
const recommendations = await contextualMemoryService.getAIEnhancedMemoryRecommendations(messages, limit)
// 转换为Redux状态中的推荐格式
const memoryRecommendations: MemoryRecommendation[] = recommendations.map(rec => ({
const memoryRecommendations: MemoryRecommendation[] = recommendations.map((rec) => ({
memoryId: rec.memory.id,
relevanceScore: rec.relevanceScore,
source: rec.source,
@ -342,7 +353,9 @@ export const useMemoryService = () => {
const isActive = useAppSelector((state) => state.memory?.isActive || false)
const autoAnalyze = useAppSelector((state) => state.memory?.autoAnalyze || false)
const analyzeModel = useAppSelector((state) => state.memory?.analyzeModel || null)
const contextualRecommendationEnabled = useAppSelector((state) => state.memory?.contextualRecommendationEnabled || false)
const contextualRecommendationEnabled = useAppSelector(
(state) => state.memory?.contextualRecommendationEnabled || false
)
const autoRecommendMemories = useAppSelector((state) => state.memory?.autoRecommendMemories || false)
// 使用 useCallback 定义分析函数,但减少依赖项
@ -511,7 +524,7 @@ ${existingMemoriesContent}
// 调用分析函数,传递自定义提示词和对话内容
// 将对话内容直接放在提示词中,不再单独传递
const memories = await analyzeConversation("", memoryState.analyzeModel!, basePrompt)
const memories = await analyzeConversation('', memoryState.analyzeModel!, basePrompt)
// 用户关注点学习
if (memoryState.interestTrackingEnabled) {
@ -623,12 +636,16 @@ ${existingMemoriesContent}
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()
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)
@ -712,8 +729,6 @@ ${existingMemoriesContent}
analyzeAndAddMemoriesRef.current = analyzeAndAddMemories
}, [analyzeAndAddMemories])
// 记录记忆访问
const recordMemoryAccess = useCallback((memoryId: string, isShortMemory: boolean = false) => {
store.dispatch(accessMemory({ id: memoryId, isShortMemory }))
@ -722,20 +737,23 @@ ${existingMemoriesContent}
// Effect 来设置/清除定时器,只依赖于启动条件
useEffect(() => {
// 定期更新记忆优先级
const priorityUpdateInterval = setInterval(() => {
const memoryState = store.getState().memory
if (!memoryState?.priorityManagementEnabled) return
const priorityUpdateInterval = setInterval(
() => {
const memoryState = store.getState().memory
if (!memoryState?.priorityManagementEnabled) return
// 检查上次更新时间,避免频繁更新
const now = Date.now()
const lastUpdate = memoryState.lastPriorityUpdate || 0
const updateInterval = 30 * 60 * 1000 // 30分钟更新一次
// 检查上次更新时间,避免频繁更新
const now = Date.now()
const lastUpdate = memoryState.lastPriorityUpdate || 0
const updateInterval = 30 * 60 * 1000 // 30分钟更新一次
if (now - lastUpdate < updateInterval) return
if (now - lastUpdate < updateInterval) return
console.log('[Memory Priority] Updating memory priorities and freshness...')
store.dispatch(updateMemoryPriorities())
}, 10 * 60 * 1000) // 每10分钟检查一次
console.log('[Memory Priority] Updating memory priorities and freshness...')
store.dispatch(updateMemoryPriorities())
},
10 * 60 * 1000
) // 每10分钟检查一次
return () => {
clearInterval(priorityUpdateInterval)
@ -825,16 +843,19 @@ ${existingMemoriesContent}
console.log('[ContextualMemory] Setting up auto recommendation timer...')
// 每5分钟自动推荐一次记忆
const intervalId = setInterval(() => {
const state = store.getState()
const currentTopicId = state.messages.currentTopic?.id
const messages = currentTopicId ? state.messages.messagesByTopic?.[currentTopicId] || [] : []
const intervalId = setInterval(
() => {
const state = store.getState()
const currentTopicId = state.messages.currentTopic?.id
const messages = currentTopicId ? state.messages.messagesByTopic?.[currentTopicId] || [] : []
if (currentTopicId && messages.length > 0) {
console.log('[ContextualMemory] Auto recommendation triggered')
getContextualRecommendations(messages, currentTopicId)
}
}, 5 * 60 * 1000) // 5分钟
if (currentTopicId && messages.length > 0) {
console.log('[ContextualMemory] Auto recommendation triggered')
getContextualRecommendations(messages, currentTopicId)
}
},
5 * 60 * 1000
) // 5分钟
return () => {
console.log('[ContextualMemory] Clearing auto recommendation timer')
@ -873,10 +894,14 @@ export const addShortMemoryItem = async (
// 保存到文件,并强制覆盖
try {
const state = store.getState().memory
await store.dispatch(saveMemoryData({
shortMemories: state.shortMemories,
forceOverwrite: true // 强制覆盖文件,确保数据正确保存
})).unwrap()
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)
@ -905,12 +930,16 @@ export const addMemoryItem = async (
// 保存到长期记忆文件
try {
const state = store.getState().memory
await store.dispatch(saveLongTermMemoryData({
memories: state.memories,
memoryLists: state.memoryLists,
currentListId: state.currentListId,
analyzeModel: state.analyzeModel
})).unwrap()
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)
@ -934,7 +963,7 @@ export const resetLongTermMemoryAnalyzedMessageIds = async (topicId: string): Pr
// 找到指定话题的所有长期记忆
const memories = state.memories || []
const topicMemories = memories.filter(memory => memory.topicId === topicId)
const topicMemories = memories.filter((memory) => memory.topicId === topicId)
if (topicMemories.length === 0) {
console.log(`[Memory Reset] No long-term memories found for topic ${topicId}`)
@ -947,7 +976,7 @@ export const resetLongTermMemoryAnalyzedMessageIds = async (topicId: string): Pr
let hasChanges = false
// 创建更新后的记忆数组
const updatedMemories = state.memories.map(memory => {
const updatedMemories = state.memories.map((memory) => {
// 只更新指定话题的记忆
if (memory.topicId === topicId && memory.analyzedMessageIds && memory.analyzedMessageIds.length > 0) {
hasChanges = true
@ -972,9 +1001,13 @@ export const resetLongTermMemoryAnalyzedMessageIds = async (topicId: string): Pr
})
// 保存更改到文件
await store.dispatch(saveMemoryData({
memories: updatedMemories
})).unwrap()
await store
.dispatch(
saveMemoryData({
memories: updatedMemories
})
)
.unwrap()
// 尝试获取话题的消息,以确保分析时能找到消息
try {
@ -1089,8 +1122,11 @@ export const analyzeAndAddShortMemories = async (topicId: string) => {
console.log(`[Short Memory Analysis] Analyzing topic: ${topicId}`)
console.log('[Short Memory Analysis] New conversation length:', newConversation.length)
// 获取当前的过滤敏感信息设置
const filterSensitiveInfo = store.getState().memory?.filterSensitiveInfo ?? true
// 构建短期记忆分析提示词,包含已有记忆和新对话
const prompt = `
let prompt = `
AI理解当前对话的完整上下文
@ -1101,7 +1137,22 @@ export const analyzeAndAddShortMemories = async (topicId: string) => {
5.
6.
7.
8.
8. `
// 如果启用了敏感信息过滤,添加相关指令
if (filterSensitiveInfo) {
prompt += `
9.
- API密钥访
-
-
-
-
-
`
}
prompt += `
@ -1173,7 +1224,7 @@ ${newConversation}
let extractedLines: string[] = []
// 首先尝试匹配带有数字或短横线的列表项
const listItemRegex = /(?:^|\n)(?:\d+\.\s*|\-\s*)(.+?)(?=\n\d+\.\s*|\n\-\s*|\n\n|$)/gs
const listItemRegex = /(?:^|\n)(?:\d+\.\s*|-\s*)(.+?)(?=\n\d+\.\s*|\n-\s*|\n\n|$)/gs
let match: RegExpExecArray | null
while ((match = listItemRegex.exec(result)) !== null) {
if (match[1] && match[1].trim()) {
@ -1185,18 +1236,20 @@ ${newConversation}
if (extractedLines.length === 0) {
extractedLines = result
.split('\n')
.map(line => line.trim())
.filter(line => {
.map((line) => line.trim())
.filter((line) => {
// 过滤掉空行和非内容行(如标题、分隔符等)
return line &&
!line.startsWith('#') &&
!line.startsWith('---') &&
!line.startsWith('===') &&
!line.includes('没有找到新的重要信息') &&
!line.includes('No new important information')
return (
line &&
!line.startsWith('#') &&
!line.startsWith('---') &&
!line.startsWith('===') &&
!line.includes('没有找到新的重要信息') &&
!line.includes('No new important information')
)
})
// 清理行首的数字、点和短横线
.map(line => line.replace(/^(\d+\.\s*|\-\s*)/, '').trim())
.map((line) => line.replace(/^(\d+\.\s*|-\s*)/, '').trim())
}
console.log('[Short Memory Analysis] Extracted items:', extractedLines)
@ -1211,11 +1264,12 @@ ${newConversation}
const newMemories = extractedLines.filter((content: string) => {
const normalizedContent = content.toLowerCase()
// 检查是否与现有记忆完全匹配或高度相似
return !existingContents.some(existingContent =>
existingContent === normalizedContent ||
// 简单的相似度检查 - 如果一个字符串包含另一个的80%以上的内容
(existingContent.includes(normalizedContent) && normalizedContent.length > existingContent.length * 0.8) ||
(normalizedContent.includes(existingContent) && existingContent.length > normalizedContent.length * 0.8)
return !existingContents.some(
(existingContent) =>
existingContent === normalizedContent ||
// 简单的相似度检查 - 如果一个字符串包含另一个的80%以上的内容
(existingContent.includes(normalizedContent) && normalizedContent.length > existingContent.length * 0.8) ||
(normalizedContent.includes(existingContent) && existingContent.length > normalizedContent.length * 0.8)
)
})
@ -1254,12 +1308,16 @@ ${newConversation}
// 显式触发保存操作,确保数据被持久化,并强制覆盖
try {
const state = store.getState().memory
await store.dispatch(saveMemoryData({
memoryLists: state.memoryLists,
memories: state.memories,
shortMemories: state.shortMemories,
forceOverwrite: true // 强制覆盖文件,确保数据正确保存
})).unwrap() // 使用unwrap()来等待异步操作完成并处理错误
await store
.dispatch(
saveMemoryData({
memoryLists: state.memoryLists,
memories: state.memories,
shortMemories: state.shortMemories,
forceOverwrite: true // 强制覆盖文件,确保数据正确保存
})
)
.unwrap() // 使用unwrap()来等待异步操作完成并处理错误
console.log('[Short Memory Analysis] Memory data saved successfully (force overwrite)')
} catch (error) {
console.error('[Short Memory Analysis] Failed to save memory data:', error)
@ -1324,16 +1382,16 @@ export const applyMemoriesToPrompt = async (systemPrompt: string, topicId?: stri
// 处理上下文感知记忆推荐
if (contextualRecommendationEnabled && currentRecommendations && currentRecommendations.length > 0) {
// 获取推荐记忆的详细信息
const recommendedMemories: Array<{content: string, source: string, reason: string}> = []
const recommendedMemories: Array<{ content: string; source: string; reason: string }> = []
// 处理每个推荐记忆
for (const recommendation of currentRecommendations) {
// 根据来源查找记忆
let memory: any = null
if (recommendation.source === 'long-term') {
memory = memories.find(m => m.id === recommendation.memoryId)
memory = memories.find((m) => m.id === recommendation.memoryId)
} else if (recommendation.source === 'short-term') {
memory = shortMemories.find(m => m.id === recommendation.memoryId)
memory = shortMemories.find((m) => m.id === recommendation.memoryId)
}
if (memory) {
@ -1344,10 +1402,12 @@ export const applyMemoriesToPrompt = async (systemPrompt: string, topicId?: stri
})
// 记录访问
store.dispatch(accessMemory({
id: memory.id,
isShortMemory: recommendation.source === 'short-term'
}))
store.dispatch(
accessMemory({
id: memory.id,
isShortMemory: recommendation.source === 'short-term'
})
)
}
}
@ -1355,8 +1415,10 @@ export const applyMemoriesToPrompt = async (systemPrompt: string, topicId?: stri
// 构建推荐记忆提示词
// 按重要性排序
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 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
@ -1383,7 +1445,7 @@ export const applyMemoriesToPrompt = async (systemPrompt: string, topicId?: stri
// 如果启用了智能优先级管理,根据优先级排序
if (priorityManagementEnabled && topicShortMemories.length > 0) {
// 计算每个记忆的综合分数(重要性 * 衰减因子 * 鲜度)
const scoredMemories = topicShortMemories.map(memory => {
const scoredMemories = topicShortMemories.map((memory) => {
// 记录访问
store.dispatch(accessMemory({ id: memory.id, isShortMemory: true }))
@ -1399,7 +1461,7 @@ export const applyMemoriesToPrompt = async (systemPrompt: string, topicId?: stri
scoredMemories.sort((a, b) => b.score - a.score)
// 提取排序后的记忆
topicShortMemories = scoredMemories.map(item => item.memory)
topicShortMemories = scoredMemories.map((item) => item.memory)
// 限制数量,避免提示词过长
if (topicShortMemories.length > 10) {
@ -1437,7 +1499,7 @@ export const applyMemoriesToPrompt = async (systemPrompt: string, topicId?: stri
// 如果启用了智能优先级管理,根据优先级排序
if (priorityManagementEnabled && activeMemories.length > 0) {
// 计算每个记忆的综合分数
const scoredMemories = activeMemories.map(memory => {
const scoredMemories = activeMemories.map((memory) => {
// 记录访问
store.dispatch(accessMemory({ id: memory.id }))
@ -1457,9 +1519,9 @@ export const applyMemoriesToPrompt = async (systemPrompt: string, topicId?: stri
const memoriesByList: Record<string, Memory[]> = {}
// 提取排序后的记忆
const sortedMemories = scoredMemories.map(item => item.memory)
const sortedMemories = scoredMemories.map((item) => item.memory)
sortedMemories.forEach(memory => {
sortedMemories.forEach((memory) => {
if (!memoriesByList[memory.listId]) {
memoriesByList[memory.listId] = []
}

View File

@ -1,5 +1,6 @@
import SearchPopup from '@renderer/components/Popups/SearchPopup'
import { DEFAULT_CONTEXTCOUNT } from '@renderer/config/constant'
import db from '@renderer/databases'
import { getTopicById } from '@renderer/hooks/useTopic'
import i18n from '@renderer/i18n'
import { fetchMessagesSummary } from '@renderer/services/ApiService'
@ -200,6 +201,40 @@ export function getMessageModelId(message: Message) {
return message?.model?.id || message.modelId
}
/**
* ID查找消息
* @param messageId ID
* @returns null
*/
export async function findMessageById(messageId: string): Promise<Message | null> {
console.log(`[findMessageById] 正在查找消息ID: ${messageId}`)
try {
// 获取所有话题
const topics = await db.topics.toArray()
console.log(`[findMessageById] 找到 ${topics.length} 个话题`)
// 遍历所有话题,查找消息
for (const topic of topics) {
if (!topic.messages || topic.messages.length === 0) {
continue
}
const message = topic.messages.find((msg) => msg.id === messageId)
if (message) {
console.log(`[findMessageById] 在话题 ${topic.id} 中找到消息`)
return message
}
}
console.log(`[findMessageById] 未找到消息ID: ${messageId}`)
return null
} catch (error) {
console.error(`[findMessageById] 查找消息时出错:`, error)
return null
}
}
export function resetAssistantMessage(message: Message, model?: Model): Message {
return {
...message,

View File

@ -1,7 +1,6 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
import { nanoid } from 'nanoid'
import log from 'electron-log'
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
import store, { RootState } from '@renderer/store'
import { nanoid } from 'nanoid'
// 记忆列表接口
export interface MemoryList {
@ -93,6 +92,7 @@ export interface MemoryState {
isActive: boolean // 记忆功能是否激活
shortMemoryActive: boolean // 短记忆功能是否激活
autoAnalyze: boolean // 是否自动分析
filterSensitiveInfo: boolean // 是否过滤敏感信息
analyzeModel: string | null // 用于长期记忆分析的模型ID
shortMemoryAnalyzeModel: string | null // 用于短期记忆分析的模型ID
historicalContextAnalyzeModel: string | null // 用于历史对话上下文分析的模型ID
@ -148,6 +148,7 @@ const initialState: MemoryState = {
isActive: true,
shortMemoryActive: true, // 默认启用短记忆功能
autoAnalyze: true,
filterSensitiveInfo: true, // 默认启用敏感信息过滤
analyzeModel: 'gpt-3.5-turbo', // 设置默认长期记忆分析模型
shortMemoryAnalyzeModel: 'gpt-3.5-turbo', // 设置默认短期记忆分析模型
historicalContextAnalyzeModel: 'gpt-3.5-turbo', // 设置默认历史对话上下文分析模型
@ -288,6 +289,11 @@ const memorySlice = createSlice({
state.autoAnalyze = action.payload
},
// 设置是否过滤敏感信息
setFilterSensitiveInfo: (state, action: PayloadAction<boolean>) => {
state.filterSensitiveInfo = action.payload
},
// 设置长期记忆分析模型
setAnalyzeModel: (state, action: PayloadAction<string | null>) => {
state.analyzeModel = action.payload
@ -497,10 +503,12 @@ const memorySlice = createSlice({
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
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)
}
})
@ -508,10 +516,12 @@ const memorySlice = createSlice({
})
// 找出没有被其他记忆引用的消息ID
const unusedMessageIds = Array.from(messageIdsToCheck).filter(msgId => !messageIdReferences.has(msgId))
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`)
console.log(
`[Memory] Found ${unusedMessageIds.length} message IDs that are no longer referenced by any memory`
)
// 将这些消息ID标记为未分析以便下次分析时重新分析这些消息
// 注意我们不需要显式地清除标记因为分析逻辑会检查消息ID是否在任何记忆的analyzedMessageIds中
@ -655,11 +665,11 @@ const memorySlice = createSlice({
// 更新长期记忆优先级
if (state.memories && state.memories.length > 0) {
state.memories.forEach(memory => {
state.memories.forEach((memory) => {
// 计算时间衰减因子
if (state.decayEnabled && memory.lastAccessedAt) {
const daysSinceLastAccess = (now - new Date(memory.lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24)
const decayFactor = Math.max(0, 1 - (daysSinceLastAccess * state.decayRate))
const decayFactor = Math.max(0, 1 - daysSinceLastAccess * state.decayRate)
memory.decayFactor = decayFactor
} else {
memory.decayFactor = 1 // 无衰减
@ -673,20 +683,20 @@ const memorySlice = createSlice({
: daysSinceCreation
// 鲜度评分结合创建时间和最后访问时间
const creationFreshness = Math.max(0, 1 - (daysSinceCreation / 30)) // 30天内创建的记忆较新
const accessFreshness = Math.max(0, 1 - (lastAccessDays / 7)) // 7天内访问的记忆较新
memory.freshness = (creationFreshness * 0.3) + (accessFreshness * 0.7) // 加权平均
const creationFreshness = Math.max(0, 1 - daysSinceCreation / 30) // 30天内创建的记忆较新
const accessFreshness = Math.max(0, 1 - lastAccessDays / 7) // 7天内访问的记忆较新
memory.freshness = creationFreshness * 0.3 + accessFreshness * 0.7 // 加权平均
}
})
}
// 更新短期记忆优先级
if (state.shortMemories && state.shortMemories.length > 0) {
state.shortMemories.forEach(memory => {
state.shortMemories.forEach((memory) => {
// 计算时间衰减因子
if (state.decayEnabled && memory.lastAccessedAt) {
const hoursSinceLastAccess = (now - new Date(memory.lastAccessedAt).getTime()) / (1000 * 60 * 60)
const decayFactor = Math.max(0, 1 - (hoursSinceLastAccess * state.decayRate * 4)) // 短期记忆衰减更快
const decayFactor = Math.max(0, 1 - hoursSinceLastAccess * state.decayRate * 4) // 短期记忆衰减更快
memory.decayFactor = decayFactor
} else {
memory.decayFactor = 1 // 无衰减
@ -700,9 +710,9 @@ const memorySlice = createSlice({
: hoursSinceCreation
// 短期记忆的鲜度评分更注重最近性
const creationFreshness = Math.max(0, 1 - (hoursSinceCreation / 24)) // 24小时内创建的记忆较新
const accessFreshness = Math.max(0, 1 - (lastAccessHours / 6)) // 6小时内访问的记忆较新
memory.freshness = (creationFreshness * 0.2) + (accessFreshness * 0.8) // 加权平均,更注重访问时间
const creationFreshness = Math.max(0, 1 - hoursSinceCreation / 24) // 24小时内创建的记忆较新
const accessFreshness = Math.max(0, 1 - lastAccessHours / 6) // 6小时内访问的记忆较新
memory.freshness = creationFreshness * 0.2 + accessFreshness * 0.8 // 加权平均,更注重访问时间
}
})
}
@ -718,29 +728,29 @@ const memorySlice = createSlice({
// 更新长期记忆鲜度
if (state.memories && state.memories.length > 0) {
state.memories.forEach(memory => {
state.memories.forEach((memory) => {
const daysSinceCreation = (now - new Date(memory.createdAt).getTime()) / (1000 * 60 * 60 * 24)
const lastAccessDays = memory.lastAccessedAt
? (now - new Date(memory.lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24)
: daysSinceCreation
const creationFreshness = Math.max(0, 1 - (daysSinceCreation / 30))
const accessFreshness = Math.max(0, 1 - (lastAccessDays / 7))
memory.freshness = (creationFreshness * 0.3) + (accessFreshness * 0.7)
const creationFreshness = Math.max(0, 1 - daysSinceCreation / 30)
const accessFreshness = Math.max(0, 1 - lastAccessDays / 7)
memory.freshness = creationFreshness * 0.3 + accessFreshness * 0.7
})
}
// 更新短期记忆鲜度
if (state.shortMemories && state.shortMemories.length > 0) {
state.shortMemories.forEach(memory => {
state.shortMemories.forEach((memory) => {
const hoursSinceCreation = (now - new Date(memory.createdAt).getTime()) / (1000 * 60 * 60)
const lastAccessHours = memory.lastAccessedAt
? (now - new Date(memory.lastAccessedAt).getTime()) / (1000 * 60 * 60)
: hoursSinceCreation
const creationFreshness = Math.max(0, 1 - (hoursSinceCreation / 24))
const accessFreshness = Math.max(0, 1 - (lastAccessHours / 6))
memory.freshness = (creationFreshness * 0.2) + (accessFreshness * 0.8)
const creationFreshness = Math.max(0, 1 - hoursSinceCreation / 24)
const accessFreshness = Math.max(0, 1 - lastAccessHours / 6)
memory.freshness = creationFreshness * 0.2 + accessFreshness * 0.8
})
}
},
@ -752,14 +762,14 @@ const memorySlice = createSlice({
if (isShortMemory) {
// 更新短期记忆访问信息
const memory = state.shortMemories?.find(m => m.id === id)
const memory = state.shortMemories?.find((m) => m.id === id)
if (memory) {
memory.accessCount = (memory.accessCount || 0) + 1
memory.lastAccessedAt = now
}
} else {
// 更新长期记忆访问信息
const memory = state.memories?.find(m => m.id === id)
const memory = state.memories?.find((m) => m.id === id)
if (memory) {
memory.accessCount = (memory.accessCount || 0) + 1
memory.lastAccessedAt = now
@ -822,7 +832,7 @@ const memorySlice = createSlice({
console.log('[Memory Reducer] Loaded short memory analyze model:', action.payload.shortMemoryAnalyzeModel)
}
log.info('Short-term memory data loaded into state')
console.log('Short-term memory data loaded into state')
}
})
.addCase(loadLongTermMemoryData.fulfilled, (state, action) => {
@ -840,7 +850,7 @@ const memorySlice = createSlice({
// 自动选择默认的记忆列表
if (!state.currentListId && state.memoryLists && state.memoryLists.length > 0) {
// 先尝试找到一个isActive为true的列表
const activeList = state.memoryLists.find(list => list.isActive)
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)
@ -851,15 +861,21 @@ const memorySlice = createSlice({
}
}
log.info('Long-term memory data loaded into state')
console.log('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)
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)
console.log(
'[Memory Reducer] Using default model for historical context:',
state.historicalContextAnalyzeModel
)
}
if (action.payload.vectorizeModel) {
@ -867,7 +883,7 @@ const memorySlice = createSlice({
console.log('[Memory Reducer] Loaded vectorize model:', action.payload.vectorizeModel)
}
log.info('Memory data loaded into state')
console.log('Memory data loaded into state')
}
})
}
@ -879,6 +895,7 @@ export const {
editMemory,
setMemoryActive,
setAutoAnalyze,
setFilterSensitiveInfo,
setAnalyzeModel,
setShortMemoryAnalyzeModel,
setHistoricalContextAnalyzeModel,
@ -972,8 +989,11 @@ export const saveMemoryData = createAsyncThunk(
const completeData = {
// 基本设置
isActive: memoryData.isActive !== undefined ? memoryData.isActive : state.isActive,
shortMemoryActive: memoryData.shortMemoryActive !== undefined ? memoryData.shortMemoryActive : state.shortMemoryActive,
shortMemoryActive:
memoryData.shortMemoryActive !== undefined ? memoryData.shortMemoryActive : state.shortMemoryActive,
autoAnalyze: memoryData.autoAnalyze !== undefined ? memoryData.autoAnalyze : state.autoAnalyze,
filterSensitiveInfo:
memoryData.filterSensitiveInfo !== undefined ? memoryData.filterSensitiveInfo : state.filterSensitiveInfo,
// 模型选择
analyzeModel: memoryData.analyzeModel || state.analyzeModel,
@ -987,26 +1007,47 @@ export const saveMemoryData = createAsyncThunk(
currentListId: memoryData.currentListId || state.currentListId,
// 自适应分析相关
adaptiveAnalysisEnabled: memoryData.adaptiveAnalysisEnabled !== undefined ? memoryData.adaptiveAnalysisEnabled : state.adaptiveAnalysisEnabled,
analysisFrequency: memoryData.analysisFrequency !== undefined ? memoryData.analysisFrequency : state.analysisFrequency,
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,
interestTrackingEnabled:
memoryData.interestTrackingEnabled !== undefined
? memoryData.interestTrackingEnabled
: state.interestTrackingEnabled,
// 性能监控相关
monitoringEnabled: memoryData.monitoringEnabled !== undefined ? memoryData.monitoringEnabled : state.monitoringEnabled,
monitoringEnabled:
memoryData.monitoringEnabled !== undefined ? memoryData.monitoringEnabled : state.monitoringEnabled,
// 智能优先级与时效性管理相关
priorityManagementEnabled: memoryData.priorityManagementEnabled !== undefined ? memoryData.priorityManagementEnabled : state.priorityManagementEnabled,
priorityManagementEnabled:
memoryData.priorityManagementEnabled !== undefined
? memoryData.priorityManagementEnabled
: state.priorityManagementEnabled,
decayEnabled: memoryData.decayEnabled !== undefined ? memoryData.decayEnabled : state.decayEnabled,
freshnessEnabled: memoryData.freshnessEnabled !== undefined ? memoryData.freshnessEnabled : state.freshnessEnabled,
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,
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)
@ -1020,20 +1061,17 @@ 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
}
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(
@ -1060,6 +1098,8 @@ export const saveLongTermMemoryData = createAsyncThunk(
// 基本设置
isActive: memoryData.isActive !== undefined ? memoryData.isActive : state.isActive,
autoAnalyze: memoryData.autoAnalyze !== undefined ? memoryData.autoAnalyze : state.autoAnalyze,
filterSensitiveInfo:
memoryData.filterSensitiveInfo !== undefined ? memoryData.filterSensitiveInfo : state.filterSensitiveInfo,
// 模型选择
analyzeModel: memoryData.analyzeModel || state.analyzeModel,
@ -1070,26 +1110,47 @@ export const saveLongTermMemoryData = createAsyncThunk(
currentListId: memoryData.currentListId || state.currentListId,
// 自适应分析相关
adaptiveAnalysisEnabled: memoryData.adaptiveAnalysisEnabled !== undefined ? memoryData.adaptiveAnalysisEnabled : state.adaptiveAnalysisEnabled,
analysisFrequency: memoryData.analysisFrequency !== undefined ? memoryData.analysisFrequency : state.analysisFrequency,
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,
interestTrackingEnabled:
memoryData.interestTrackingEnabled !== undefined
? memoryData.interestTrackingEnabled
: state.interestTrackingEnabled,
// 性能监控相关
monitoringEnabled: memoryData.monitoringEnabled !== undefined ? memoryData.monitoringEnabled : state.monitoringEnabled,
monitoringEnabled:
memoryData.monitoringEnabled !== undefined ? memoryData.monitoringEnabled : state.monitoringEnabled,
// 智能优先级与时效性管理相关
priorityManagementEnabled: memoryData.priorityManagementEnabled !== undefined ? memoryData.priorityManagementEnabled : state.priorityManagementEnabled,
priorityManagementEnabled:
memoryData.priorityManagementEnabled !== undefined
? memoryData.priorityManagementEnabled
: state.priorityManagementEnabled,
decayEnabled: memoryData.decayEnabled !== undefined ? memoryData.decayEnabled : state.decayEnabled,
freshnessEnabled: memoryData.freshnessEnabled !== undefined ? memoryData.freshnessEnabled : state.freshnessEnabled,
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,
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)
@ -1103,57 +1164,54 @@ export const saveLongTermMemoryData = createAsyncThunk(
)
// 保存所有记忆设置的函数
export const saveAllMemorySettings = createAsyncThunk(
'memory/saveAllSettings',
async (_, { dispatch, getState }) => {
try {
const state = (getState() as RootState).memory
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,
// 创建一个包含所有设置的对象,但不包含记忆内容和记忆列表
const settings = {
// 基本设置
isActive: state.isActive,
shortMemoryActive: state.shortMemoryActive,
autoAnalyze: state.autoAnalyze,
// 模型选择
analyzeModel: state.analyzeModel,
shortMemoryAnalyzeModel: state.shortMemoryAnalyzeModel,
historicalContextAnalyzeModel: state.historicalContextAnalyzeModel,
vectorizeModel: state.vectorizeModel,
// 模型选择
analyzeModel: state.analyzeModel,
shortMemoryAnalyzeModel: state.shortMemoryAnalyzeModel,
historicalContextAnalyzeModel: state.historicalContextAnalyzeModel,
vectorizeModel: state.vectorizeModel,
// 自适应分析相关
adaptiveAnalysisEnabled: state.adaptiveAnalysisEnabled,
analysisFrequency: state.analysisFrequency,
analysisDepth: state.analysisDepth,
// 自适应分析相关
adaptiveAnalysisEnabled: state.adaptiveAnalysisEnabled,
analysisFrequency: state.analysisFrequency,
analysisDepth: state.analysisDepth,
// 用户关注点相关
interestTrackingEnabled: state.interestTrackingEnabled,
// 用户关注点相关
interestTrackingEnabled: state.interestTrackingEnabled,
// 性能监控相关
monitoringEnabled: state.monitoringEnabled,
// 性能监控相关
monitoringEnabled: state.monitoringEnabled,
// 智能优先级与时效性管理相关
priorityManagementEnabled: state.priorityManagementEnabled,
decayEnabled: state.decayEnabled,
freshnessEnabled: state.freshnessEnabled,
decayRate: state.decayRate,
// 智能优先级与时效性管理相关
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
// 上下文感知记忆推荐相关
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.

View File

@ -71,6 +71,13 @@ export type Message = {
useful?: boolean
error?: Record<string, any>
enabledMCPs?: MCPServer[]
// 引用消息
referencedMessages?: {
id: string
content: string
role: 'user' | 'assistant'
createdAt: string
}[]
metadata?: {
// Gemini
groundingMetadata?: GroundingMetadata

View File

@ -0,0 +1,280 @@
# 敏感信息过滤功能实现方案(修改版)
## 需求分析
用户希望增加一个按钮,控制记忆功能是否过滤密钥等安全敏感信息。当开启过滤功能时,分析模型会过滤掉密钥等敏感信息;关闭则不过滤。此功能对于保护用户隐私和敏感数据至关重要。
## 实现思路
1. 在Redux状态中添加一个新的状态属性`filterSensitiveInfo`
2. 在设置界面中添加一个开关按钮,默认为开启状态
3. 修改分析函数,根据`filterSensitiveInfo`状态添加过滤指令
4. 添加日志记录,跟踪过滤状态的变化
## 修改文件
### 1. 修改 src/renderer/src/store/memory.ts
```typescript
// 在 MemoryState 接口中添加
export interface MemoryState {
// 其他属性...
filterSensitiveInfo: boolean // 是否过滤敏感信息
}
// 在 initialState 中添加
const initialState: MemoryState = {
// 其他属性...
filterSensitiveInfo: true, // 默认启用敏感信息过滤
}
// 添加新的 action creator
setFilterSensitiveInfo: (state, action: PayloadAction<boolean>) => {
state.filterSensitiveInfo = action.payload
},
// 导出 action
export const {
// 其他 actions...
setFilterSensitiveInfo,
} = memorySlice.actions
// 修改 saveMemoryData 函数,确保 filterSensitiveInfo 设置也被保存
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,
filterSensitiveInfo: memoryData.filterSensitiveInfo !== undefined ? memoryData.filterSensitiveInfo : state.filterSensitiveInfo,
// 其他属性...
}
// 同样修改 saveLongTermMemoryData 函数
const completeData = {
// 基本设置
isActive: memoryData.isActive !== undefined ? memoryData.isActive : state.isActive,
autoAnalyze: memoryData.autoAnalyze !== undefined ? memoryData.autoAnalyze : state.autoAnalyze,
filterSensitiveInfo: memoryData.filterSensitiveInfo !== undefined ? memoryData.filterSensitiveInfo : state.filterSensitiveInfo,
// 其他属性...
}
```
### 2. 修改 src/renderer/src/pages/settings/MemorySettings/index.tsx
```typescript
// 导入 InfoCircleOutlined 图标
import {
AppstoreOutlined,
DeleteOutlined,
EditOutlined,
InfoCircleOutlined,
PlusOutlined,
SearchOutlined,
UnorderedListOutlined
} from '@ant-design/icons'
// 导入 setFilterSensitiveInfo action
import {
addMemory,
clearMemories,
deleteMemory,
editMemory,
setAnalyzeModel,
setAnalyzing,
setAutoAnalyze,
setFilterSensitiveInfo,
setMemoryActive,
setShortMemoryAnalyzeModel,
saveMemoryData,
saveLongTermMemoryData,
saveAllMemorySettings
} from '@renderer/store/memory'
// 从 Redux 获取 filterSensitiveInfo 状态
const filterSensitiveInfo = useAppSelector((state) => state.memory?.filterSensitiveInfo ?? true) // 默认启用敏感信息过滤
// 添加处理切换敏感信息过滤的函数
const handleToggleFilterSensitiveInfo = async (checked: boolean) => {
dispatch(setFilterSensitiveInfo(checked))
console.log('[Memory Settings] Filter sensitive info set:', checked)
// 使用Redux Thunk保存到JSON文件
try {
await dispatch(saveMemoryData({ filterSensitiveInfo: checked })).unwrap()
console.log('[Memory Settings] Filter sensitive info saved to file successfully:', checked)
} catch (error) {
console.error('[Memory Settings] Failed to save filter sensitive info to file:', error)
}
}
// 在短期记忆设置中添加开关按钮
<SettingRow>
<SettingRowTitle>
{t('settings.memory.filterSensitiveInfo') || '过滤敏感信息'}
<Tooltip title={t('settings.memory.filterSensitiveInfoTip') || '启用后记忆功能将不会提取API密钥、密码等敏感信息'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch checked={filterSensitiveInfo} onChange={handleToggleFilterSensitiveInfo} disabled={!isActive} />
</SettingRow>
// 在长期记忆设置中也添加相同的开关按钮
<SettingRow>
<SettingRowTitle>
{t('settings.memory.filterSensitiveInfo') || '过滤敏感信息'}
<Tooltip title={t('settings.memory.filterSensitiveInfoTip') || '启用后记忆功能将不会提取API密钥、密码等敏感信息'}>
<InfoCircleOutlined style={{ marginLeft: 8 }} />
</Tooltip>
</SettingRowTitle>
<Switch checked={filterSensitiveInfo} onChange={handleToggleFilterSensitiveInfo} disabled={!isActive} />
</SettingRow>
```
### 3. 修改 src/renderer/src/services/MemoryService.ts
```typescript
// 修改 analyzeConversation 函数
const analyzeConversation = async (
conversation: string,
modelId: string,
customPrompt?: string
): Promise<Array<{ content: string; category: string }>> => {
try {
// 获取当前的过滤敏感信息设置
const filterSensitiveInfo = store.getState().memory?.filterSensitiveInfo ?? true
// 使用自定义提示词或默认提示词
let basePrompt =
customPrompt ||
`
请分析对话内容,提取出重要的用户偏好、习惯、需求和背景信息,这些信息在未来的对话中可能有用。
将每条信息分类并按以下格式返回:
类别: 信息内容
类别应该是以下几种之一:
- 用户偏好:用户喜好、喜欢的事物、风格等
- 技术需求:用户的技术相关需求、开发偏好等
- 个人信息:用户的背景、经历等个人信息
- 交互偏好:用户喜欢的交流方式、沟通风格等
- 其他:不属于以上类别的重要信息
请确保每条信息都是简洁、准确的。如果没有找到重要信息,请返回空字符串。
`
// 如果启用了敏感信息过滤,添加相关指令
if (filterSensitiveInfo) {
basePrompt += `
## 安全提示:
请注意不要提取任何敏感信息,包括但不限于:
- API密钥、访问令牌或其他凭证
- 密码或密码提示
- 私人联系方式(如电话号码、邮箱地址)
- 个人身份信息(如身份证号、社保号)
- 银行账户或支付信息
- 私密的个人或商业信息
如果发现此类信息,请完全忽略,不要以任何形式记录或提取。
`
}
// 其余代码保持不变...
}
}
// 修改 analyzeAndAddShortMemories 函数
export const analyzeAndAddShortMemories = async (topicId: string) => {
// 其他代码...
try {
// 获取当前的过滤敏感信息设置
const filterSensitiveInfo = store.getState().memory?.filterSensitiveInfo ?? true
// 构建短期记忆分析提示词
let prompt = `
请对以下对话内容进行非常详细的分析和总结提取对当前对话至关重要的上下文信息。请注意这个分析将用于生成短期记忆帮助AI理解当前对话的完整上下文。
分析要求:
1. 非常详细地总结用户的每一句话中表达的关键信息、需求和意图
2. 全面分析AI回复中的重要内容和对用户问题的解决方案
3. 详细记录对话中的重要事实、数据、代码示例和具体细节
4. 清晰捕捉对话的逻辑发展、转折点和关键决策
5. 提取对理解当前对话上下文必不可少的信息
6. 记录用户提出的具体问题和关注点
7. 捕捉用户在对话中表达的偏好、困惑和反馈
8. 记录对话中提到的文件、路径、变量名等具体技术细节
`
// 如果启用了敏感信息过滤,添加相关指令
if (filterSensitiveInfo) {
prompt += `
9. 请注意不要提取任何敏感信息,包括但不限于:
- API密钥、访问令牌或其他凭证
- 密码或密码提示
- 私人联系方式(如电话号码、邮箱地址)
- 个人身份信息(如身份证号、社保号)
- 银行账户或支付信息
- 私密的个人或商业信息
如果发现此类信息,请完全忽略,不要以任何形式记录或提取。
`
}
// 其余代码保持不变...
}
}
```
### 4. 修改 src/renderer/src/i18n/locales/zh-cn.json 和 en-us.json
```json
{
"settings": {
"memory": {
"filterSensitiveInfo": "过滤敏感信息",
"filterSensitiveInfoTip": "启用后记忆功能将不会提取API密钥、密码等敏感信息"
}
}
}
```
```json
{
"settings": {
"memory": {
"filterSensitiveInfo": "Filter Sensitive Information",
"filterSensitiveInfoTip": "When enabled, memory function will not extract API keys, passwords, or other sensitive information"
}
}
}
```
## 实现效果
这些修改后,用户将能够通过开关按钮控制记忆功能是否过滤敏感信息:
1. 当开启过滤功能时默认状态分析模型会被明确指示不要提取API密钥、密码等敏感信息
2. 当关闭过滤功能时,分析模型会正常提取所有信息,包括可能的敏感信息
开关按钮会出现在短期记忆和长期记忆设置中,用户可以根据需要随时切换。设置会被保存到配置文件中,确保应用重启后设置仍然生效。
## 思考过程
1. **状态管理**首先考虑如何在Redux中添加新的状态属性并确保它能够被正确保存和加载。
2. **UI设计**:在设置界面中添加开关按钮,并提供提示信息,帮助用户理解这个功能的作用。
3. **提示词修改**:根据开关状态修改分析提示词,添加不要提取敏感信息的指令。这是实现过滤功能的核心部分。
4. **国际化支持**:添加相关的翻译键值对,确保功能在不同语言环境下都能正常使用。
5. **持久化**:确保设置能够被正确保存到配置文件中,并在应用重启后加载。
## 注意事项
1. 这个功能只能在一定程度上防止敏感信息被提取但不能完全保证。如果用户在对话中明确提到了敏感信息AI模型可能仍然会提取部分内容。
2. 建议用户在讨论敏感信息时,最好暂时关闭记忆功能,或者在对话中避免提及敏感信息。
3. 这个功能只影响新分析的对话内容,已经存储的记忆不会受到影响。如果用户想要清除可能包含敏感信息的记忆,需要手动删除这些记忆。