记忆功能升级

This commit is contained in:
1600822305 2025-04-13 20:49:52 +08:00
parent 09e6871118
commit 550e83a673
26 changed files with 2038 additions and 93 deletions

View File

@ -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",

View File

@ -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'
}

View File

@ -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'

View File

@ -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'

View File

@ -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)

View File

@ -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',

View 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()

View File

@ -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),

View File

@ -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>
}
}
}
}

View File

@ -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)
}
}

View File

@ -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])

View File

@ -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

View File

@ -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.",

View File

@ -1404,7 +1404,11 @@
"noCurrentTopic": "まず会話トピックを選択してください",
"confirmDelete": "削除の確認",
"confirmDeleteContent": "この短期メモリーを削除しますか?",
"delete": "削除"
"delete": "削除",
"performanceStats": "パフォーマンス統計",
"totalAnalyses": "分析回数合計",
"successRate": "成功率",
"avgAnalysisTime": "平均分析時間"
}
},
"translate": {

View File

@ -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": "分析记忆库中的相似记忆,提供智能合并建议。",

View File

@ -1404,7 +1404,11 @@
"noCurrentTopic": "請先選擇一個對話話題",
"confirmDelete": "確認刪除",
"confirmDeleteContent": "確定要刪除這條短期記憶嗎?",
"delete": "刪除"
"delete": "刪除",
"performanceStats": "性能統計",
"totalAnalyses": "總分析次數",
"successRate": "成功率",
"avgAnalysisTime": "平均分析時間"
}
},
"translate": {

View File

@ -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
}, [])

View File

@ -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 (

View File

@ -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

View File

@ -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 (

View File

@ -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`

View File

@ -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) {
// 按列表分组构建记忆提示词

View 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()

View File

@ -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
})

View File

@ -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
View File

@ -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"