记忆功能升级
This commit is contained in:
@@ -7,6 +7,7 @@ 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'
|
||||
|
||||
@@ -18,6 +18,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 * as NutstoreService from './services/NutstoreService'
|
||||
import ObsidianVaultService from './services/ObsidianVaultService'
|
||||
import { ProxyConfig, proxyManager } from './services/ProxyManager'
|
||||
|
||||
@@ -10,6 +10,7 @@ import { createClient, CreateDirectoryOptions, FileStat } from 'webdav'
|
||||
|
||||
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')
|
||||
@@ -111,10 +112,29 @@ class BackupManager {
|
||||
// 使用流式复制
|
||||
await this.copyDirWithProgress(sourcePath, tempDataDir, (size) => {
|
||||
copiedSize += size
|
||||
const progress = Math.min(80, 20 + Math.floor((copiedSize / totalSize) * 60))
|
||||
const progress = Math.min(70, 20 + Math.floor((copiedSize / totalSize) * 50))
|
||||
onProgress({ stage: 'copying_files', progress, total: 100 })
|
||||
})
|
||||
|
||||
// 复制记忆数据文件
|
||||
const configDir = getConfigDir()
|
||||
const memoryDataPath = path.join(configDir, 'memory-data.json')
|
||||
const tempConfigDir = path.join(this.tempDir, 'Config')
|
||||
const tempMemoryDataPath = path.join(tempConfigDir, 'memory-data.json')
|
||||
|
||||
// 确保目录存在
|
||||
await fs.ensureDir(tempConfigDir)
|
||||
|
||||
// 如果记忆数据文件存在,则复制
|
||||
if (await fs.pathExists(memoryDataPath)) {
|
||||
await fs.copy(memoryDataPath, tempMemoryDataPath)
|
||||
Logger.log('[BackupManager] Memory data file copied')
|
||||
onProgress({ stage: 'copying_memory_data', progress: 75, total: 100 })
|
||||
} else {
|
||||
Logger.log('[BackupManager] Memory data file not found, skipping')
|
||||
onProgress({ stage: 'copying_memory_data', progress: 75, total: 100 })
|
||||
}
|
||||
|
||||
await this.setWritableRecursive(tempDataDir)
|
||||
onProgress({ stage: 'compressing', progress: 80, total: 100 })
|
||||
|
||||
@@ -176,11 +196,32 @@ class BackupManager {
|
||||
// 使用流式复制
|
||||
await this.copyDirWithProgress(sourcePath, destPath, (size) => {
|
||||
copiedSize += size
|
||||
const progress = Math.min(90, 40 + Math.floor((copiedSize / totalSize) * 50))
|
||||
const progress = Math.min(80, 40 + Math.floor((copiedSize / totalSize) * 40))
|
||||
onProgress({ stage: 'copying_files', progress, total: 100 })
|
||||
})
|
||||
|
||||
Logger.log('[backup] step 4: clean up temp directory')
|
||||
// 恢复记忆数据文件
|
||||
Logger.log('[backup] step 4: restore memory data file')
|
||||
const tempConfigDir = path.join(this.tempDir, 'Config')
|
||||
const tempMemoryDataPath = path.join(tempConfigDir, 'memory-data.json')
|
||||
|
||||
if (await fs.pathExists(tempMemoryDataPath)) {
|
||||
const configDir = getConfigDir()
|
||||
const memoryDataPath = path.join(configDir, 'memory-data.json')
|
||||
|
||||
// 确保目录存在
|
||||
await fs.ensureDir(configDir)
|
||||
|
||||
// 复制记忆数据文件
|
||||
await fs.copy(tempMemoryDataPath, memoryDataPath)
|
||||
Logger.log('[backup] Memory data file restored')
|
||||
onProgress({ stage: 'restoring_memory_data', progress: 90, total: 100 })
|
||||
} else {
|
||||
Logger.log('[backup] Memory data file not found in backup, skipping')
|
||||
onProgress({ stage: 'restoring_memory_data', progress: 90, total: 100 })
|
||||
}
|
||||
|
||||
Logger.log('[backup] step 5: clean up temp directory')
|
||||
// 清理临时目录
|
||||
await this.setWritableRecursive(this.tempDir)
|
||||
await fs.remove(this.tempDir)
|
||||
|
||||
@@ -111,6 +111,20 @@ export class StreamableHTTPClientTransport implements Transport {
|
||||
headers.set('last-event-id', this._lastEventId)
|
||||
}
|
||||
|
||||
// 删除可能存在的HTTP/2伪头部
|
||||
if (headers.has(':path')) {
|
||||
headers.delete(':path')
|
||||
}
|
||||
if (headers.has(':method')) {
|
||||
headers.delete(':method')
|
||||
}
|
||||
if (headers.has(':authority')) {
|
||||
headers.delete(':authority')
|
||||
}
|
||||
if (headers.has(':scheme')) {
|
||||
headers.delete(':scheme')
|
||||
}
|
||||
|
||||
const response = await fetch(this._url, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
@@ -216,6 +230,21 @@ export class StreamableHTTPClientTransport implements Transport {
|
||||
headers.set('content-type', 'application/json')
|
||||
headers.set('accept', 'application/json, text/event-stream')
|
||||
|
||||
// 添加错误处理,确保不使用HTTP/2伪头部
|
||||
// 删除可能存在的HTTP/2伪头部
|
||||
if (headers.has(':path')) {
|
||||
headers.delete(':path')
|
||||
}
|
||||
if (headers.has(':method')) {
|
||||
headers.delete(':method')
|
||||
}
|
||||
if (headers.has(':authority')) {
|
||||
headers.delete(':authority')
|
||||
}
|
||||
if (headers.has(':scheme')) {
|
||||
headers.delete(':scheme')
|
||||
}
|
||||
|
||||
const init = {
|
||||
...this._requestInit,
|
||||
method: 'POST',
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { getConfigDir } from '../utils/file'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { ipcMain } from 'electron'
|
||||
import log from 'electron-log'
|
||||
|
||||
// 定义记忆文件路径
|
||||
const memoryDataPath = path.join(getConfigDir(), 'memory-data.json')
|
||||
|
||||
export class MemoryFileService {
|
||||
constructor() {
|
||||
this.ensureMemoryFileExists()
|
||||
this.registerIpcHandlers()
|
||||
}
|
||||
|
||||
private async ensureMemoryFileExists() {
|
||||
try {
|
||||
const directory = path.dirname(memoryDataPath)
|
||||
await fs.mkdir(directory, { recursive: true })
|
||||
try {
|
||||
await fs.access(memoryDataPath)
|
||||
} catch (error) {
|
||||
// 文件不存在,创建一个空文件
|
||||
await fs.writeFile(memoryDataPath, JSON.stringify({
|
||||
memoryLists: [],
|
||||
memories: [],
|
||||
shortMemories: []
|
||||
}, null, 2))
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Failed to ensure memory file exists:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private registerIpcHandlers() {
|
||||
// 读取记忆数据
|
||||
ipcMain.handle(IpcChannel.Memory_LoadData, async () => {
|
||||
try {
|
||||
const data = await fs.readFile(memoryDataPath, 'utf-8')
|
||||
return JSON.parse(data)
|
||||
} catch (error) {
|
||||
log.error('Failed to load memory data:', error)
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
// 保存记忆数据
|
||||
ipcMain.handle(IpcChannel.Memory_SaveData, async (_, data) => {
|
||||
try {
|
||||
await fs.writeFile(memoryDataPath, JSON.stringify(data, null, 2))
|
||||
return true
|
||||
} catch (error) {
|
||||
log.error('Failed to save memory data:', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 创建单例实例
|
||||
export const memoryFileService = new MemoryFileService()
|
||||
@@ -127,7 +127,15 @@ export class ProxyManager {
|
||||
const [protocol, address] = proxyUrl.split('://')
|
||||
const [host, port] = address.split(':')
|
||||
if (!protocol.includes('socks')) {
|
||||
setGlobalDispatcher(new ProxyAgent(proxyUrl))
|
||||
// 使用标准方式创建ProxyAgent,但添加错误处理
|
||||
try {
|
||||
// 尝试使用代理
|
||||
const agent = new ProxyAgent(proxyUrl)
|
||||
setGlobalDispatcher(agent)
|
||||
console.log('[Proxy] Successfully set HTTP proxy:', proxyUrl)
|
||||
} catch (error) {
|
||||
console.error('[Proxy] Failed to set proxy:', error)
|
||||
}
|
||||
} else {
|
||||
const dispatcher = socksDispatcher({
|
||||
port: parseInt(port),
|
||||
|
||||
Vendored
+4
@@ -190,6 +190,10 @@ declare global {
|
||||
closeSearchWindow: (uid: string) => Promise<string>
|
||||
openUrlInSearchWindow: (uid: string, url: string) => Promise<string>
|
||||
}
|
||||
memory: {
|
||||
loadData: () => Promise<any>
|
||||
saveData: (data: any) => Promise<boolean>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +177,10 @@ const api = {
|
||||
openSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Open, uid),
|
||||
closeSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Close, uid),
|
||||
openUrlInSearchWindow: (uid: string, url: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_OpenUrl, uid, url)
|
||||
},
|
||||
memory: {
|
||||
loadData: () => ipcRenderer.invoke(IpcChannel.Memory_LoadData),
|
||||
saveData: (data: any) => ipcRenderer.invoke(IpcChannel.Memory_SaveData, data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMemoryService } from '@renderer/services/MemoryService'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { clearShortMemories } from '@renderer/store/memory'
|
||||
import store from '@renderer/store'
|
||||
import { clearShortMemories, loadMemoryData } from '@renderer/store/memory'
|
||||
import { FC, ReactNode, useEffect, useRef } from 'react'
|
||||
|
||||
interface MemoryProviderProps {
|
||||
@@ -37,18 +38,40 @@ const MemoryProvider: FC<MemoryProviderProps> = ({ children }) => {
|
||||
// 添加一个 ref 来存储上次分析时的消息数量
|
||||
const lastAnalyzedCountRef = useRef(0)
|
||||
|
||||
// 在组件挂载时加载记忆数据
|
||||
useEffect(() => {
|
||||
console.log('[MemoryProvider] Loading memory data from file')
|
||||
dispatch(loadMemoryData())
|
||||
}, [])
|
||||
|
||||
// 当对话更新时,触发记忆分析
|
||||
useEffect(() => {
|
||||
if (isActive && autoAnalyze && analyzeModel && messages.length > 0) {
|
||||
// 获取当前的分析频率
|
||||
const memoryState = store.getState().memory || {}
|
||||
const analysisFrequency = memoryState.analysisFrequency || 5
|
||||
const adaptiveAnalysisEnabled = memoryState.adaptiveAnalysisEnabled || false
|
||||
|
||||
// 检查是否有新消息需要分析
|
||||
const newMessagesCount = messages.length - lastAnalyzedCountRef.current
|
||||
|
||||
// 当有 5 条或更多新消息,或者消息数量是 5 的倍数且从未分析过时触发分析
|
||||
if (newMessagesCount >= 5 || (messages.length % 5 === 0 && lastAnalyzedCountRef.current === 0)) {
|
||||
console.log(`[Memory Analysis] Triggering analysis with ${newMessagesCount} new messages`)
|
||||
// 使用自适应分析频率
|
||||
if (
|
||||
newMessagesCount >= analysisFrequency ||
|
||||
(messages.length % analysisFrequency === 0 && lastAnalyzedCountRef.current === 0)
|
||||
) {
|
||||
console.log(
|
||||
`[Memory Analysis] Triggering analysis with ${newMessagesCount} new messages (frequency: ${analysisFrequency})`
|
||||
)
|
||||
|
||||
// 将当前话题ID传递给分析函数
|
||||
analyzeAndAddMemories(currentTopic)
|
||||
lastAnalyzedCountRef.current = messages.length
|
||||
|
||||
// 性能监控:记录当前分析触发时的消息数量
|
||||
if (adaptiveAnalysisEnabled) {
|
||||
console.log(`[Memory Analysis] Adaptive analysis enabled, current frequency: ${analysisFrequency}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isActive, autoAnalyze, analyzeModel, messages.length, analyzeAndAddMemories, currentTopic])
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
|
||||
import { DeleteOutlined, InfoCircleOutlined } from '@ant-design/icons'
|
||||
import { Box } from '@renderer/components/Layout'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { addShortMemoryItem, analyzeAndAddShortMemories } from '@renderer/services/MemoryService'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import store from '@renderer/store'
|
||||
import { deleteShortMemory } from '@renderer/store/memory'
|
||||
import { Button, Empty, Input, List, Modal, Tooltip } from 'antd'
|
||||
import { Button, Card, Col, Empty, Input, List, Modal, Row, Statistic, Tooltip } from 'antd'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const { confirm } = Modal
|
||||
// 不再需要确认对话框
|
||||
|
||||
const ButtonGroup = styled.div`
|
||||
display: flex;
|
||||
@@ -85,16 +86,10 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 删除短记忆
|
||||
// 删除短记忆 - 直接删除无需确认
|
||||
const handleDeleteMemory = (id: string) => {
|
||||
confirm({
|
||||
title: t('settings.memory.confirmDelete'),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: t('settings.memory.confirmDeleteContent'),
|
||||
onOk() {
|
||||
dispatch(deleteShortMemory(id))
|
||||
}
|
||||
})
|
||||
// 直接删除记忆,无需确认对话框
|
||||
dispatch(deleteShortMemory(id))
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
@@ -137,6 +132,46 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
|
||||
{/* 性能监控统计信息 */}
|
||||
<Box mb={16}>
|
||||
<Card
|
||||
size="small"
|
||||
title={t('settings.memory.performanceStats') || '系统性能统计'}
|
||||
extra={<InfoCircleOutlined />}>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={t('settings.memory.totalAnalyses') || '总分析次数'}
|
||||
value={store.getState().memory?.analysisStats?.totalAnalyses || 0}
|
||||
precision={0}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={t('settings.memory.successRate') || '成功率'}
|
||||
value={
|
||||
store.getState().memory?.analysisStats?.totalAnalyses
|
||||
? ((store.getState().memory?.analysisStats?.successfulAnalyses || 0) /
|
||||
(store.getState().memory?.analysisStats?.totalAnalyses || 1)) *
|
||||
100
|
||||
: 0
|
||||
}
|
||||
precision={1}
|
||||
suffix="%"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={t('settings.memory.avgAnalysisTime') || '平均分析时间'}
|
||||
value={store.getState().memory?.analysisStats?.averageAnalysisTime || 0}
|
||||
precision={0}
|
||||
suffix="ms"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
<MemoriesList>
|
||||
{shortMemories.length > 0 ? (
|
||||
<List
|
||||
|
||||
@@ -1055,6 +1055,10 @@
|
||||
"shortMemoryAnalysisNoNewContent": "No new important information found or all information already exists",
|
||||
"shortMemoryAnalysisError": "Analysis Failed",
|
||||
"shortMemoryAnalysisErrorContent": "Error occurred while analyzing conversation content",
|
||||
"performanceStats": "Performance Statistics",
|
||||
"totalAnalyses": "Total Analyses",
|
||||
"successRate": "Success Rate",
|
||||
"avgAnalysisTime": "Average Analysis Time",
|
||||
"deduplication": {
|
||||
"title": "Memory Deduplication",
|
||||
"description": "Analyze similar memories in your memory library and provide intelligent merging suggestions.",
|
||||
|
||||
@@ -1404,7 +1404,11 @@
|
||||
"noCurrentTopic": "まず会話トピックを選択してください",
|
||||
"confirmDelete": "削除の確認",
|
||||
"confirmDeleteContent": "この短期メモリーを削除しますか?",
|
||||
"delete": "削除"
|
||||
"delete": "削除",
|
||||
"performanceStats": "パフォーマンス統計",
|
||||
"totalAnalyses": "分析回数合計",
|
||||
"successRate": "成功率",
|
||||
"avgAnalysisTime": "平均分析時間"
|
||||
}
|
||||
},
|
||||
"translate": {
|
||||
|
||||
@@ -1060,6 +1060,25 @@
|
||||
"shortMemoryAnalysisNoNewContent": "未发现新的重要信息或所有信息已存在",
|
||||
"shortMemoryAnalysisError": "分析失败",
|
||||
"shortMemoryAnalysisErrorContent": "分析对话内容时出错",
|
||||
"performanceStats": "性能统计",
|
||||
"totalAnalyses": "总分析次数",
|
||||
"successRate": "成功率",
|
||||
"avgAnalysisTime": "平均分析时间",
|
||||
"priorityManagement": {
|
||||
"title": "智能优先级与时效性管理",
|
||||
"description": "智能管理记忆的优先级、衰减和鲜度,确保最重要和最相关的记忆优先显示。",
|
||||
"enable": "启用智能优先级管理",
|
||||
"enableTip": "启用后,系统将根据重要性、访问频率和时间因素自动排序记忆",
|
||||
"decay": "记忆衰减",
|
||||
"decayTip": "随着时间推移,未访问的记忆重要性会逐渐降低",
|
||||
"decayRate": "衰减速率",
|
||||
"decayRateTip": "值越大,记忆衰减越快。0.05表示每天衰减5%",
|
||||
"freshness": "记忆鲜度",
|
||||
"freshnessTip": "考虑记忆的创建时间和最后访问时间,优先显示较新的记忆",
|
||||
"updateNow": "立即更新优先级",
|
||||
"updateNowTip": "手动更新所有记忆的优先级和鲜度评分",
|
||||
"update": "更新"
|
||||
},
|
||||
"deduplication": {
|
||||
"title": "记忆去重与合并",
|
||||
"description": "分析记忆库中的相似记忆,提供智能合并建议。",
|
||||
|
||||
@@ -1404,7 +1404,11 @@
|
||||
"noCurrentTopic": "請先選擇一個對話話題",
|
||||
"confirmDelete": "確認刪除",
|
||||
"confirmDeleteContent": "確定要刪除這條短期記憶嗎?",
|
||||
"delete": "刪除"
|
||||
"delete": "刪除",
|
||||
"performanceStats": "性能統計",
|
||||
"totalAnalyses": "總分析次數",
|
||||
"successRate": "成功率",
|
||||
"avgAnalysisTime": "平均分析時間"
|
||||
}
|
||||
},
|
||||
"translate": {
|
||||
|
||||
@@ -25,7 +25,7 @@ import ImagePreview from './ImagePreview'
|
||||
import Link from './Link'
|
||||
|
||||
const ALLOWED_ELEMENTS =
|
||||
/<(style|p|div|span|b|i|strong|em|ul|ol|li|table|tr|td|th|thead|tbody|h[1-6]|blockquote|pre|code|br|hr|svg|path|circle|rect|line|polyline|polygon|text|g|defs|title|desc|tspan|sub|sup)/i
|
||||
/<(style|p|div|span|b|i|strong|em|ul|ol|li|table|tr|td|th|thead|tbody|h[1-6]|blockquote|pre|code|br|hr|svg|path|circle|rect|line|polyline|polygon|text|g|defs|title|desc|tspan|sub|sup|think)/i
|
||||
|
||||
interface Props {
|
||||
message: Message
|
||||
@@ -56,7 +56,27 @@ const Markdown: FC<Props> = ({ message }) => {
|
||||
a: (props: any) => <Link {...props} citationData={parseJSON(findCitationInChildren(props.children))} />,
|
||||
code: CodeBlock,
|
||||
img: ImagePreview,
|
||||
pre: (props: any) => <pre style={{ overflow: 'visible' }} {...props} />
|
||||
pre: (props: any) => <pre style={{ overflow: 'visible' }} {...props} />,
|
||||
// 自定义处理think标签
|
||||
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>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
} as Partial<Components>
|
||||
return baseComponents
|
||||
}, [])
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
|
||||
import { DeleteOutlined } from '@ant-design/icons'
|
||||
import { TopicManager } from '@renderer/hooks/useTopic'
|
||||
import { addShortMemoryItem } from '@renderer/services/MemoryService'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import store from '@renderer/store'
|
||||
import { deleteShortMemory, setShortMemoryActive, ShortMemory } from '@renderer/store/memory' // Import ShortMemory from here
|
||||
import { Topic } from '@renderer/types' // Remove ShortMemory import from here
|
||||
import { Button, Collapse, Empty, Input, List, Modal, Switch, Tooltip, Typography } from 'antd'
|
||||
import { Button, Collapse, Empty, Input, List, Switch, Tooltip, Typography } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const { Title } = Typography
|
||||
const { confirm } = Modal
|
||||
// 不再需要确认对话框
|
||||
// const { Panel } = Collapse // Panel is no longer used
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
@@ -167,16 +167,10 @@ const CollapsibleShortMemoryManager = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 删除短记忆
|
||||
// 删除短记忆 - 直接删除无需确认
|
||||
const handleDeleteMemory = (id: string) => {
|
||||
confirm({
|
||||
title: t('settings.memory.confirmDelete'),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: t('settings.memory.confirmDeleteContent'),
|
||||
onOk() {
|
||||
dispatch(deleteShortMemory(id))
|
||||
}
|
||||
})
|
||||
// 直接删除记忆,无需确认对话框
|
||||
dispatch(deleteShortMemory(id))
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import { InfoCircleOutlined } from '@ant-design/icons'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import {
|
||||
setDecayEnabled,
|
||||
setDecayRate,
|
||||
setFreshnessEnabled,
|
||||
setPriorityManagementEnabled,
|
||||
updateMemoryPriorities
|
||||
} from '@renderer/store/memory'
|
||||
import { Button, InputNumber, Slider, Switch, Tooltip } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||
|
||||
const SliderContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin-right: 16px;
|
||||
`
|
||||
|
||||
const PriorityManagementSettings: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
// 获取相关状态
|
||||
const priorityManagementEnabled = useAppSelector((state) => state.memory.priorityManagementEnabled)
|
||||
const decayEnabled = useAppSelector((state) => state.memory.decayEnabled)
|
||||
const freshnessEnabled = useAppSelector((state) => state.memory.freshnessEnabled)
|
||||
const decayRate = useAppSelector((state) => state.memory.decayRate)
|
||||
|
||||
// 处理开关状态变化
|
||||
const handlePriorityManagementToggle = (checked: boolean) => {
|
||||
dispatch(setPriorityManagementEnabled(checked))
|
||||
}
|
||||
|
||||
const handleDecayToggle = (checked: boolean) => {
|
||||
dispatch(setDecayEnabled(checked))
|
||||
}
|
||||
|
||||
const handleFreshnessToggle = (checked: boolean) => {
|
||||
dispatch(setFreshnessEnabled(checked))
|
||||
}
|
||||
|
||||
// 处理衰减率变化
|
||||
const handleDecayRateChange = (value: number | null) => {
|
||||
if (value !== null) {
|
||||
dispatch(setDecayRate(value))
|
||||
}
|
||||
}
|
||||
|
||||
// 手动更新记忆优先级
|
||||
const handleUpdatePriorities = () => {
|
||||
dispatch(updateMemoryPriorities())
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingGroup>
|
||||
<SettingTitle>{t('settings.memory.priorityManagement.title') || '智能优先级与时效性管理'}</SettingTitle>
|
||||
<SettingHelpText>
|
||||
{t('settings.memory.priorityManagement.description') ||
|
||||
'智能管理记忆的优先级、衰减和鲜度,确保最重要和最相关的记忆优先显示。'}
|
||||
</SettingHelpText>
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
{t('settings.memory.priorityManagement.enable') || '启用智能优先级管理'}
|
||||
<Tooltip title={t('settings.memory.priorityManagement.enableTip') ||
|
||||
'启用后,系统将根据重要性、访问频率和时间因素自动排序记忆'}>
|
||||
<InfoCircleOutlined style={{ marginLeft: 8 }} />
|
||||
</Tooltip>
|
||||
</SettingRowTitle>
|
||||
<Switch checked={priorityManagementEnabled} onChange={handlePriorityManagementToggle} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
{t('settings.memory.priorityManagement.decay') || '记忆衰减'}
|
||||
<Tooltip title={t('settings.memory.priorityManagement.decayTip') ||
|
||||
'随着时间推移,未访问的记忆重要性会逐渐降低'}>
|
||||
<InfoCircleOutlined style={{ marginLeft: 8 }} />
|
||||
</Tooltip>
|
||||
</SettingRowTitle>
|
||||
<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%'}>
|
||||
<InfoCircleOutlined style={{ marginLeft: 8 }} />
|
||||
</Tooltip>
|
||||
</SettingRowTitle>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<SliderContainer>
|
||||
<Slider
|
||||
min={0.01}
|
||||
max={0.2}
|
||||
step={0.01}
|
||||
value={decayRate}
|
||||
onChange={handleDecayRateChange}
|
||||
disabled={!priorityManagementEnabled || !decayEnabled}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</SliderContainer>
|
||||
<InputNumber
|
||||
min={0.01}
|
||||
max={0.2}
|
||||
step={0.01}
|
||||
value={decayRate}
|
||||
onChange={handleDecayRateChange}
|
||||
disabled={!priorityManagementEnabled || !decayEnabled}
|
||||
style={{ width: 70 }}
|
||||
/>
|
||||
</div>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
{t('settings.memory.priorityManagement.freshness') || '记忆鲜度'}
|
||||
<Tooltip title={t('settings.memory.priorityManagement.freshnessTip') ||
|
||||
'考虑记忆的创建时间和最后访问时间,优先显示较新的记忆'}>
|
||||
<InfoCircleOutlined style={{ marginLeft: 8 }} />
|
||||
</Tooltip>
|
||||
</SettingRowTitle>
|
||||
<Switch
|
||||
checked={freshnessEnabled}
|
||||
onChange={handleFreshnessToggle}
|
||||
disabled={!priorityManagementEnabled}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
{t('settings.memory.priorityManagement.updateNow') || '立即更新优先级'}
|
||||
<Tooltip title={t('settings.memory.priorityManagement.updateNowTip') ||
|
||||
'手动更新所有记忆的优先级和鲜度评分'}>
|
||||
<InfoCircleOutlined style={{ marginLeft: 8 }} />
|
||||
</Tooltip>
|
||||
</SettingRowTitle>
|
||||
<Button
|
||||
onClick={handleUpdatePriorities}
|
||||
disabled={!priorityManagementEnabled}
|
||||
>
|
||||
{t('settings.memory.priorityManagement.update') || '更新'}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default PriorityManagementSettings
|
||||
@@ -1,13 +1,13 @@
|
||||
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
|
||||
import { DeleteOutlined } from '@ant-design/icons'
|
||||
import { addShortMemoryItem } from '@renderer/services/MemoryService'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { deleteShortMemory, setShortMemoryActive } from '@renderer/store/memory'
|
||||
import { Button, Empty, Input, List, Modal, Switch, Tooltip, Typography } from 'antd'
|
||||
import { Button, Empty, Input, List, Switch, Tooltip, Typography } from 'antd'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const { Title } = Typography
|
||||
const { confirm } = Modal
|
||||
// 不再需要确认对话框
|
||||
|
||||
const ShortMemoryManager = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -40,16 +40,10 @@ const ShortMemoryManager = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 删除短记忆
|
||||
// 删除短记忆 - 直接删除无需确认
|
||||
const handleDeleteMemory = (id: string) => {
|
||||
confirm({
|
||||
title: t('settings.memory.confirmDelete'),
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: t('settings.memory.confirmDeleteContent'),
|
||||
onOk() {
|
||||
dispatch(deleteShortMemory(id))
|
||||
}
|
||||
})
|
||||
// 直接删除记忆,无需确认对话框
|
||||
dispatch(deleteShortMemory(id))
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -40,6 +40,7 @@ import CollapsibleShortMemoryManager from './CollapsibleShortMemoryManager'
|
||||
import MemoryDeduplicationPanel from './MemoryDeduplicationPanel'
|
||||
import MemoryListManager from './MemoryListManager'
|
||||
import MemoryMindMap from './MemoryMindMap'
|
||||
import PriorityManagementSettings from './PriorityManagementSettings'
|
||||
|
||||
const MemorySettings: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -62,19 +63,67 @@ const MemorySettings: FC = () => {
|
||||
|
||||
// 使用 useMemo 缓存模型数组,避免不必要的重新渲染
|
||||
const models = useMemo(() => {
|
||||
// 获取所有模型,不过滤可用性
|
||||
return providers.flatMap((provider) => provider.models || [])
|
||||
// 只获取已启用的提供商的模型
|
||||
return providers
|
||||
.filter(provider => provider.enabled) // 只保留已启用的提供商
|
||||
.flatMap((provider) => provider.models || [])
|
||||
}, [providers])
|
||||
|
||||
// 使用 useMemo 缓存模型选项数组,避免不必要的重新渲染
|
||||
const modelOptions = useMemo(() => {
|
||||
if (models.length > 0) {
|
||||
return models.map((model) => ({
|
||||
label: model.name,
|
||||
value: model.id
|
||||
// 按提供商分组模型
|
||||
const modelsByProvider = models.reduce(
|
||||
(acc, model) => {
|
||||
const provider = providers.find((p) => p.models.some((m) => m.id === model.id))
|
||||
const providerName = provider ? (provider.isSystem ? t(`provider.${provider.id}`) : provider.name) : ''
|
||||
|
||||
if (!acc[providerName]) {
|
||||
acc[providerName] = []
|
||||
}
|
||||
|
||||
// 检查是否已经存在相同的模型,避免重复
|
||||
const isDuplicate = acc[providerName].some((m) => m.value === model.id)
|
||||
if (!isDuplicate) {
|
||||
acc[providerName].push({
|
||||
label: `${model.name}`,
|
||||
value: model.id
|
||||
})
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, { label: string; value: string }[]>
|
||||
)
|
||||
|
||||
// 转换为Select组件的options格式
|
||||
const groupedOptions = Object.entries(modelsByProvider).map(([provider, models]) => ({
|
||||
label: provider,
|
||||
options: models
|
||||
}))
|
||||
|
||||
// 将分组选项展平为单个选项数组,以兼容现有代码
|
||||
const flatOptions = models.reduce(
|
||||
(acc, model) => {
|
||||
// 检查是否已经存在相同的模型,避免重复
|
||||
const isDuplicate = acc.some((m) => m.value === model.id)
|
||||
if (!isDuplicate) {
|
||||
acc.push({
|
||||
label: model.name,
|
||||
value: model.id
|
||||
})
|
||||
}
|
||||
return acc
|
||||
},
|
||||
[] as { label: string; value: string }[]
|
||||
)
|
||||
|
||||
return {
|
||||
groupedOptions,
|
||||
flatOptions
|
||||
}
|
||||
} else {
|
||||
return [
|
||||
const defaultOptions = [
|
||||
// 默认模型选项
|
||||
{ label: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' },
|
||||
{ label: 'GPT-4', value: 'gpt-4' },
|
||||
@@ -82,8 +131,12 @@ const MemorySettings: FC = () => {
|
||||
{ label: 'Claude 3 Sonnet', value: 'claude-3-sonnet-20240229' },
|
||||
{ label: 'Claude 3 Haiku', value: 'claude-3-haiku-20240307' }
|
||||
]
|
||||
return {
|
||||
groupedOptions: [],
|
||||
flatOptions: defaultOptions
|
||||
}
|
||||
}
|
||||
}, [models])
|
||||
}, [models, providers, t])
|
||||
|
||||
// 如果没有模型,添加一个默认模型
|
||||
useEffect(() => {
|
||||
@@ -435,8 +488,10 @@ const MemorySettings: FC = () => {
|
||||
value={shortMemoryAnalyzeModel}
|
||||
onChange={handleSelectShortMemoryModel}
|
||||
placeholder={t('settings.memory.selectModel') || '选择模型'}
|
||||
options={modelOptions}
|
||||
options={modelOptions.groupedOptions}
|
||||
disabled={!isActive || !autoAnalyze} // 确保在未激活或未开启自动分析时禁用
|
||||
optionFilterProp="label"
|
||||
listHeight={300}
|
||||
/>
|
||||
</SettingRow>
|
||||
)}
|
||||
@@ -505,6 +560,20 @@ const MemorySettings: FC = () => {
|
||||
</TabPaneSettingGroup>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'priorityManagement',
|
||||
label: (
|
||||
<TabLabelContainer>
|
||||
<TabDot color="#722ed1">●</TabDot>
|
||||
{t('settings.memory.priorityManagement.title') || '智能优先级管理'}
|
||||
</TabLabelContainer>
|
||||
),
|
||||
children: (
|
||||
<TabPaneSettingGroup theme={theme}>
|
||||
<PriorityManagementSettings />
|
||||
</TabPaneSettingGroup>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'longMemory',
|
||||
label: (
|
||||
@@ -543,8 +612,10 @@ const MemorySettings: FC = () => {
|
||||
value={analyzeModel}
|
||||
onChange={handleSelectModel}
|
||||
placeholder={t('settings.memory.selectModel') || '选择模型'}
|
||||
options={modelOptions}
|
||||
options={modelOptions.groupedOptions}
|
||||
disabled={!isActive || !autoAnalyze}
|
||||
optionFilterProp="label"
|
||||
listHeight={300}
|
||||
/>
|
||||
</SettingRow>
|
||||
)}
|
||||
@@ -707,11 +778,7 @@ const MemorySettings: FC = () => {
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<div>
|
||||
{memory.category && (
|
||||
<TagWithCursor color="blue">
|
||||
{memory.category}
|
||||
</TagWithCursor>
|
||||
)}
|
||||
{memory.category && <TagWithCursor color="blue">{memory.category}</TagWithCursor>}
|
||||
{memory.content}
|
||||
</div>
|
||||
}
|
||||
@@ -900,7 +967,7 @@ const TabLabelContainer = styled.span`
|
||||
|
||||
const TabDot = styled.span<{ color: string }>`
|
||||
font-size: 18px;
|
||||
color: ${props => props.color};
|
||||
color: ${(props) => props.color};
|
||||
`
|
||||
|
||||
const ButtonsContainer = styled.div`
|
||||
|
||||
@@ -5,9 +5,73 @@ import { fetchGenerate } from '@renderer/services/ApiService' // Import fetchGen
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
// Removed duplicate import: import store from '@renderer/store';
|
||||
import store from '@renderer/store' // Import store
|
||||
import { addMemory, addShortMemory, setAnalyzing } from '@renderer/store/memory'
|
||||
import {
|
||||
addAnalysisLatency,
|
||||
addMemory,
|
||||
addShortMemory,
|
||||
setAnalyzing,
|
||||
updateAnalysisStats,
|
||||
updatePerformanceMetrics,
|
||||
updateUserInterest,
|
||||
updateMemoryPriorities,
|
||||
accessMemory,
|
||||
Memory
|
||||
} from '@renderer/store/memory'
|
||||
import { useCallback, useEffect, useRef } from 'react' // Add useRef back
|
||||
|
||||
// 计算对话复杂度,用于调整分析深度
|
||||
const calculateConversationComplexity = (conversation: string): 'low' | 'medium' | 'high' => {
|
||||
const wordCount = conversation.split(/\s+/).length
|
||||
const sentenceCount = conversation.split(/[.!?]+/).length
|
||||
const avgSentenceLength = wordCount / (sentenceCount || 1)
|
||||
|
||||
// 简单的复杂度评估算法
|
||||
if (wordCount < 100 || avgSentenceLength < 5) {
|
||||
return 'low'
|
||||
} else if (wordCount > 500 || avgSentenceLength > 15) {
|
||||
return 'high'
|
||||
} else {
|
||||
return 'medium'
|
||||
}
|
||||
}
|
||||
|
||||
// 根据分析深度调整提示词
|
||||
const adjustPromptForDepth = (basePrompt: string, depth: 'low' | 'medium' | 'high'): string => {
|
||||
switch (depth) {
|
||||
case 'low':
|
||||
// 简化提示词,减少分析要求
|
||||
return basePrompt
|
||||
.replace(/\u8be6\u7ec6\u5206\u6790/g, '\u7b80\u8981\u5206\u6790')
|
||||
.replace(/\u63d0\u53d6\u51fa\u91cd\u8981\u7684/g, '\u63d0\u53d6\u51fa\u6700\u91cd\u8981\u7684')
|
||||
case 'high':
|
||||
// 增强提示词,要求更深入的分析
|
||||
return (
|
||||
basePrompt +
|
||||
'\n\n\u8bf7\u8fdb\u884c\u66f4\u6df1\u5165\u7684\u5206\u6790\uff0c\u8003\u8651\u9690\u542b\u7684\u7528\u6237\u9700\u6c42\u548c\u504f\u597d\uff0c\u8bc6\u522b\u6f5c\u5728\u7684\u5173\u8054\u4fe1\u606f\u3002'
|
||||
)
|
||||
default:
|
||||
return basePrompt
|
||||
}
|
||||
}
|
||||
|
||||
// 提取用户关注点
|
||||
const extractUserInterests = (conversation: string): string[] => {
|
||||
// 简单实现:提取对话中的关键词或主题
|
||||
const topics = new Set<string>()
|
||||
|
||||
// 简单的关键词提取,匹配4个或更多字符的单词
|
||||
const keywords = conversation.match(/\b\w{4,}\b/g) || []
|
||||
const commonWords = ['this', 'that', 'these', 'those', 'with', 'from', 'have', 'what', 'when', 'where', 'which']
|
||||
|
||||
keywords.forEach((word) => {
|
||||
if (!commonWords.includes(word.toLowerCase())) {
|
||||
topics.add(word.toLowerCase())
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(topics)
|
||||
}
|
||||
|
||||
// 分析对话内容并提取重要信息
|
||||
const analyzeConversation = async (
|
||||
conversation: string,
|
||||
@@ -204,13 +268,26 @@ export const useMemoryService = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// 性能监控:记录开始时间
|
||||
const startTime = performance.now()
|
||||
|
||||
dispatch(setAnalyzing(true))
|
||||
console.log('[Memory Analysis] Starting analysis...')
|
||||
console.log(`[Memory Analysis] Analyzing topic: ${targetTopicId}`)
|
||||
console.log('[Memory Analysis] Conversation length:', newConversation.length)
|
||||
|
||||
// 自适应分析:根据对话复杂度调整分析深度
|
||||
const conversationComplexity = calculateConversationComplexity(newConversation)
|
||||
let analysisDepth = memoryState.analysisDepth || 'medium'
|
||||
|
||||
// 如果启用了自适应分析,根据复杂度调整深度
|
||||
if (memoryState.adaptiveAnalysisEnabled) {
|
||||
analysisDepth = conversationComplexity
|
||||
console.log(`[Memory Analysis] Adjusted analysis depth to ${analysisDepth} based on conversation complexity`)
|
||||
}
|
||||
|
||||
// 构建长期记忆分析提示词,包含已有记忆和新对话
|
||||
const prompt = `
|
||||
const basePrompt = `
|
||||
请分析以下对话内容,提取出重要的用户偏好、习惯、需求和背景信息,这些信息在未来的对话中可能有用。
|
||||
|
||||
将每条信息分类并按以下格式返回:
|
||||
@@ -236,12 +313,75 @@ ${existingMemoriesContent}
|
||||
${newConversation}
|
||||
`
|
||||
|
||||
// 根据分析深度调整提示词
|
||||
const adjustedPrompt = adjustPromptForDepth(basePrompt, analysisDepth)
|
||||
|
||||
// 调用分析函数,传递自定义提示词
|
||||
const memories = await analyzeConversation(newConversation, memoryState.analyzeModel!, prompt)
|
||||
const memories = await analyzeConversation(newConversation, memoryState.analyzeModel!, adjustedPrompt)
|
||||
|
||||
// 用户关注点学习
|
||||
if (memoryState.interestTrackingEnabled) {
|
||||
const newTopics = extractUserInterests(newConversation)
|
||||
if (newTopics.length > 0) {
|
||||
console.log(`[Memory Analysis] Extracted user interests: ${newTopics.join(', ')}`)
|
||||
|
||||
// 更新用户关注点
|
||||
const now = new Date().toISOString()
|
||||
const updatedInterests = [...(memoryState.userInterests || [])]
|
||||
|
||||
// 增加新发现的关注点权重
|
||||
newTopics.forEach((topic) => {
|
||||
const existingIndex = updatedInterests.findIndex((i) => i.topic === topic)
|
||||
if (existingIndex >= 0) {
|
||||
// 已存在的关注点,增加权重
|
||||
const updatedInterest = {
|
||||
...updatedInterests[existingIndex],
|
||||
weight: Math.min(1, updatedInterests[existingIndex].weight + 0.1),
|
||||
lastUpdated: now
|
||||
}
|
||||
store.dispatch(updateUserInterest(updatedInterest))
|
||||
} else {
|
||||
// 新的关注点
|
||||
const newInterest = {
|
||||
topic,
|
||||
weight: 0.5, // 初始权重
|
||||
lastUpdated: now
|
||||
}
|
||||
store.dispatch(updateUserInterest(newInterest))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
console.log('[Memory Analysis] Analysis complete. Memories extracted:', memories)
|
||||
|
||||
// 添加提取的记忆
|
||||
if (memories && memories.length > 0) {
|
||||
// 性能监控:记录分析时间
|
||||
const endTime = performance.now()
|
||||
const analysisTime = endTime - startTime
|
||||
|
||||
// 更新分析统计数据
|
||||
store.dispatch(
|
||||
updateAnalysisStats({
|
||||
totalAnalyses: (memoryState.analysisStats?.totalAnalyses || 0) + 1,
|
||||
successfulAnalyses: (memoryState.analysisStats?.successfulAnalyses || 0) + 1,
|
||||
newMemoriesGenerated: (memoryState.analysisStats?.newMemoriesGenerated || 0) + memories.length,
|
||||
averageAnalysisTime: memoryState.analysisStats?.totalAnalyses
|
||||
? ((memoryState.analysisStats.averageAnalysisTime || 0) *
|
||||
(memoryState.analysisStats.totalAnalyses || 0) +
|
||||
analysisTime) /
|
||||
((memoryState.analysisStats.totalAnalyses || 0) + 1)
|
||||
: analysisTime,
|
||||
lastAnalysisTime: Date.now()
|
||||
})
|
||||
)
|
||||
|
||||
// 性能监控:记录分析延迟
|
||||
try {
|
||||
store.dispatch(addAnalysisLatency(analysisTime))
|
||||
} catch (error) {
|
||||
console.warn('[Memory Analysis] Failed to add analysis latency:', error)
|
||||
}
|
||||
// 智能去重:使用AI模型检查语义相似的记忆
|
||||
const existingMemories = store.getState().memory?.memories || []
|
||||
|
||||
@@ -280,8 +420,64 @@ ${newConversation}
|
||||
}
|
||||
|
||||
console.log(`[Memory Analysis] Processed ${memories.length} potential memories, added ${newMemories.length}.`)
|
||||
|
||||
// 自适应分析:根据分析结果调整分析频率
|
||||
if (memoryState.adaptiveAnalysisEnabled) {
|
||||
// 如果分析成功率低,增加分析频率
|
||||
const successRate =
|
||||
(memoryState.analysisStats?.successfulAnalyses || 0) /
|
||||
Math.max(1, memoryState.analysisStats?.totalAnalyses || 1)
|
||||
let newFrequency = memoryState.analysisFrequency || 5
|
||||
|
||||
if (successRate < 0.3 && newFrequency > 3) {
|
||||
// 成功率低,减少分析频率(增加消息数阈值)
|
||||
newFrequency += 1
|
||||
console.log(
|
||||
`[Memory Analysis] Low success rate (${successRate.toFixed(2)}), increasing message threshold to ${newFrequency}`
|
||||
)
|
||||
} else if (successRate > 0.7 && newFrequency > 2) {
|
||||
// 成功率高,增加分析频率(减少消息数阈值)
|
||||
newFrequency -= 1
|
||||
console.log(
|
||||
`[Memory Analysis] High success rate (${successRate.toFixed(2)}), decreasing message threshold to ${newFrequency}`
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('[Memory Analysis] No new memories extracted.')
|
||||
|
||||
// 更新分析统计数据(分析失败)
|
||||
const endTime = performance.now()
|
||||
const analysisTime = endTime - startTime
|
||||
|
||||
store.dispatch(
|
||||
updateAnalysisStats({
|
||||
totalAnalyses: (memoryState.analysisStats?.totalAnalyses || 0) + 1,
|
||||
lastAnalysisTime: Date.now()
|
||||
})
|
||||
)
|
||||
|
||||
// 性能监控:记录分析延迟
|
||||
try {
|
||||
store.dispatch(addAnalysisLatency(analysisTime))
|
||||
} catch (error) {
|
||||
console.warn('[Memory Analysis] Failed to add analysis latency:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 性能监控:更新性能指标
|
||||
if (memoryState.monitoringEnabled) {
|
||||
try {
|
||||
store.dispatch(
|
||||
updatePerformanceMetrics({
|
||||
memoryCount: store.getState().memory?.memories.length || 0,
|
||||
shortMemoryCount: store.getState().memory?.shortMemories.length || 0,
|
||||
lastPerformanceCheck: Date.now()
|
||||
})
|
||||
)
|
||||
} catch (error) {
|
||||
console.warn('[Memory Analysis] Failed to update performance metrics:', error)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to analyze and add memories:', error)
|
||||
@@ -302,7 +498,37 @@ ${newConversation}
|
||||
analyzeAndAddMemoriesRef.current = analyzeAndAddMemories
|
||||
}, [analyzeAndAddMemories])
|
||||
|
||||
|
||||
|
||||
// 记录记忆访问
|
||||
const recordMemoryAccess = useCallback((memoryId: string, isShortMemory: boolean = false) => {
|
||||
store.dispatch(accessMemory({ id: memoryId, isShortMemory }))
|
||||
}, [])
|
||||
|
||||
// Effect 来设置/清除定时器,只依赖于启动条件
|
||||
useEffect(() => {
|
||||
// 定期更新记忆优先级
|
||||
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分钟更新一次
|
||||
|
||||
if (now - lastUpdate < updateInterval) return
|
||||
|
||||
console.log('[Memory Priority] Updating memory priorities and freshness...')
|
||||
store.dispatch(updateMemoryPriorities())
|
||||
}, 10 * 60 * 1000) // 每10分钟检查一次
|
||||
|
||||
return () => {
|
||||
clearInterval(priorityUpdateInterval)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Effect 来设置/清除分析定时器,只依赖于启动条件
|
||||
useEffect(() => {
|
||||
if (!isActive || !autoAnalyze || !analyzeModel) {
|
||||
console.log('[Memory Analysis Timer] Conditions not met for setting up timer:', {
|
||||
@@ -332,8 +558,8 @@ ${newConversation}
|
||||
// 依赖项只包含决定是否启动定时器的设置
|
||||
}, [isActive, autoAnalyze, analyzeModel])
|
||||
|
||||
// 返回分析函数,以便在MemoryProvider中使用
|
||||
return { analyzeAndAddMemories }
|
||||
// 返回分析函数和记忆访问函数,以便在其他组件中使用
|
||||
return { analyzeAndAddMemories, recordMemoryAccess }
|
||||
}
|
||||
|
||||
// 手动添加短记忆
|
||||
@@ -547,12 +773,13 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
|
||||
|
||||
const state = store.getState() // Use imported store
|
||||
// 确保 state.memory 存在,如果不存在则提供默认值
|
||||
const { isActive, memories, memoryLists, shortMemoryActive, shortMemories } = state.memory || {
|
||||
const { isActive, memories, memoryLists, shortMemoryActive, shortMemories, priorityManagementEnabled } = state.memory || {
|
||||
isActive: false,
|
||||
memories: [],
|
||||
memoryLists: [],
|
||||
shortMemoryActive: false,
|
||||
shortMemories: []
|
||||
shortMemories: [],
|
||||
priorityManagementEnabled: false
|
||||
}
|
||||
|
||||
// 获取当前话题ID
|
||||
@@ -564,7 +791,8 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
|
||||
listsCount: memoryLists?.length,
|
||||
shortMemoryActive,
|
||||
shortMemoriesCount: shortMemories?.length,
|
||||
currentTopicId
|
||||
currentTopicId,
|
||||
priorityManagementEnabled
|
||||
})
|
||||
|
||||
let result = systemPrompt
|
||||
@@ -573,7 +801,34 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
|
||||
// 处理短记忆
|
||||
if (shortMemoryActive && shortMemories && shortMemories.length > 0 && currentTopicId) {
|
||||
// 获取当前话题的短记忆
|
||||
const topicShortMemories = shortMemories.filter((memory) => memory.topicId === currentTopicId)
|
||||
let topicShortMemories = shortMemories.filter((memory) => memory.topicId === currentTopicId)
|
||||
|
||||
// 如果启用了智能优先级管理,根据优先级排序
|
||||
if (priorityManagementEnabled && topicShortMemories.length > 0) {
|
||||
// 计算每个记忆的综合分数(重要性 * 衰减因子 * 鲜度)
|
||||
const scoredMemories = topicShortMemories.map(memory => {
|
||||
// 记录访问
|
||||
store.dispatch(accessMemory({ id: memory.id, isShortMemory: true }))
|
||||
|
||||
// 计算综合分数
|
||||
const importance = memory.importance || 0.5
|
||||
const decayFactor = memory.decayFactor || 1
|
||||
const freshness = memory.freshness || 0.5
|
||||
const score = importance * decayFactor * (freshness * 2) // 短期记忆更注重鲜度
|
||||
return { memory, score }
|
||||
})
|
||||
|
||||
// 按综合分数降序排序
|
||||
scoredMemories.sort((a, b) => b.score - a.score)
|
||||
|
||||
// 提取排序后的记忆
|
||||
topicShortMemories = scoredMemories.map(item => item.memory)
|
||||
|
||||
// 限制数量,避免提示词过长
|
||||
if (topicShortMemories.length > 10) {
|
||||
topicShortMemories = topicShortMemories.slice(0, 10)
|
||||
}
|
||||
}
|
||||
|
||||
if (topicShortMemories.length > 0) {
|
||||
const shortMemoryPrompt = topicShortMemories.map((memory) => `- ${memory.content}`).join('\n')
|
||||
@@ -592,7 +847,45 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
|
||||
|
||||
if (activeListIds.length > 0) {
|
||||
// 只获取激活列表中的记忆
|
||||
const activeMemories = memories.filter((memory) => activeListIds.includes(memory.listId))
|
||||
let activeMemories = memories.filter((memory) => activeListIds.includes(memory.listId))
|
||||
|
||||
// 如果启用了智能优先级管理,根据优先级排序
|
||||
if (priorityManagementEnabled && activeMemories.length > 0) {
|
||||
// 计算每个记忆的综合分数
|
||||
const scoredMemories = activeMemories.map(memory => {
|
||||
// 记录访问
|
||||
store.dispatch(accessMemory({ id: memory.id }))
|
||||
|
||||
// 计算综合分数
|
||||
const importance = memory.importance || 0.5
|
||||
const decayFactor = memory.decayFactor || 1
|
||||
const freshness = memory.freshness || 0.5
|
||||
const score = importance * decayFactor * freshness
|
||||
return { memory, score }
|
||||
})
|
||||
|
||||
// 按综合分数降序排序
|
||||
scoredMemories.sort((a, b) => b.score - a.score)
|
||||
|
||||
// 限制每个列表的记忆数量
|
||||
const maxMemoriesPerList = 5
|
||||
const memoriesByList: Record<string, Memory[]> = {}
|
||||
|
||||
// 提取排序后的记忆
|
||||
const sortedMemories = scoredMemories.map(item => item.memory)
|
||||
|
||||
sortedMemories.forEach(memory => {
|
||||
if (!memoriesByList[memory.listId]) {
|
||||
memoriesByList[memory.listId] = []
|
||||
}
|
||||
if (memoriesByList[memory.listId].length < maxMemoriesPerList) {
|
||||
memoriesByList[memory.listId].push(memory)
|
||||
}
|
||||
})
|
||||
|
||||
// 重新构建活跃记忆列表
|
||||
activeMemories = Object.values(memoriesByList).flat() as Memory[]
|
||||
}
|
||||
|
||||
if (activeMemories.length > 0) {
|
||||
// 按列表分组构建记忆提示词
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
// src/renderer/src/services/VectorService.ts
|
||||
|
||||
// 导入Memory和ShortMemory接口
|
||||
interface Memory {
|
||||
id: string
|
||||
content: string
|
||||
createdAt: string
|
||||
source?: string
|
||||
category?: string
|
||||
listId: string
|
||||
analyzedMessageIds?: string[]
|
||||
lastMessageId?: string
|
||||
topicId?: string
|
||||
vectorRepresentation?: number[]
|
||||
entities?: string[]
|
||||
keywords?: string[]
|
||||
importance?: number
|
||||
accessCount?: number
|
||||
lastAccessedAt?: string
|
||||
}
|
||||
|
||||
interface ShortMemory {
|
||||
id: string
|
||||
content: string
|
||||
createdAt: string
|
||||
topicId: string
|
||||
analyzedMessageIds?: string[]
|
||||
lastMessageId?: string
|
||||
vectorRepresentation?: number[]
|
||||
entities?: string[]
|
||||
keywords?: string[]
|
||||
importance?: number
|
||||
}
|
||||
// TODO: Import necessary API clients or libraries for vector embedding (e.g., OpenAI)
|
||||
|
||||
/**
|
||||
* 计算两个向量之间的余弦相似度
|
||||
* @param vecA - 第一个向量
|
||||
* @param vecB - 第二个向量
|
||||
* @returns 余弦相似度值 (-1 到 1)
|
||||
*/
|
||||
function cosineSimilarity(vecA: number[], vecB: number[]): number {
|
||||
if (!vecA || !vecB || vecA.length !== vecB.length || vecA.length === 0) {
|
||||
// console.error('Invalid vectors for cosine similarity calculation.', vecA, vecB)
|
||||
return 0 // 或者抛出错误,取决于错误处理策略
|
||||
}
|
||||
|
||||
let dotProduct = 0.0
|
||||
let normA = 0.0
|
||||
let normB = 0.0
|
||||
for (let i = 0; i < vecA.length; i++) {
|
||||
dotProduct += vecA[i] * vecB[i]
|
||||
normA += vecA[i] * vecA[i]
|
||||
normB += vecB[i] * vecB[i]
|
||||
}
|
||||
|
||||
if (normA === 0 || normB === 0) {
|
||||
// console.warn('Zero vector encountered in cosine similarity calculation.')
|
||||
return 0 // 避免除以零
|
||||
}
|
||||
|
||||
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB))
|
||||
}
|
||||
|
||||
// 简单的内存缓存来存储向量表示
|
||||
const vectorCache = new Map<string, number[]>()
|
||||
|
||||
/**
|
||||
* VectorService 类负责处理记忆内容的向量化和相似度计算
|
||||
*/
|
||||
class VectorService {
|
||||
/**
|
||||
* 获取给定文本的向量表示。
|
||||
* 优先从缓存获取,否则调用API生成。
|
||||
* @param text - 需要向量化的文本
|
||||
* @param modelId - 使用的向量化模型ID (TODO: 需要从设置或状态中获取)
|
||||
* @returns 文本的向量表示 (number[]) 或 null (如果失败)
|
||||
*/
|
||||
async getVector(text: string, modelId: string = 'text-embedding-ada-002'): Promise<number[] | null> {
|
||||
if (!text || text.trim() === '') {
|
||||
return null
|
||||
}
|
||||
|
||||
const cacheKey = `${modelId}:${text}`
|
||||
if (vectorCache.has(cacheKey)) {
|
||||
return vectorCache.get(cacheKey)!
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: 实现调用向量化API的逻辑
|
||||
console.log(`[VectorService] Requesting vector for text (length: ${text.length})...`)
|
||||
// 示例: const response = await openai.embeddings.create({ model: modelId, input: text });
|
||||
// const vector = response?.data?.[0]?.embedding;
|
||||
|
||||
// --- 占位符逻辑 ---
|
||||
// 实际应调用 API 获取向量
|
||||
// 这里生成一个随机向量作为占位符,维度需与模型一致
|
||||
const placeholderVector = Array.from({ length: 1536 }, () => Math.random() * 2 - 1) // 假设 ada-002 是 1536 维
|
||||
const vector = placeholderVector
|
||||
// --- 占位符结束 ---
|
||||
|
||||
if (vector) {
|
||||
vectorCache.set(cacheKey, vector)
|
||||
console.log(`[VectorService] Vector obtained and cached for text (length: ${text.length}).`)
|
||||
return vector
|
||||
} else {
|
||||
console.error('[VectorService] Failed to get vector embedding.')
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[VectorService] Error getting vector embedding:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保一个记忆项具有向量表示。
|
||||
* 如果没有,则尝试生成并更新。
|
||||
* @param memory - 记忆项 (Memory 或 ShortMemory)
|
||||
* @returns 更新后的记忆项 (如果成功生成向量) 或原记忆项
|
||||
*/
|
||||
async ensureVectorRepresentation(memory: Memory | ShortMemory): Promise<Memory | ShortMemory> {
|
||||
if (memory.vectorRepresentation && memory.vectorRepresentation.length > 0) {
|
||||
return memory // 已经有向量了
|
||||
}
|
||||
|
||||
// 从状态或设置中获取 vectorizeModel
|
||||
const vectorizeModel = 'text-embedding-ada-002' // 暂时硬编码
|
||||
const vector = await this.getVector(memory.content, vectorizeModel)
|
||||
|
||||
if (vector) {
|
||||
return { ...memory, vectorRepresentation: vector }
|
||||
}
|
||||
|
||||
return memory // 无法生成向量,返回原样
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个记忆项之间的语义相似度。
|
||||
* @param memoryA - 第一个记忆项
|
||||
* @param memoryB - 第二个记忆项
|
||||
* @returns 相似度分数 (0 到 1) 或 0 (如果无法计算)
|
||||
*/
|
||||
async calculateSimilarity(memoryA: Memory | ShortMemory, memoryB: Memory | ShortMemory): Promise<number> {
|
||||
try {
|
||||
const memoryAWithVector = await this.ensureVectorRepresentation(memoryA)
|
||||
const memoryBWithVector = await this.ensureVectorRepresentation(memoryB)
|
||||
|
||||
if (
|
||||
memoryAWithVector.vectorRepresentation &&
|
||||
memoryBWithVector.vectorRepresentation &&
|
||||
memoryAWithVector.vectorRepresentation.length > 0 &&
|
||||
memoryBWithVector.vectorRepresentation.length > 0
|
||||
) {
|
||||
const similarity = cosineSimilarity(
|
||||
memoryAWithVector.vectorRepresentation,
|
||||
memoryBWithVector.vectorRepresentation
|
||||
)
|
||||
// 将余弦相似度 (-1 到 1) 映射到 0 到 1 范围 (可选,但通常更直观)
|
||||
return (similarity + 1) / 2
|
||||
} else {
|
||||
// console.warn('[VectorService] Could not calculate similarity due to missing vectors.')
|
||||
return 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[VectorService] Error calculating similarity:', error)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找与给定记忆最相似的记忆项列表。
|
||||
* @param targetMemory - 目标记忆项
|
||||
* @param candidates - 候选记忆项列表
|
||||
* @param topN - 返回最相似的 N 个结果
|
||||
* @param threshold - 相似度阈值 (0 到 1)
|
||||
* @returns 最相似的记忆项列表及其相似度分数
|
||||
*/
|
||||
async findSimilarMemories(
|
||||
targetMemory: Memory | ShortMemory,
|
||||
candidates: (Memory | ShortMemory)[],
|
||||
topN: number = 5,
|
||||
threshold: number = 0.7 // 默认阈值
|
||||
): Promise<{ memory: Memory | ShortMemory; similarity: number }[]> {
|
||||
const targetMemoryWithVector = await this.ensureVectorRepresentation(targetMemory)
|
||||
|
||||
if (!targetMemoryWithVector.vectorRepresentation || targetMemoryWithVector.vectorRepresentation.length === 0) {
|
||||
console.warn('[VectorService] Target memory has no vector representation. Cannot find similar memories.')
|
||||
return []
|
||||
}
|
||||
|
||||
const results: { memory: Memory | ShortMemory; similarity: number }[] = []
|
||||
|
||||
for (const candidate of candidates) {
|
||||
// 排除目标记忆自身
|
||||
if (candidate.id === targetMemory.id) {
|
||||
continue
|
||||
}
|
||||
|
||||
const similarity = await this.calculateSimilarity(targetMemoryWithVector, candidate)
|
||||
if (similarity >= threshold) {
|
||||
results.push({ memory: candidate, similarity })
|
||||
}
|
||||
}
|
||||
|
||||
// 按相似度降序排序
|
||||
results.sort((a, b) => b.similarity - a.similarity)
|
||||
|
||||
// 返回前 N 个结果
|
||||
return results.slice(0, topN)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算查询文本与一组记忆项的相似度。
|
||||
* @param queryText - 查询文本
|
||||
* @param candidates - 候选记忆项列表
|
||||
* @param topN - 返回最相似的 N 个结果
|
||||
* @param threshold - 相似度阈值 (0 到 1)
|
||||
* @returns 最相似的记忆项列表及其相似度分数
|
||||
*/
|
||||
async findSimilarMemoriesToQuery(
|
||||
queryText: string,
|
||||
candidates: (Memory | ShortMemory)[],
|
||||
topN: number = 10,
|
||||
threshold: number = 0.7
|
||||
): Promise<{ memory: Memory | ShortMemory; similarity: number }[]> {
|
||||
const queryVector = await this.getVector(queryText)
|
||||
if (!queryVector) {
|
||||
console.warn('[VectorService] Could not get vector for query text. Cannot find similar memories.')
|
||||
return []
|
||||
}
|
||||
|
||||
const results: { memory: Memory | ShortMemory; similarity: number }[] = []
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const candidateWithVector = await this.ensureVectorRepresentation(candidate)
|
||||
if (candidateWithVector.vectorRepresentation && candidateWithVector.vectorRepresentation.length > 0) {
|
||||
const similarity = cosineSimilarity(queryVector, candidateWithVector.vectorRepresentation)
|
||||
const normalizedSimilarity = (similarity + 1) / 2 // 归一化到 0-1
|
||||
if (normalizedSimilarity >= threshold) {
|
||||
results.push({ memory: candidate, similarity: normalizedSimilarity })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.sort((a, b) => b.similarity - a.similarity)
|
||||
return results.slice(0, topN)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空向量缓存
|
||||
*/
|
||||
clearCache(): void {
|
||||
vectorCache.clear()
|
||||
console.log('[VectorService] Vector cache cleared.')
|
||||
}
|
||||
}
|
||||
|
||||
// 导出 VectorService 的单例
|
||||
export const vectorService = new VectorService()
|
||||
@@ -10,7 +10,7 @@ import copilot from './copilot'
|
||||
import knowledge from './knowledge'
|
||||
import llm from './llm'
|
||||
import mcp from './mcp'
|
||||
import memory from './memory'
|
||||
import memory, { memoryPersistenceMiddleware } from './memory'
|
||||
import messagesReducer from './messages'
|
||||
import migrate from './migrate'
|
||||
import minapps from './minapps'
|
||||
@@ -45,7 +45,7 @@ const persistedReducer = persistReducer(
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 95,
|
||||
blacklist: ['runtime', 'messages'],
|
||||
blacklist: ['runtime', 'messages', 'memory'],
|
||||
migrate
|
||||
},
|
||||
rootReducer
|
||||
@@ -59,7 +59,7 @@ const store = configureStore({
|
||||
serializableCheck: {
|
||||
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
|
||||
}
|
||||
})
|
||||
}).concat(memoryPersistenceMiddleware)
|
||||
},
|
||||
devTools: true
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { nanoid } from 'nanoid'
|
||||
import log from 'electron-log'
|
||||
|
||||
// 记忆列表接口
|
||||
export interface MemoryList {
|
||||
@@ -22,6 +23,14 @@ export interface Memory {
|
||||
analyzedMessageIds?: string[] // 记录该记忆是从哪些消息中分析出来的
|
||||
lastMessageId?: string // 分析时的最后一条消息的ID,用于跟踪分析进度
|
||||
topicId?: string // 关联的对话话题ID,用于跟踪该记忆来自哪个话题
|
||||
vector?: number[] // 记忆的向量表示,用于语义搜索
|
||||
entities?: string[] // 记忆中提取的实体
|
||||
keywords?: string[] // 记忆中提取的关键词
|
||||
importance?: number // 记忆的重要性评分(0-1)
|
||||
accessCount?: number // 记忆被访问的次数
|
||||
lastAccessedAt?: string // 记忆最后被访问的时间
|
||||
decayFactor?: number // 记忆衰减因子(0-1),值越小衰减越大
|
||||
freshness?: number // 记忆鲜度评分(0-1),基于创建时间和最后访问时间
|
||||
}
|
||||
|
||||
// 短记忆项接口
|
||||
@@ -32,6 +41,39 @@ export interface ShortMemory {
|
||||
topicId: string // 关联的对话话题ID
|
||||
analyzedMessageIds?: string[] // 记录该记忆是从哪些消息中分析出来的
|
||||
lastMessageId?: string // 分析时的最后一条消息的ID,用于跟踪分析进度
|
||||
vector?: number[] // 记忆的向量表示,用于语义搜索
|
||||
entities?: string[] // 记忆中提取的实体
|
||||
keywords?: string[] // 记忆中提取的关键词
|
||||
importance?: number // 记忆的重要性评分(0-1)
|
||||
accessCount?: number // 记忆被访问的次数
|
||||
lastAccessedAt?: string // 记忆最后被访问的时间
|
||||
decayFactor?: number // 记忆衰减因子(0-1),值越小衰减越快
|
||||
freshness?: number // 记忆鲜度评分(0-1),基于创建时间和最后访问时间
|
||||
}
|
||||
|
||||
// 分析统计数据接口
|
||||
export interface AnalysisStats {
|
||||
totalAnalyses: number // 总分析次数
|
||||
successfulAnalyses: number // 成功分析次数(生成了新记忆)
|
||||
newMemoriesGenerated: number // 生成的新记忆数量
|
||||
averageAnalysisTime: number // 平均分析时间(毫秒)
|
||||
lastAnalysisTime: number // 上次分析时间戳
|
||||
}
|
||||
|
||||
// 性能指标接口
|
||||
export interface PerformanceMetrics {
|
||||
analysisLatency: number[] // 最近的分析延迟时间(毫秒)
|
||||
memoryRetrievalLatency: number[] // 最近的记忆检索延迟时间(毫秒)
|
||||
memoryCount: number // 当前记忆数量
|
||||
shortMemoryCount: number // 当前短期记忆数量
|
||||
lastPerformanceCheck: number // 上次性能检查时间
|
||||
}
|
||||
|
||||
// 用户关注点接口
|
||||
export interface UserInterest {
|
||||
topic: string // 关注主题
|
||||
weight: number // 权重(0-1)
|
||||
lastUpdated: string // 上次更新时间
|
||||
}
|
||||
|
||||
export interface MemoryState {
|
||||
@@ -44,8 +86,30 @@ export interface MemoryState {
|
||||
autoAnalyze: boolean // 是否自动分析
|
||||
analyzeModel: string | null // 用于长期记忆分析的模型ID
|
||||
shortMemoryAnalyzeModel: string | null // 用于短期记忆分析的模型ID
|
||||
vectorizeModel: string | null // 用于向量化的模型ID
|
||||
lastAnalyzeTime: number | null // 上次分析时间
|
||||
isAnalyzing: boolean // 是否正在分析
|
||||
|
||||
// 自适应分析相关
|
||||
adaptiveAnalysisEnabled: boolean // 是否启用自适应分析
|
||||
analysisFrequency: number // 分析频率(消息数)
|
||||
analysisDepth: 'low' | 'medium' | 'high' // 分析深度
|
||||
analysisStats: AnalysisStats // 分析统计数据
|
||||
|
||||
// 用户关注点相关
|
||||
interestTrackingEnabled: boolean // 是否启用兴趣跟踪
|
||||
userInterests: UserInterest[] // 用户关注点
|
||||
|
||||
// 性能监控相关
|
||||
monitoringEnabled: boolean // 是否启用性能监控
|
||||
performanceMetrics: PerformanceMetrics // 性能指标
|
||||
|
||||
// 智能优先级与时效性管理相关
|
||||
priorityManagementEnabled: boolean // 是否启用智能优先级管理
|
||||
decayEnabled: boolean // 是否启用记忆衰减功能
|
||||
freshnessEnabled: boolean // 是否启用记忆鲜度评估
|
||||
decayRate: number // 记忆衰减速率(0-1)
|
||||
lastPriorityUpdate: number // 上次优先级更新时间
|
||||
}
|
||||
|
||||
// 创建默认记忆列表
|
||||
@@ -68,8 +132,42 @@ const initialState: MemoryState = {
|
||||
autoAnalyze: true,
|
||||
analyzeModel: 'gpt-3.5-turbo', // 设置默认长期记忆分析模型
|
||||
shortMemoryAnalyzeModel: 'gpt-3.5-turbo', // 设置默认短期记忆分析模型
|
||||
vectorizeModel: 'gpt-3.5-turbo', // 设置默认向量化模型
|
||||
lastAnalyzeTime: null,
|
||||
isAnalyzing: false
|
||||
isAnalyzing: false,
|
||||
|
||||
// 自适应分析相关
|
||||
adaptiveAnalysisEnabled: true, // 默认启用自适应分析
|
||||
analysisFrequency: 5, // 默认每5条消息分析一次
|
||||
analysisDepth: 'medium', // 默认分析深度
|
||||
analysisStats: {
|
||||
totalAnalyses: 0,
|
||||
successfulAnalyses: 0,
|
||||
newMemoriesGenerated: 0,
|
||||
averageAnalysisTime: 0,
|
||||
lastAnalysisTime: 0
|
||||
},
|
||||
|
||||
// 用户关注点相关
|
||||
interestTrackingEnabled: true, // 默认启用兴趣跟踪
|
||||
userInterests: [],
|
||||
|
||||
// 性能监控相关
|
||||
monitoringEnabled: true, // 默认启用性能监控
|
||||
performanceMetrics: {
|
||||
analysisLatency: [],
|
||||
memoryRetrievalLatency: [],
|
||||
memoryCount: 0,
|
||||
shortMemoryCount: 0,
|
||||
lastPerformanceCheck: Date.now()
|
||||
},
|
||||
|
||||
// 智能优先级与时效性管理相关
|
||||
priorityManagementEnabled: true, // 默认启用智能优先级管理
|
||||
decayEnabled: true, // 默认启用记忆衰减功能
|
||||
freshnessEnabled: true, // 默认启用记忆鲜度评估
|
||||
decayRate: 0.05, // 默认衰减速率,每天减少5%
|
||||
lastPriorityUpdate: Date.now() // 初始化为当前时间
|
||||
}
|
||||
|
||||
const memorySlice = createSlice({
|
||||
@@ -168,6 +266,10 @@ const memorySlice = createSlice({
|
||||
setShortMemoryAnalyzeModel: (state, action: PayloadAction<string | null>) => {
|
||||
state.shortMemoryAnalyzeModel = action.payload
|
||||
},
|
||||
// 设置向量化模型
|
||||
setVectorizeModel: (state, action: PayloadAction<string | null>) => {
|
||||
state.vectorizeModel = action.payload
|
||||
},
|
||||
|
||||
// 设置分析状态
|
||||
setAnalyzing: (state, action: PayloadAction<boolean>) => {
|
||||
@@ -341,7 +443,6 @@ const memorySlice = createSlice({
|
||||
state.shortMemories = []
|
||||
return
|
||||
}
|
||||
|
||||
state.shortMemories = state.shortMemories.filter((memory) => memory.id !== action.payload)
|
||||
},
|
||||
|
||||
@@ -367,7 +468,236 @@ const memorySlice = createSlice({
|
||||
// 设置短记忆功能是否激活
|
||||
setShortMemoryActive: (state, action: PayloadAction<boolean>) => {
|
||||
state.shortMemoryActive = action.payload
|
||||
},
|
||||
|
||||
// 自适应分析相关的reducer
|
||||
setAdaptiveAnalysisEnabled: (state, action: PayloadAction<boolean>) => {
|
||||
state.adaptiveAnalysisEnabled = action.payload
|
||||
},
|
||||
|
||||
setAnalysisFrequency: (state, action: PayloadAction<number>) => {
|
||||
state.analysisFrequency = action.payload
|
||||
},
|
||||
|
||||
setAnalysisDepth: (state, action: PayloadAction<'low' | 'medium' | 'high'>) => {
|
||||
state.analysisDepth = action.payload
|
||||
},
|
||||
|
||||
updateAnalysisStats: (state, action: PayloadAction<Partial<AnalysisStats>>) => {
|
||||
state.analysisStats = { ...state.analysisStats, ...action.payload }
|
||||
},
|
||||
|
||||
// 用户关注点相关的reducer
|
||||
setInterestTrackingEnabled: (state, action: PayloadAction<boolean>) => {
|
||||
state.interestTrackingEnabled = action.payload
|
||||
},
|
||||
|
||||
updateUserInterest: (state, action: PayloadAction<UserInterest>) => {
|
||||
const index = state.userInterests.findIndex((i) => i.topic === action.payload.topic)
|
||||
if (index >= 0) {
|
||||
state.userInterests[index] = action.payload
|
||||
} else {
|
||||
state.userInterests.push(action.payload)
|
||||
}
|
||||
},
|
||||
|
||||
// 性能监控相关的reducer
|
||||
setMonitoringEnabled: (state, action: PayloadAction<boolean>) => {
|
||||
state.monitoringEnabled = action.payload
|
||||
},
|
||||
|
||||
updatePerformanceMetrics: (state, action: PayloadAction<Partial<PerformanceMetrics>>) => {
|
||||
state.performanceMetrics = { ...state.performanceMetrics, ...action.payload }
|
||||
},
|
||||
|
||||
addAnalysisLatency: (state, action: PayloadAction<number>) => {
|
||||
// 确保 performanceMetrics 存在
|
||||
if (!state.performanceMetrics) {
|
||||
state.performanceMetrics = {
|
||||
analysisLatency: [],
|
||||
memoryRetrievalLatency: [],
|
||||
memoryCount: 0,
|
||||
shortMemoryCount: 0,
|
||||
lastPerformanceCheck: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 analysisLatency 存在
|
||||
if (!state.performanceMetrics.analysisLatency) {
|
||||
state.performanceMetrics.analysisLatency = []
|
||||
}
|
||||
|
||||
const latencies = [...state.performanceMetrics.analysisLatency, action.payload].slice(-10) // 保留最近10次
|
||||
state.performanceMetrics.analysisLatency = latencies
|
||||
},
|
||||
|
||||
addMemoryRetrievalLatency: (state, action: PayloadAction<number>) => {
|
||||
// 确保 performanceMetrics 存在
|
||||
if (!state.performanceMetrics) {
|
||||
state.performanceMetrics = {
|
||||
analysisLatency: [],
|
||||
memoryRetrievalLatency: [],
|
||||
memoryCount: 0,
|
||||
shortMemoryCount: 0,
|
||||
lastPerformanceCheck: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 memoryRetrievalLatency 存在
|
||||
if (!state.performanceMetrics.memoryRetrievalLatency) {
|
||||
state.performanceMetrics.memoryRetrievalLatency = []
|
||||
}
|
||||
|
||||
const latencies = [...state.performanceMetrics.memoryRetrievalLatency, action.payload].slice(-10) // 保留最近10次
|
||||
state.performanceMetrics.memoryRetrievalLatency = latencies
|
||||
},
|
||||
|
||||
// 智能优先级与时效性管理相关的reducer
|
||||
setPriorityManagementEnabled: (state, action: PayloadAction<boolean>) => {
|
||||
state.priorityManagementEnabled = action.payload
|
||||
},
|
||||
|
||||
setDecayEnabled: (state, action: PayloadAction<boolean>) => {
|
||||
state.decayEnabled = action.payload
|
||||
},
|
||||
|
||||
setFreshnessEnabled: (state, action: PayloadAction<boolean>) => {
|
||||
state.freshnessEnabled = action.payload
|
||||
},
|
||||
|
||||
setDecayRate: (state, action: PayloadAction<number>) => {
|
||||
state.decayRate = action.payload
|
||||
},
|
||||
|
||||
// 更新记忆优先级
|
||||
updateMemoryPriorities: (state) => {
|
||||
const now = Date.now()
|
||||
|
||||
// 更新长期记忆优先级
|
||||
if (state.memories && state.memories.length > 0) {
|
||||
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))
|
||||
memory.decayFactor = decayFactor
|
||||
} else {
|
||||
memory.decayFactor = 1 // 无衰减
|
||||
}
|
||||
|
||||
// 计算鲜度评分
|
||||
if (state.freshnessEnabled) {
|
||||
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)) // 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 => {
|
||||
// 计算时间衰减因子
|
||||
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)) // 短期记忆衰减更快
|
||||
memory.decayFactor = decayFactor
|
||||
} else {
|
||||
memory.decayFactor = 1 // 无衰减
|
||||
}
|
||||
|
||||
// 计算鲜度评分
|
||||
if (state.freshnessEnabled) {
|
||||
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)) // 24小时内创建的记忆较新
|
||||
const accessFreshness = Math.max(0, 1 - (lastAccessHours / 6)) // 6小时内访问的记忆较新
|
||||
memory.freshness = (creationFreshness * 0.2) + (accessFreshness * 0.8) // 加权平均,更注重访问时间
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
state.lastPriorityUpdate = now
|
||||
},
|
||||
|
||||
// 更新记忆鲜度
|
||||
updateMemoryFreshness: (state) => {
|
||||
if (!state.freshnessEnabled) return
|
||||
|
||||
const now = Date.now()
|
||||
|
||||
// 更新长期记忆鲜度
|
||||
if (state.memories && state.memories.length > 0) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
// 更新短期记忆鲜度
|
||||
if (state.shortMemories && state.shortMemories.length > 0) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 记录记忆访问
|
||||
accessMemory: (state, action: PayloadAction<{ id: string; isShortMemory?: boolean }>) => {
|
||||
const { id, isShortMemory } = action.payload
|
||||
const now = new Date().toISOString()
|
||||
|
||||
if (isShortMemory) {
|
||||
// 更新短期记忆访问信息
|
||||
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)
|
||||
if (memory) {
|
||||
memory.accessCount = (memory.accessCount || 0) + 1
|
||||
memory.lastAccessedAt = now
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(loadMemoryData.fulfilled, (state, action) => {
|
||||
if (action.payload) {
|
||||
// 更新状态中的记忆数据
|
||||
state.memoryLists = action.payload.memoryLists || state.memoryLists
|
||||
state.memories = action.payload.memories || state.memories
|
||||
state.shortMemories = action.payload.shortMemories || state.shortMemories
|
||||
log.info('Memory data loaded into state')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -379,6 +709,7 @@ export const {
|
||||
setAutoAnalyze,
|
||||
setAnalyzeModel,
|
||||
setShortMemoryAnalyzeModel,
|
||||
setVectorizeModel,
|
||||
setAnalyzing,
|
||||
importMemories,
|
||||
clearMemories,
|
||||
@@ -391,7 +722,83 @@ export const {
|
||||
addShortMemory,
|
||||
deleteShortMemory,
|
||||
clearShortMemories,
|
||||
setShortMemoryActive
|
||||
setShortMemoryActive,
|
||||
|
||||
// 自适应分析相关的action
|
||||
setAdaptiveAnalysisEnabled,
|
||||
setAnalysisFrequency,
|
||||
setAnalysisDepth,
|
||||
updateAnalysisStats,
|
||||
|
||||
// 用户关注点相关的action
|
||||
setInterestTrackingEnabled,
|
||||
updateUserInterest,
|
||||
|
||||
// 性能监控相关的action
|
||||
setMonitoringEnabled,
|
||||
updatePerformanceMetrics,
|
||||
addAnalysisLatency,
|
||||
addMemoryRetrievalLatency,
|
||||
|
||||
// 智能优先级与时效性管理相关的action
|
||||
setPriorityManagementEnabled,
|
||||
setDecayEnabled,
|
||||
setFreshnessEnabled,
|
||||
setDecayRate,
|
||||
updateMemoryPriorities,
|
||||
updateMemoryFreshness,
|
||||
accessMemory
|
||||
} = memorySlice.actions
|
||||
|
||||
// 加载记忆数据的异步 thunk
|
||||
export const loadMemoryData = createAsyncThunk(
|
||||
'memory/loadData',
|
||||
async () => {
|
||||
try {
|
||||
log.info('Loading memory data from file...')
|
||||
const data = await window.api.memory.loadData()
|
||||
log.info('Memory data loaded successfully')
|
||||
return data
|
||||
} catch (error) {
|
||||
log.error('Failed to load memory data:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 保存记忆数据的异步 thunk
|
||||
export const saveMemoryData = createAsyncThunk(
|
||||
'memory/saveData',
|
||||
async (data: Partial<MemoryState>) => {
|
||||
try {
|
||||
log.info('Saving memory data to file...')
|
||||
const result = await window.api.memory.saveData(data)
|
||||
log.info('Memory data saved successfully')
|
||||
return result
|
||||
} catch (error) {
|
||||
log.error('Failed to save memory data:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 创建一个中间件来自动保存记忆数据的变化
|
||||
export const memoryPersistenceMiddleware = (store) => (next) => (action) => {
|
||||
const result = next(action)
|
||||
|
||||
// 如果是记忆相关的操作,保存数据到文件
|
||||
if (action.type.startsWith('memory/') &&
|
||||
!action.type.includes('loadData') &&
|
||||
!action.type.includes('saveData')) {
|
||||
const state = store.getState().memory
|
||||
store.dispatch(saveMemoryData({
|
||||
memoryLists: state.memoryLists,
|
||||
memories: state.memories,
|
||||
shortMemories: state.shortMemories
|
||||
}))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export default memorySlice.reducer
|
||||
|
||||
Reference in New Issue
Block a user