mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 10:40:07 +08:00
记忆功能升级
This commit is contained in:
parent
09e6871118
commit
550e83a673
@ -75,6 +75,7 @@
|
||||
"adm-zip": "^0.5.16",
|
||||
"async-mutex": "^0.5.0",
|
||||
"color": "^5.0.0",
|
||||
"d3": "^7.9.0",
|
||||
"diff": "^7.0.0",
|
||||
"docx": "^9.0.2",
|
||||
"electron-log": "^5.1.5",
|
||||
@ -121,6 +122,7 @@
|
||||
"@tavily/core": "patch:@tavily/core@npm%3A0.3.1#~/.yarn/patches/@tavily-core-npm-0.3.1-fe69bf2bea.patch",
|
||||
"@tryfabric/martian": "^1.2.4",
|
||||
"@types/adm-zip": "^0",
|
||||
"@types/d3": "^7",
|
||||
"@types/diff": "^7",
|
||||
"@types/fs-extra": "^11",
|
||||
"@types/lodash": "^4.17.5",
|
||||
|
||||
@ -153,5 +153,9 @@ export enum IpcChannel {
|
||||
// Search Window
|
||||
SearchWindow_Open = 'search-window:open',
|
||||
SearchWindow_Close = 'search-window:close',
|
||||
SearchWindow_OpenUrl = 'search-window:open-url'
|
||||
SearchWindow_OpenUrl = 'search-window:open-url',
|
||||
|
||||
// Memory File Storage
|
||||
Memory_LoadData = 'memory:load-data',
|
||||
Memory_SaveData = 'memory:save-data'
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
62
src/main/services/MemoryFileService.ts
Normal file
62
src/main/services/MemoryFileService.ts
Normal file
@ -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),
|
||||
|
||||
4
src/preload/index.d.ts
vendored
4
src/preload/index.d.ts
vendored
@ -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) {
|
||||
// 按列表分组构建记忆提示词
|
||||
|
||||
260
src/renderer/src/services/VectorService.ts
Normal file
260
src/renderer/src/services/VectorService.ts
Normal file
@ -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
|
||||
|
||||
527
yarn.lock
527
yarn.lock
@ -3304,6 +3304,38 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-array@npm:*":
|
||||
version: 3.2.1
|
||||
resolution: "@types/d3-array@npm:3.2.1"
|
||||
checksum: 10c0/38bf2c778451f4b79ec81a2288cb4312fe3d6449ecdf562970cc339b60f280f31c93a024c7ff512607795e79d3beb0cbda123bb07010167bce32927f71364bca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-axis@npm:*":
|
||||
version: 3.0.6
|
||||
resolution: "@types/d3-axis@npm:3.0.6"
|
||||
dependencies:
|
||||
"@types/d3-selection": "npm:*"
|
||||
checksum: 10c0/d756d42360261f44d8eefd0950c5bb0a4f67a46dd92069da3f723ac36a1e8cb2b9ce6347d836ef19d5b8aef725dbcf8fdbbd6cfbff676ca4b0642df2f78b599a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-brush@npm:*":
|
||||
version: 3.0.6
|
||||
resolution: "@types/d3-brush@npm:3.0.6"
|
||||
dependencies:
|
||||
"@types/d3-selection": "npm:*"
|
||||
checksum: 10c0/fd6e2ac7657a354f269f6b9c58451ffae9d01b89ccb1eb6367fd36d635d2f1990967215ab498e0c0679ff269429c57fad6a2958b68f4d45bc9f81d81672edc01
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-chord@npm:*":
|
||||
version: 3.0.6
|
||||
resolution: "@types/d3-chord@npm:3.0.6"
|
||||
checksum: 10c0/c5a25eb5389db01e63faec0c5c2ec7cc41c494e9b3201630b494c4e862a60f1aa83fabbc33a829e7e1403941e3c30d206c741559b14406ac2a4239cfdf4b4c17
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-color@npm:*":
|
||||
version: 3.1.3
|
||||
resolution: "@types/d3-color@npm:3.1.3"
|
||||
@ -3311,7 +3343,31 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-drag@npm:^3.0.7":
|
||||
"@types/d3-contour@npm:*":
|
||||
version: 3.0.6
|
||||
resolution: "@types/d3-contour@npm:3.0.6"
|
||||
dependencies:
|
||||
"@types/d3-array": "npm:*"
|
||||
"@types/geojson": "npm:*"
|
||||
checksum: 10c0/e7d83e94719af4576ceb5ac7f277c5806f83ba6c3631744ae391cffc3641f09dfa279470b83053cd0b2acd6784e8749c71141d05bdffa63ca58ffb5b31a0f27c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-delaunay@npm:*":
|
||||
version: 6.0.4
|
||||
resolution: "@types/d3-delaunay@npm:6.0.4"
|
||||
checksum: 10c0/d154a8864f08c4ea23ecb9bdabcef1c406a25baa8895f0cb08a0ed2799de0d360e597552532ce7086ff0cdffa8f3563f9109d18f0191459d32bb620a36939123
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-dispatch@npm:*":
|
||||
version: 3.0.6
|
||||
resolution: "@types/d3-dispatch@npm:3.0.6"
|
||||
checksum: 10c0/405eb7d0ec139fbf72fa6a43b0f3ca8a1f913bb2cb38f607827e63fca8d4393f021f32f3b96b33c93ddbd37789453a0b3624f14f504add5308fd9aec8a46dda0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-drag@npm:*, @types/d3-drag@npm:^3.0.7":
|
||||
version: 3.0.7
|
||||
resolution: "@types/d3-drag@npm:3.0.7"
|
||||
dependencies:
|
||||
@ -3320,6 +3376,59 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-dsv@npm:*":
|
||||
version: 3.0.7
|
||||
resolution: "@types/d3-dsv@npm:3.0.7"
|
||||
checksum: 10c0/c0f01da862465594c8a28278b51c850af3b4239cc22b14fd1a19d7a98f93d94efa477bf59d8071beb285dca45bf614630811451e18e7c52add3a0abfee0a1871
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-ease@npm:*":
|
||||
version: 3.0.2
|
||||
resolution: "@types/d3-ease@npm:3.0.2"
|
||||
checksum: 10c0/aff5a1e572a937ee9bff6465225d7ba27d5e0c976bd9eacdac2e6f10700a7cb0c9ea2597aff6b43a6ed850a3210030870238894a77ec73e309b4a9d0333f099c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-fetch@npm:*":
|
||||
version: 3.0.7
|
||||
resolution: "@types/d3-fetch@npm:3.0.7"
|
||||
dependencies:
|
||||
"@types/d3-dsv": "npm:*"
|
||||
checksum: 10c0/3d147efa52a26da1a5d40d4d73e6cebaaa964463c378068062999b93ea3731b27cc429104c21ecbba98c6090e58ef13429db6399238c5e3500162fb3015697a0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-force@npm:*":
|
||||
version: 3.0.10
|
||||
resolution: "@types/d3-force@npm:3.0.10"
|
||||
checksum: 10c0/c82b459079a106b50e346c9b79b141f599f2fc4f598985a5211e72c7a2e20d35bd5dc6e91f306b323c8bfa325c02c629b1645f5243f1c6a55bd51bc85cccfa92
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-format@npm:*":
|
||||
version: 3.0.4
|
||||
resolution: "@types/d3-format@npm:3.0.4"
|
||||
checksum: 10c0/3ac1600bf9061a59a228998f7cd3f29e85cbf522997671ba18d4d84d10a2a1aff4f95aceb143fa9960501c3ec351e113fc75884e6a504ace44dc1744083035ee
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-geo@npm:*":
|
||||
version: 3.1.0
|
||||
resolution: "@types/d3-geo@npm:3.1.0"
|
||||
dependencies:
|
||||
"@types/geojson": "npm:*"
|
||||
checksum: 10c0/3745a93439038bb5b0b38facf435f7079812921d46406f5d38deaee59e90084ff742443c7ea0a8446df81a0d81eaf622fe7068cf4117a544bd4aa3b2dc182f88
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-hierarchy@npm:*":
|
||||
version: 3.1.7
|
||||
resolution: "@types/d3-hierarchy@npm:3.1.7"
|
||||
checksum: 10c0/873711737d6b8e7b6f1dda0bcd21294a48f75024909ae510c5d2c21fad2e72032e0958def4d9f68319d3aaac298ad09c49807f8bfc87a145a82693b5208613c7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-interpolate@npm:*":
|
||||
version: 3.0.4
|
||||
resolution: "@types/d3-interpolate@npm:3.0.4"
|
||||
@ -3329,6 +3438,50 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-path@npm:*":
|
||||
version: 3.1.1
|
||||
resolution: "@types/d3-path@npm:3.1.1"
|
||||
checksum: 10c0/2c36eb31ebaf2ce4712e793fd88087117976f7c4ed69cc2431825f999c8c77cca5cea286f3326432b770739ac6ccd5d04d851eb65e7a4dbcc10c982b49ad2c02
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-polygon@npm:*":
|
||||
version: 3.0.2
|
||||
resolution: "@types/d3-polygon@npm:3.0.2"
|
||||
checksum: 10c0/f46307bb32b6c2aef8c7624500e0f9b518de8f227ccc10170b869dc43e4c542560f6c8d62e9f087fac45e198d6e4b623e579c0422e34c85baf56717456d3f439
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-quadtree@npm:*":
|
||||
version: 3.0.6
|
||||
resolution: "@types/d3-quadtree@npm:3.0.6"
|
||||
checksum: 10c0/7eaa0a4d404adc856971c9285e1c4ab17e9135ea669d847d6db7e0066126a28ac751864e7ce99c65d526e130f56754a2e437a1617877098b3bdcc3ef23a23616
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-random@npm:*":
|
||||
version: 3.0.3
|
||||
resolution: "@types/d3-random@npm:3.0.3"
|
||||
checksum: 10c0/5f4fea40080cd6d4adfee05183d00374e73a10c530276a6455348983dda341003a251def28565a27c25d9cf5296a33e870e397c9d91ff83fb7495a21c96b6882
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-scale-chromatic@npm:*":
|
||||
version: 3.1.0
|
||||
resolution: "@types/d3-scale-chromatic@npm:3.1.0"
|
||||
checksum: 10c0/93c564e02d2e97a048e18fe8054e4a935335da6ab75a56c3df197beaa87e69122eef0dfbeb7794d4a444a00e52e3123514ee27cec084bd21f6425b7037828cc2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-scale@npm:*":
|
||||
version: 4.0.9
|
||||
resolution: "@types/d3-scale@npm:4.0.9"
|
||||
dependencies:
|
||||
"@types/d3-time": "npm:*"
|
||||
checksum: 10c0/4ac44233c05cd50b65b33ecb35d99fdf07566bcdbc55bc1306b2f27d1c5134d8c560d356f2c8e76b096e9125ffb8d26d95f78d56e210d1c542cb255bdf31d6c8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.10":
|
||||
version: 3.0.11
|
||||
resolution: "@types/d3-selection@npm:3.0.11"
|
||||
@ -3336,7 +3489,37 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-transition@npm:^3.0.8":
|
||||
"@types/d3-shape@npm:*":
|
||||
version: 3.1.7
|
||||
resolution: "@types/d3-shape@npm:3.1.7"
|
||||
dependencies:
|
||||
"@types/d3-path": "npm:*"
|
||||
checksum: 10c0/38e59771c1c4c83b67aa1f941ce350410522a149d2175832fdc06396b2bb3b2c1a2dd549e0f8230f9f24296ee5641a515eaf10f55ee1ef6c4f83749e2dd7dcfd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-time-format@npm:*":
|
||||
version: 4.0.3
|
||||
resolution: "@types/d3-time-format@npm:4.0.3"
|
||||
checksum: 10c0/9ef5e8e2b96b94799b821eed5d61a3d432c7903247966d8ad951b8ce5797fe46554b425cb7888fa5bf604b4663c369d7628c0328ffe80892156671c58d1a7f90
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-time@npm:*":
|
||||
version: 3.0.4
|
||||
resolution: "@types/d3-time@npm:3.0.4"
|
||||
checksum: 10c0/6d9e2255d63f7a313a543113920c612e957d70da4fb0890931da6c2459010291b8b1f95e149a538500c1c99e7e6c89ffcce5554dd29a31ff134a38ea94b6d174
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-timer@npm:*":
|
||||
version: 3.0.2
|
||||
resolution: "@types/d3-timer@npm:3.0.2"
|
||||
checksum: 10c0/c644dd9571fcc62b1aa12c03bcad40571553020feeb5811f1d8a937ac1e65b8a04b759b4873aef610e28b8714ac71c9885a4d6c127a048d95118f7e5b506d9e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-transition@npm:*, @types/d3-transition@npm:^3.0.8":
|
||||
version: 3.0.9
|
||||
resolution: "@types/d3-transition@npm:3.0.9"
|
||||
dependencies:
|
||||
@ -3345,7 +3528,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-zoom@npm:^3.0.8":
|
||||
"@types/d3-zoom@npm:*, @types/d3-zoom@npm:^3.0.8":
|
||||
version: 3.0.8
|
||||
resolution: "@types/d3-zoom@npm:3.0.8"
|
||||
dependencies:
|
||||
@ -3355,6 +3538,44 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3@npm:^7":
|
||||
version: 7.4.3
|
||||
resolution: "@types/d3@npm:7.4.3"
|
||||
dependencies:
|
||||
"@types/d3-array": "npm:*"
|
||||
"@types/d3-axis": "npm:*"
|
||||
"@types/d3-brush": "npm:*"
|
||||
"@types/d3-chord": "npm:*"
|
||||
"@types/d3-color": "npm:*"
|
||||
"@types/d3-contour": "npm:*"
|
||||
"@types/d3-delaunay": "npm:*"
|
||||
"@types/d3-dispatch": "npm:*"
|
||||
"@types/d3-drag": "npm:*"
|
||||
"@types/d3-dsv": "npm:*"
|
||||
"@types/d3-ease": "npm:*"
|
||||
"@types/d3-fetch": "npm:*"
|
||||
"@types/d3-force": "npm:*"
|
||||
"@types/d3-format": "npm:*"
|
||||
"@types/d3-geo": "npm:*"
|
||||
"@types/d3-hierarchy": "npm:*"
|
||||
"@types/d3-interpolate": "npm:*"
|
||||
"@types/d3-path": "npm:*"
|
||||
"@types/d3-polygon": "npm:*"
|
||||
"@types/d3-quadtree": "npm:*"
|
||||
"@types/d3-random": "npm:*"
|
||||
"@types/d3-scale": "npm:*"
|
||||
"@types/d3-scale-chromatic": "npm:*"
|
||||
"@types/d3-selection": "npm:*"
|
||||
"@types/d3-shape": "npm:*"
|
||||
"@types/d3-time": "npm:*"
|
||||
"@types/d3-time-format": "npm:*"
|
||||
"@types/d3-timer": "npm:*"
|
||||
"@types/d3-transition": "npm:*"
|
||||
"@types/d3-zoom": "npm:*"
|
||||
checksum: 10c0/a9c6d65b13ef3b42c87f2a89ea63a6d5640221869f97d0657b0cb2f1dac96a0f164bf5605643c0794e0de3aa2bf05df198519aaf15d24ca135eb0e8bd8a9d879
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.6":
|
||||
version: 4.1.12
|
||||
resolution: "@types/debug@npm:4.1.12"
|
||||
@ -3406,6 +3627,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/geojson@npm:*":
|
||||
version: 7946.0.16
|
||||
resolution: "@types/geojson@npm:7946.0.16"
|
||||
checksum: 10c0/1ff24a288bd5860b766b073ead337d31d73bdc715e5b50a2cee5cb0af57a1ed02cc04ef295f5fa68dc40fe3e4f104dd31282b2b818a5ba3231bc1001ba084e3c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4":
|
||||
version: 3.0.4
|
||||
resolution: "@types/hast@npm:3.0.4"
|
||||
@ -3925,6 +4153,7 @@ __metadata:
|
||||
"@tavily/core": "patch:@tavily/core@npm%3A0.3.1#~/.yarn/patches/@tavily-core-npm-0.3.1-fe69bf2bea.patch"
|
||||
"@tryfabric/martian": "npm:^1.2.4"
|
||||
"@types/adm-zip": "npm:^0"
|
||||
"@types/d3": "npm:^7"
|
||||
"@types/diff": "npm:^7"
|
||||
"@types/fs-extra": "npm:^11"
|
||||
"@types/lodash": "npm:^4.17.5"
|
||||
@ -3947,6 +4176,7 @@ __metadata:
|
||||
babel-plugin-styled-components: "npm:^2.1.4"
|
||||
browser-image-compression: "npm:^2.0.2"
|
||||
color: "npm:^5.0.0"
|
||||
d3: "npm:^7.9.0"
|
||||
dayjs: "npm:^1.11.11"
|
||||
dexie: "npm:^4.0.8"
|
||||
dexie-react-hooks: "npm:^1.1.7"
|
||||
@ -5405,6 +5635,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:7":
|
||||
version: 7.2.0
|
||||
resolution: "commander@npm:7.2.0"
|
||||
checksum: 10c0/8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:9.2.0":
|
||||
version: 9.2.0
|
||||
resolution: "commander@npm:9.2.0"
|
||||
@ -5688,21 +5925,77 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-color@npm:1 - 3":
|
||||
"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:2.5.0 - 3, d3-array@npm:3, d3-array@npm:^3.2.0":
|
||||
version: 3.2.4
|
||||
resolution: "d3-array@npm:3.2.4"
|
||||
dependencies:
|
||||
internmap: "npm:1 - 2"
|
||||
checksum: 10c0/08b95e91130f98c1375db0e0af718f4371ccacef7d5d257727fe74f79a24383e79aba280b9ffae655483ffbbad4fd1dec4ade0119d88c4749f388641c8bf8c50
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-axis@npm:3":
|
||||
version: 3.0.0
|
||||
resolution: "d3-axis@npm:3.0.0"
|
||||
checksum: 10c0/a271e70ba1966daa5aaf6a7f959ceca3e12997b43297e757c7b945db2e1ead3c6ee226f2abcfa22abbd4e2e28bd2b71a0911794c4e5b911bbba271328a582c78
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-brush@npm:3":
|
||||
version: 3.0.0
|
||||
resolution: "d3-brush@npm:3.0.0"
|
||||
dependencies:
|
||||
d3-dispatch: "npm:1 - 3"
|
||||
d3-drag: "npm:2 - 3"
|
||||
d3-interpolate: "npm:1 - 3"
|
||||
d3-selection: "npm:3"
|
||||
d3-transition: "npm:3"
|
||||
checksum: 10c0/07baf00334c576da2f68a91fc0da5732c3a5fa19bd3d7aed7fd24d1d674a773f71a93e9687c154176f7246946194d77c48c2d8fed757f5dcb1a4740067ec50a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-chord@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-chord@npm:3.0.1"
|
||||
dependencies:
|
||||
d3-path: "npm:1 - 3"
|
||||
checksum: 10c0/baa6013914af3f4fe1521f0d16de31a38eb8a71d08ff1dec4741f6f45a828661e5cd3935e39bd14e3032bdc78206c283ca37411da21d46ec3cfc520be6e7a7ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-color@npm:1 - 3, d3-color@npm:3":
|
||||
version: 3.1.0
|
||||
resolution: "d3-color@npm:3.1.0"
|
||||
checksum: 10c0/a4e20e1115fa696fce041fbe13fbc80dc4c19150fa72027a7c128ade980bc0eeeba4bcf28c9e21f0bce0e0dbfe7ca5869ef67746541dcfda053e4802ad19783c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-dispatch@npm:1 - 3":
|
||||
"d3-contour@npm:4":
|
||||
version: 4.0.2
|
||||
resolution: "d3-contour@npm:4.0.2"
|
||||
dependencies:
|
||||
d3-array: "npm:^3.2.0"
|
||||
checksum: 10c0/98bc5fbed6009e08707434a952076f39f1cd6ed8b9288253cc3e6a3286e4e80c63c62d84954b20e64bf6e4ededcc69add54d3db25e990784a59c04edd3449032
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-delaunay@npm:6":
|
||||
version: 6.0.4
|
||||
resolution: "d3-delaunay@npm:6.0.4"
|
||||
dependencies:
|
||||
delaunator: "npm:5"
|
||||
checksum: 10c0/57c3aecd2525664b07c4c292aa11cf49b2752c0cf3f5257f752999399fe3c592de2d418644d79df1f255471eec8057a9cc0c3062ed7128cb3348c45f69597754
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-dispatch@npm:1 - 3, d3-dispatch@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-dispatch@npm:3.0.1"
|
||||
checksum: 10c0/6eca77008ce2dc33380e45d4410c67d150941df7ab45b91d116dbe6d0a3092c0f6ac184dd4602c796dc9e790222bad3ff7142025f5fd22694efe088d1d941753
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-drag@npm:2 - 3, d3-drag@npm:^3.0.0":
|
||||
"d3-drag@npm:2 - 3, d3-drag@npm:3, d3-drag@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "d3-drag@npm:3.0.0"
|
||||
dependencies:
|
||||
@ -5712,14 +6005,78 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-ease@npm:1 - 3":
|
||||
"d3-dsv@npm:1 - 3, d3-dsv@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-dsv@npm:3.0.1"
|
||||
dependencies:
|
||||
commander: "npm:7"
|
||||
iconv-lite: "npm:0.6"
|
||||
rw: "npm:1"
|
||||
bin:
|
||||
csv2json: bin/dsv2json.js
|
||||
csv2tsv: bin/dsv2dsv.js
|
||||
dsv2dsv: bin/dsv2dsv.js
|
||||
dsv2json: bin/dsv2json.js
|
||||
json2csv: bin/json2dsv.js
|
||||
json2dsv: bin/json2dsv.js
|
||||
json2tsv: bin/json2dsv.js
|
||||
tsv2csv: bin/dsv2dsv.js
|
||||
tsv2json: bin/dsv2json.js
|
||||
checksum: 10c0/10e6af9e331950ed258f34ab49ac1b7060128ef81dcf32afc790bd1f7e8c3cc2aac7f5f875250a83f21f39bb5925fbd0872bb209f8aca32b3b77d32bab8a65ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-ease@npm:1 - 3, d3-ease@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-ease@npm:3.0.1"
|
||||
checksum: 10c0/fec8ef826c0cc35cda3092c6841e07672868b1839fcaf556e19266a3a37e6bc7977d8298c0fcb9885e7799bfdcef7db1baaba9cd4dcf4bc5e952cf78574a88b0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-interpolate@npm:1 - 3":
|
||||
"d3-fetch@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-fetch@npm:3.0.1"
|
||||
dependencies:
|
||||
d3-dsv: "npm:1 - 3"
|
||||
checksum: 10c0/4f467a79bf290395ac0cbb5f7562483f6a18668adc4c8eb84c9d3eff048b6f6d3b6f55079ba1ebf1908dabe000c941d46be447f8d78453b2dad5fb59fb6aa93b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-force@npm:3":
|
||||
version: 3.0.0
|
||||
resolution: "d3-force@npm:3.0.0"
|
||||
dependencies:
|
||||
d3-dispatch: "npm:1 - 3"
|
||||
d3-quadtree: "npm:1 - 3"
|
||||
d3-timer: "npm:1 - 3"
|
||||
checksum: 10c0/220a16a1a1ac62ba56df61028896e4b52be89c81040d20229c876efc8852191482c233f8a52bb5a4e0875c321b8e5cb6413ef3dfa4d8fe79eeb7d52c587f52cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-format@npm:1 - 3, d3-format@npm:3":
|
||||
version: 3.1.0
|
||||
resolution: "d3-format@npm:3.1.0"
|
||||
checksum: 10c0/049f5c0871ebce9859fc5e2f07f336b3c5bfff52a2540e0bac7e703fce567cd9346f4ad1079dd18d6f1e0eaa0599941c1810898926f10ac21a31fd0a34b4aa75
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-geo@npm:3":
|
||||
version: 3.1.1
|
||||
resolution: "d3-geo@npm:3.1.1"
|
||||
dependencies:
|
||||
d3-array: "npm:2.5.0 - 3"
|
||||
checksum: 10c0/d32270dd2dc8ac3ea63e8805d63239c4c8ec6c0d339d73b5e5a30a87f8f54db22a78fb434369799465eae169503b25f9a107c642c8a16c32a3285bc0e6d8e8c1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-hierarchy@npm:3":
|
||||
version: 3.1.2
|
||||
resolution: "d3-hierarchy@npm:3.1.2"
|
||||
checksum: 10c0/6dcdb480539644aa7fc0d72dfc7b03f99dfbcdf02714044e8c708577e0d5981deb9d3e99bbbb2d26422b55bcc342ac89a0fa2ea6c9d7302e2fc0951dd96f89cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-interpolate@npm:1 - 3, d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-interpolate@npm:3.0.1"
|
||||
dependencies:
|
||||
@ -5728,6 +6085,57 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-path@npm:1 - 3, d3-path@npm:3, d3-path@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "d3-path@npm:3.1.0"
|
||||
checksum: 10c0/dc1d58ec87fa8319bd240cf7689995111a124b141428354e9637aa83059eb12e681f77187e0ada5dedfce346f7e3d1f903467ceb41b379bfd01cd8e31721f5da
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-polygon@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-polygon@npm:3.0.1"
|
||||
checksum: 10c0/e236aa7f33efa9a4072907af7dc119f85b150a0716759d4fe5f12f62573018264a6cbde8617fbfa6944a7ae48c1c0c8d3f39ae72e11f66dd471e9b5e668385df
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-quadtree@npm:1 - 3, d3-quadtree@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-quadtree@npm:3.0.1"
|
||||
checksum: 10c0/18302d2548bfecaef788152397edec95a76400fd97d9d7f42a089ceb68d910f685c96579d74e3712d57477ed042b056881b47cd836a521de683c66f47ce89090
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-random@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-random@npm:3.0.1"
|
||||
checksum: 10c0/987a1a1bcbf26e6cf01fd89d5a265b463b2cea93560fc17d9b1c45e8ed6ff2db5924601bcceb808de24c94133f000039eb7fa1c469a7a844ccbf1170cbb25b41
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-scale-chromatic@npm:3":
|
||||
version: 3.1.0
|
||||
resolution: "d3-scale-chromatic@npm:3.1.0"
|
||||
dependencies:
|
||||
d3-color: "npm:1 - 3"
|
||||
d3-interpolate: "npm:1 - 3"
|
||||
checksum: 10c0/9a3f4671ab0b971f4a411b42180d7cf92bfe8e8584e637ce7e698d705e18d6d38efbd20ec64f60cc0dfe966c20d40fc172565bc28aaa2990c0a006360eed91af
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-scale@npm:4":
|
||||
version: 4.0.2
|
||||
resolution: "d3-scale@npm:4.0.2"
|
||||
dependencies:
|
||||
d3-array: "npm:2.10.0 - 3"
|
||||
d3-format: "npm:1 - 3"
|
||||
d3-interpolate: "npm:1.2.0 - 3"
|
||||
d3-time: "npm:2.1.1 - 3"
|
||||
d3-time-format: "npm:2 - 4"
|
||||
checksum: 10c0/65d9ad8c2641aec30ed5673a7410feb187a224d6ca8d1a520d68a7d6eac9d04caedbff4713d1e8545be33eb7fec5739983a7ab1d22d4e5ad35368c6729d362f1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-selection@npm:2 - 3, d3-selection@npm:3, d3-selection@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "d3-selection@npm:3.0.0"
|
||||
@ -5735,14 +6143,41 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-timer@npm:1 - 3":
|
||||
"d3-shape@npm:3":
|
||||
version: 3.2.0
|
||||
resolution: "d3-shape@npm:3.2.0"
|
||||
dependencies:
|
||||
d3-path: "npm:^3.1.0"
|
||||
checksum: 10c0/f1c9d1f09926daaf6f6193ae3b4c4b5521e81da7d8902d24b38694517c7f527ce3c9a77a9d3a5722ad1e3ff355860b014557b450023d66a944eabf8cfde37132
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-time-format@npm:2 - 4, d3-time-format@npm:4":
|
||||
version: 4.1.0
|
||||
resolution: "d3-time-format@npm:4.1.0"
|
||||
dependencies:
|
||||
d3-time: "npm:1 - 3"
|
||||
checksum: 10c0/735e00fb25a7fd5d418fac350018713ae394eefddb0d745fab12bbff0517f9cdb5f807c7bbe87bb6eeb06249662f8ea84fec075f7d0cd68609735b2ceb29d206
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:3":
|
||||
version: 3.1.0
|
||||
resolution: "d3-time@npm:3.1.0"
|
||||
dependencies:
|
||||
d3-array: "npm:2 - 3"
|
||||
checksum: 10c0/a984f77e1aaeaa182679b46fbf57eceb6ebdb5f67d7578d6f68ef933f8eeb63737c0949991618a8d29472dbf43736c7d7f17c452b2770f8c1271191cba724ca1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-timer@npm:1 - 3, d3-timer@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-timer@npm:3.0.1"
|
||||
checksum: 10c0/d4c63cb4bb5461d7038aac561b097cd1c5673969b27cbdd0e87fa48d9300a538b9e6f39b4a7f0e3592ef4f963d858c8a9f0e92754db73116770856f2fc04561a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-transition@npm:2 - 3":
|
||||
"d3-transition@npm:2 - 3, d3-transition@npm:3":
|
||||
version: 3.0.1
|
||||
resolution: "d3-transition@npm:3.0.1"
|
||||
dependencies:
|
||||
@ -5757,7 +6192,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-zoom@npm:^3.0.0":
|
||||
"d3-zoom@npm:3, d3-zoom@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "d3-zoom@npm:3.0.0"
|
||||
dependencies:
|
||||
@ -5770,6 +6205,44 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3@npm:^7.9.0":
|
||||
version: 7.9.0
|
||||
resolution: "d3@npm:7.9.0"
|
||||
dependencies:
|
||||
d3-array: "npm:3"
|
||||
d3-axis: "npm:3"
|
||||
d3-brush: "npm:3"
|
||||
d3-chord: "npm:3"
|
||||
d3-color: "npm:3"
|
||||
d3-contour: "npm:4"
|
||||
d3-delaunay: "npm:6"
|
||||
d3-dispatch: "npm:3"
|
||||
d3-drag: "npm:3"
|
||||
d3-dsv: "npm:3"
|
||||
d3-ease: "npm:3"
|
||||
d3-fetch: "npm:3"
|
||||
d3-force: "npm:3"
|
||||
d3-format: "npm:3"
|
||||
d3-geo: "npm:3"
|
||||
d3-hierarchy: "npm:3"
|
||||
d3-interpolate: "npm:3"
|
||||
d3-path: "npm:3"
|
||||
d3-polygon: "npm:3"
|
||||
d3-quadtree: "npm:3"
|
||||
d3-random: "npm:3"
|
||||
d3-scale: "npm:4"
|
||||
d3-scale-chromatic: "npm:3"
|
||||
d3-selection: "npm:3"
|
||||
d3-shape: "npm:3"
|
||||
d3-time: "npm:3"
|
||||
d3-time-format: "npm:4"
|
||||
d3-timer: "npm:3"
|
||||
d3-transition: "npm:3"
|
||||
d3-zoom: "npm:3"
|
||||
checksum: 10c0/3dd9c08c73cfaa69c70c49e603c85e049c3904664d9c79a1a52a0f52795828a1ff23592dc9a7b2257e711d68a615472a13103c212032f38e016d609796e087e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dashdash@npm:^1.12.0":
|
||||
version: 1.14.1
|
||||
resolution: "dashdash@npm:1.14.1"
|
||||
@ -6052,6 +6525,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"delaunator@npm:5":
|
||||
version: 5.0.1
|
||||
resolution: "delaunator@npm:5.0.1"
|
||||
dependencies:
|
||||
robust-predicates: "npm:^3.0.2"
|
||||
checksum: 10c0/3d7ea4d964731c5849af33fec0a271bc6753487b331fd7d43ccb17d77834706e1c383e6ab8fda0032da955e7576d1083b9603cdaf9cbdfd6b3ebd1fb8bb675a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"delay@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "delay@npm:6.0.0"
|
||||
@ -8961,7 +9443,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2":
|
||||
"iconv-lite@npm:0.6, iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2":
|
||||
version: 0.6.3
|
||||
resolution: "iconv-lite@npm:0.6.3"
|
||||
dependencies:
|
||||
@ -9103,6 +9585,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"internmap@npm:1 - 2":
|
||||
version: 2.0.3
|
||||
resolution: "internmap@npm:2.0.3"
|
||||
checksum: 10c0/8cedd57f07bbc22501516fbfc70447f0c6812871d471096fad9ea603516eacc2137b633633daf432c029712df0baefd793686388ddf5737e3ea15074b877f7ed
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"invert-kv@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "invert-kv@npm:1.0.0"
|
||||
@ -14426,6 +14915,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"robust-predicates@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "robust-predicates@npm:3.0.2"
|
||||
checksum: 10c0/4ecd53649f1c2d49529c85518f2fa69ffb2f7a4453f7fd19c042421c7b4d76c3efb48bc1c740c8f7049346d7cb58cf08ee0c9adaae595cc23564d360adb1fde4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rollup-plugin-visualizer@npm:^5.12.0":
|
||||
version: 5.14.0
|
||||
resolution: "rollup-plugin-visualizer@npm:5.14.0"
|
||||
@ -14547,6 +15043,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rw@npm:1":
|
||||
version: 1.3.3
|
||||
resolution: "rw@npm:1.3.3"
|
||||
checksum: 10c0/b1e1ef37d1e79d9dc7050787866e30b6ddcb2625149276045c262c6b4d53075ddc35f387a856a8e76f0d0df59f4cd58fe24707e40797ebee66e542b840ed6a53
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
|
||||
version: 5.2.1
|
||||
resolution: "safe-buffer@npm:5.2.1"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user