diff --git a/package.json b/package.json index c5e391c461..eb3eee3f28 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 3576b119fd..ef2d578e7a 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -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' } diff --git a/src/main/index.ts b/src/main/index.ts index 5102225781..511b5a5220 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -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' diff --git a/src/main/ipc.ts b/src/main/ipc.ts index b4b84c63aa..b59d1bfafa 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -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' diff --git a/src/main/services/BackupManager.ts b/src/main/services/BackupManager.ts index 136ffbcee5..01bc299c45 100644 --- a/src/main/services/BackupManager.ts +++ b/src/main/services/BackupManager.ts @@ -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) diff --git a/src/main/services/MCPStreamableHttpClient.ts b/src/main/services/MCPStreamableHttpClient.ts index 1e080d2a71..bdb8ddadee 100644 --- a/src/main/services/MCPStreamableHttpClient.ts +++ b/src/main/services/MCPStreamableHttpClient.ts @@ -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', diff --git a/src/main/services/MemoryFileService.ts b/src/main/services/MemoryFileService.ts new file mode 100644 index 0000000000..4aea44f8ea --- /dev/null +++ b/src/main/services/MemoryFileService.ts @@ -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() diff --git a/src/main/services/ProxyManager.ts b/src/main/services/ProxyManager.ts index 24c741b5c3..64df333337 100644 --- a/src/main/services/ProxyManager.ts +++ b/src/main/services/ProxyManager.ts @@ -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), diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 8ba792a351..12c78b4f8f 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -190,6 +190,10 @@ declare global { closeSearchWindow: (uid: string) => Promise openUrlInSearchWindow: (uid: string, url: string) => Promise } + memory: { + loadData: () => Promise + saveData: (data: any) => Promise + } } } } diff --git a/src/preload/index.ts b/src/preload/index.ts index 0928a56d3c..dc69e6af59 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -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) } } diff --git a/src/renderer/src/components/MemoryProvider.tsx b/src/renderer/src/components/MemoryProvider.tsx index 2a7f6ad91d..6df2b28b38 100644 --- a/src/renderer/src/components/MemoryProvider.tsx +++ b/src/renderer/src/components/MemoryProvider.tsx @@ -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 = ({ 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]) diff --git a/src/renderer/src/components/Popups/ShortMemoryPopup.tsx b/src/renderer/src/components/Popups/ShortMemoryPopup.tsx index 9764517c06..daf35710f8 100644 --- a/src/renderer/src/components/Popups/ShortMemoryPopup.tsx +++ b/src/renderer/src/components/Popups/ShortMemoryPopup.tsx @@ -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 = ({ topicId, resolve }) => { } } - // 删除短记忆 + // 删除短记忆 - 直接删除无需确认 const handleDeleteMemory = (id: string) => { - confirm({ - title: t('settings.memory.confirmDelete'), - icon: , - content: t('settings.memory.confirmDeleteContent'), - onOk() { - dispatch(deleteShortMemory(id)) - } - }) + // 直接删除记忆,无需确认对话框 + dispatch(deleteShortMemory(id)) } const onClose = () => { @@ -137,6 +132,46 @@ const PopupContainer: React.FC = ({ topicId, resolve }) => { + {/* 性能监控统计信息 */} + + }> + + + + + + + + + + + + + + {shortMemories.length > 0 ? ( = ({ message }) => { a: (props: any) => , code: CodeBlock, img: ImagePreview, - pre: (props: any) =>
+      pre: (props: any) => 
,
+      // 自定义处理think标签
+      think: (props: any) => {
+        // 将think标签内容渲染为带样式的div
+        return (
+          
+
+ 思考过程: +
+ {props.children} +
+ ) + } } as Partial return baseComponents }, []) diff --git a/src/renderer/src/pages/settings/MemorySettings/CollapsibleShortMemoryManager.tsx b/src/renderer/src/pages/settings/MemorySettings/CollapsibleShortMemoryManager.tsx index 2327c32d53..9cfa368b02 100644 --- a/src/renderer/src/pages/settings/MemorySettings/CollapsibleShortMemoryManager.tsx +++ b/src/renderer/src/pages/settings/MemorySettings/CollapsibleShortMemoryManager.tsx @@ -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: , - content: t('settings.memory.confirmDeleteContent'), - onOk() { - dispatch(deleteShortMemory(id)) - } - }) + // 直接删除记忆,无需确认对话框 + dispatch(deleteShortMemory(id)) } return ( diff --git a/src/renderer/src/pages/settings/MemorySettings/PriorityManagementSettings.tsx b/src/renderer/src/pages/settings/MemorySettings/PriorityManagementSettings.tsx new file mode 100644 index 0000000000..1b09e355b5 --- /dev/null +++ b/src/renderer/src/pages/settings/MemorySettings/PriorityManagementSettings.tsx @@ -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 ( + + {t('settings.memory.priorityManagement.title') || '智能优先级与时效性管理'} + + {t('settings.memory.priorityManagement.description') || + '智能管理记忆的优先级、衰减和鲜度,确保最重要和最相关的记忆优先显示。'} + + + + + {t('settings.memory.priorityManagement.enable') || '启用智能优先级管理'} + + + + + + + + + + + + {t('settings.memory.priorityManagement.decay') || '记忆衰减'} + + + + + + + + + + {t('settings.memory.priorityManagement.decayRate') || '衰减速率'} + + + + +
+ + + + +
+
+ + + + {t('settings.memory.priorityManagement.freshness') || '记忆鲜度'} + + + + + + + + + + {t('settings.memory.priorityManagement.updateNow') || '立即更新优先级'} + + + + + + +
+ ) +} + +export default PriorityManagementSettings diff --git a/src/renderer/src/pages/settings/MemorySettings/ShortMemoryManager.tsx b/src/renderer/src/pages/settings/MemorySettings/ShortMemoryManager.tsx index cf57ba3f1a..211c40f404 100644 --- a/src/renderer/src/pages/settings/MemorySettings/ShortMemoryManager.tsx +++ b/src/renderer/src/pages/settings/MemorySettings/ShortMemoryManager.tsx @@ -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: , - content: t('settings.memory.confirmDeleteContent'), - onOk() { - dispatch(deleteShortMemory(id)) - } - }) + // 直接删除记忆,无需确认对话框 + dispatch(deleteShortMemory(id)) } return ( diff --git a/src/renderer/src/pages/settings/MemorySettings/index.tsx b/src/renderer/src/pages/settings/MemorySettings/index.tsx index 61b0177d2b..2458bec4fa 100644 --- a/src/renderer/src/pages/settings/MemorySettings/index.tsx +++ b/src/renderer/src/pages/settings/MemorySettings/index.tsx @@ -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 + ) + + // 转换为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} /> )} @@ -505,6 +560,20 @@ const MemorySettings: FC = () => { ) }, + { + key: 'priorityManagement', + label: ( + + + {t('settings.memory.priorityManagement.title') || '智能优先级管理'} + + ), + children: ( + + + + ) + }, { 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} /> )} @@ -707,11 +778,7 @@ const MemorySettings: FC = () => { - {memory.category && ( - - {memory.category} - - )} + {memory.category && {memory.category}} {memory.content} } @@ -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` diff --git a/src/renderer/src/services/MemoryService.ts b/src/renderer/src/services/MemoryService.ts index 47967abe76..9471406c73 100644 --- a/src/renderer/src/services/MemoryService.ts +++ b/src/renderer/src/services/MemoryService.ts @@ -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() + + // 简单的关键词提取,匹配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 = {} + + // 提取排序后的记忆 + 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) { // 按列表分组构建记忆提示词 diff --git a/src/renderer/src/services/VectorService.ts b/src/renderer/src/services/VectorService.ts new file mode 100644 index 0000000000..c874f470b4 --- /dev/null +++ b/src/renderer/src/services/VectorService.ts @@ -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() + +/** + * VectorService 类负责处理记忆内容的向量化和相似度计算 + */ +class VectorService { + /** + * 获取给定文本的向量表示。 + * 优先从缓存获取,否则调用API生成。 + * @param text - 需要向量化的文本 + * @param modelId - 使用的向量化模型ID (TODO: 需要从设置或状态中获取) + * @returns 文本的向量表示 (number[]) 或 null (如果失败) + */ + async getVector(text: string, modelId: string = 'text-embedding-ada-002'): Promise { + 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 { + 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 { + 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() diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index bfc0632d5a..1cefe7a78a 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -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 }) diff --git a/src/renderer/src/store/memory.ts b/src/renderer/src/store/memory.ts index 9d32350f00..1aaf3236cd 100644 --- a/src/renderer/src/store/memory.ts +++ b/src/renderer/src/store/memory.ts @@ -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) => { state.shortMemoryAnalyzeModel = action.payload }, + // 设置向量化模型 + setVectorizeModel: (state, action: PayloadAction) => { + state.vectorizeModel = action.payload + }, // 设置分析状态 setAnalyzing: (state, action: PayloadAction) => { @@ -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) => { state.shortMemoryActive = action.payload + }, + + // 自适应分析相关的reducer + setAdaptiveAnalysisEnabled: (state, action: PayloadAction) => { + state.adaptiveAnalysisEnabled = action.payload + }, + + setAnalysisFrequency: (state, action: PayloadAction) => { + state.analysisFrequency = action.payload + }, + + setAnalysisDepth: (state, action: PayloadAction<'low' | 'medium' | 'high'>) => { + state.analysisDepth = action.payload + }, + + updateAnalysisStats: (state, action: PayloadAction>) => { + state.analysisStats = { ...state.analysisStats, ...action.payload } + }, + + // 用户关注点相关的reducer + setInterestTrackingEnabled: (state, action: PayloadAction) => { + state.interestTrackingEnabled = action.payload + }, + + updateUserInterest: (state, action: PayloadAction) => { + 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) => { + state.monitoringEnabled = action.payload + }, + + updatePerformanceMetrics: (state, action: PayloadAction>) => { + state.performanceMetrics = { ...state.performanceMetrics, ...action.payload } + }, + + addAnalysisLatency: (state, action: PayloadAction) => { + // 确保 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) => { + // 确保 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) => { + state.priorityManagementEnabled = action.payload + }, + + setDecayEnabled: (state, action: PayloadAction) => { + state.decayEnabled = action.payload + }, + + setFreshnessEnabled: (state, action: PayloadAction) => { + state.freshnessEnabled = action.payload + }, + + setDecayRate: (state, action: PayloadAction) => { + 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) => { + 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 diff --git a/yarn.lock b/yarn.lock index 03f1f5217a..c00c99146a 100644 --- a/yarn.lock +++ b/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"