From f6ff43629455cd082f1fdbd137aa62b145b671b1 Mon Sep 17 00:00:00 2001
From: fullex <0xfullex@gmail.com>
Date: Thu, 25 Sep 2025 11:38:05 +0800
Subject: [PATCH] refactor: replace keyv with cacheService
---
package.json | 1 -
.../aiCore/legacy/clients/BaseApiClient.ts | 22 +++++++++----------
.../src/aiCore/provider/providerConfig.ts | 10 ++++-----
src/renderer/src/env.d.ts | 2 --
src/renderer/src/hooks/useScrollPosition.ts | 5 +++--
src/renderer/src/init.ts | 7 ------
.../src/pages/home/Tabs/TopicsTab.tsx | 2 +-
.../MemorySettings/MemorySettings.tsx | 9 ++++----
.../BaseWebSearchProvider.ts | 9 ++++----
src/renderer/src/services/MemoryProcessor.ts | 3 ++-
.../services/ocr/clients/OcrBaseApiClient.ts | 10 ++++-----
src/renderer/src/store/thunk/messageThunk.ts | 5 +++--
src/renderer/src/windows/mini/entryPoint.tsx | 13 -----------
.../windows/selection/action/entryPoint.tsx | 12 ----------
yarn.lock | 8 -------
15 files changed, 40 insertions(+), 78 deletions(-)
diff --git a/package.json b/package.json
index 7eb9269ad8..d75bb0039e 100644
--- a/package.json
+++ b/package.json
@@ -140,7 +140,6 @@
"@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch",
"@hello-pangea/dnd": "^18.0.1",
"@heroui/react": "^2.8.3",
- "@kangfenmao/keyv-storage": "^0.1.0",
"@langchain/community": "^0.3.50",
"@mistralai/mistralai": "^1.7.5",
"@modelcontextprotocol/sdk": "^1.17.5",
diff --git a/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts b/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts
index 884499dfb1..5a2131a1f7 100644
--- a/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts
+++ b/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts
@@ -1,3 +1,4 @@
+import { cacheService } from '@data/CacheService'
import { loggerService } from '@logger'
import {
isFunctionCallingModel,
@@ -53,7 +54,6 @@ import { isEmpty } from 'lodash'
import type { CompletionsContext } from '../middleware/types'
import type { ApiClient, RequestTransformer, ResponseChunkTransformer } from './types'
-
const logger = loggerService.withContext('BaseApiClient')
/**
@@ -166,16 +166,16 @@ export abstract class BaseApiClient<
return keys[0]
}
- const lastUsedKey = window.keyv.get(keyName)
- if (!lastUsedKey) {
- window.keyv.set(keyName, keys[0])
+ const lastUsedKey = cacheService.getShared(keyName) as string | undefined
+ if (lastUsedKey === undefined) {
+ cacheService.setShared(keyName, keys[0])
return keys[0]
}
const currentIndex = keys.indexOf(lastUsedKey)
const nextIndex = (currentIndex + 1) % keys.length
const nextKey = keys[nextIndex]
- window.keyv.set(keyName, nextKey)
+ cacheService.setShared(keyName, nextKey)
return nextKey
}
@@ -331,7 +331,7 @@ export abstract class BaseApiClient<
}
private getMemoryReferencesFromCache(message: Message) {
- const memories = window.keyv.get(`memory-search-${message.id}`) as MemoryItem[] | undefined
+ const memories = cacheService.get(`memory-search-${message.id}`) as MemoryItem[] | undefined
if (memories) {
const memoryReferences: KnowledgeReference[] = memories.map((mem, index) => ({
id: index + 1,
@@ -349,10 +349,10 @@ export abstract class BaseApiClient<
if (isEmpty(content)) {
return []
}
- const webSearch: WebSearchResponse = window.keyv.get(`web-search-${message.id}`)
+ const webSearch: WebSearchResponse | undefined = cacheService.get(`web-search-${message.id}`)
if (webSearch) {
- window.keyv.remove(`web-search-${message.id}`)
+ cacheService.delete(`web-search-${message.id}`)
return (webSearch.results as WebSearchProviderResponse).results.map(
(result, index) =>
({
@@ -375,10 +375,10 @@ export abstract class BaseApiClient<
if (isEmpty(content)) {
return []
}
- const knowledgeReferences: KnowledgeReference[] = window.keyv.get(`knowledge-search-${message.id}`)
+ const knowledgeReferences: KnowledgeReference[] | undefined = cacheService.get(`knowledge-search-${message.id}`)
- if (!isEmpty(knowledgeReferences)) {
- window.keyv.remove(`knowledge-search-${message.id}`)
+ if (knowledgeReferences && !isEmpty(knowledgeReferences)) {
+ cacheService.delete(`knowledge-search-${message.id}`)
logger.debug(`Found ${knowledgeReferences.length} knowledge base references in cache for ID: ${message.id}`)
return knowledgeReferences
}
diff --git a/src/renderer/src/aiCore/provider/providerConfig.ts b/src/renderer/src/aiCore/provider/providerConfig.ts
index b91dad9cf7..b700a1f25f 100644
--- a/src/renderer/src/aiCore/provider/providerConfig.ts
+++ b/src/renderer/src/aiCore/provider/providerConfig.ts
@@ -5,6 +5,7 @@ import {
type ProviderId,
type ProviderSettingsMap
} from '@cherrystudio/ai-core/provider'
+import { cacheService } from '@data/CacheService'
import { isOpenAIChatCompletionOnlyModel } from '@renderer/config/models'
import { isNewApiProvider } from '@renderer/config/providers'
import {
@@ -22,7 +23,6 @@ import { cloneDeep, isEmpty } from 'lodash'
import { aihubmixProviderCreator, newApiResolverCreator, vertexAnthropicProviderCreator } from './config'
import { getAiSdkProviderId } from './factory'
-
const logger = loggerService.withContext('ProviderConfigProcessor')
/**
@@ -37,16 +37,16 @@ function getRotatedApiKey(provider: Provider): string {
return keys[0]
}
- const lastUsedKey = window.keyv.get(keyName)
- if (!lastUsedKey) {
- window.keyv.set(keyName, keys[0])
+ const lastUsedKey = cacheService.getShared(keyName) as string | undefined
+ if (lastUsedKey === undefined) {
+ cacheService.setShared(keyName, keys[0])
return keys[0]
}
const currentIndex = keys.indexOf(lastUsedKey)
const nextIndex = (currentIndex + 1) % keys.length
const nextKey = keys[nextIndex]
- window.keyv.set(keyName, nextKey)
+ cacheService.setShared(keyName, nextKey)
return nextKey
}
diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts
index eefc880a57..23b0ddebf2 100644
--- a/src/renderer/src/env.d.ts
+++ b/src/renderer/src/env.d.ts
@@ -1,7 +1,6 @@
///
import type { ToastUtilities } from '@cherrystudio/ui'
-import type KeyvStorage from '@kangfenmao/keyv-storage'
import type { HookAPI } from 'antd/es/modal/useModal'
import type { NavigateFunction } from 'react-router-dom'
@@ -17,7 +16,6 @@ declare global {
interface Window {
root: HTMLElement
modal: HookAPI
- keyv: KeyvStorage
store: any
navigate: NavigateFunction
toast: ToastUtilities
diff --git a/src/renderer/src/hooks/useScrollPosition.ts b/src/renderer/src/hooks/useScrollPosition.ts
index 872004d58e..5a3bd38d7a 100644
--- a/src/renderer/src/hooks/useScrollPosition.ts
+++ b/src/renderer/src/hooks/useScrollPosition.ts
@@ -1,3 +1,4 @@
+import { cacheService } from '@data/CacheService'
import { throttle } from 'lodash'
import { useEffect, useRef } from 'react'
@@ -9,12 +10,12 @@ export default function useScrollPosition(key: string) {
const handleScroll = throttle(() => {
const position = containerRef.current?.scrollTop ?? 0
window.requestAnimationFrame(() => {
- window.keyv.set(scrollKey, position)
+ cacheService.set(scrollKey, position)
})
}, 100)
useEffect(() => {
- const scroll = () => containerRef.current?.scrollTo({ top: window.keyv.get(scrollKey) || 0 })
+ const scroll = () => containerRef.current?.scrollTo({ top: cacheService.get(scrollKey) || 0 })
scroll()
clearTimeout(scrollTimerRef.current)
scrollTimerRef.current = setTimeout(scroll, 50)
diff --git a/src/renderer/src/init.ts b/src/renderer/src/init.ts
index 456961f913..1a7693db63 100644
--- a/src/renderer/src/init.ts
+++ b/src/renderer/src/init.ts
@@ -1,5 +1,4 @@
import { preferenceService } from '@data/PreferenceService'
-import KeyvStorage from '@kangfenmao/keyv-storage'
import { loggerService } from '@logger'
import { startAutoSync } from './services/BackupService'
@@ -8,11 +7,6 @@ import storeSyncService from './services/StoreSyncService'
import { webTraceService } from './services/WebTraceService'
loggerService.initWindowSource('mainWindow')
-function initKeyv() {
- window.keyv = new KeyvStorage()
- window.keyv.init()
-}
-
function initAutoSync() {
setTimeout(async () => {
const autoSyncStates = await preferenceService.getMultiple({
@@ -39,7 +33,6 @@ function initWebTrace() {
webTraceService.init()
}
-initKeyv()
initAutoSync()
initStoreSync()
initWebTrace()
diff --git a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx
index 4abf8f404a..5a2859ab15 100644
--- a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx
+++ b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx
@@ -137,7 +137,7 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic,
const onClearMessages = useCallback(
(topic: Topic) => {
- // window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, true)
+ // cacheService.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, true)
setGenerating(false)
EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES, topic)
},
diff --git a/src/renderer/src/pages/settings/MemorySettings/MemorySettings.tsx b/src/renderer/src/pages/settings/MemorySettings/MemorySettings.tsx
index 86548fa637..c24f65cfb6 100644
--- a/src/renderer/src/pages/settings/MemorySettings/MemorySettings.tsx
+++ b/src/renderer/src/pages/settings/MemorySettings/MemorySettings.tsx
@@ -2,6 +2,7 @@ import { ExclamationCircleOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { Flex } from '@cherrystudio/ui'
import { Switch } from '@cherrystudio/ui'
+import { cacheService } from '@data/CacheService'
import { loggerService } from '@logger'
import { DeleteIcon, EditIcon, LoadingIcon, RefreshIcon } from '@renderer/components/Icons'
import TextBadge from '@renderer/components/TextBadge'
@@ -479,8 +480,8 @@ const MemorySettings = () => {
const handleSettingsSubmit = async () => {
setSettingsModalVisible(false)
await memoryService.updateConfig()
- if (window.keyv.get('memory.wait.settings')) {
- window.keyv.remove('memory.wait.settings')
+ if (cacheService.get('memory.wait.settings')) {
+ cacheService.delete('memory.wait.settings')
dispatch(setGlobalMemoryEnabled(true))
}
}
@@ -488,7 +489,7 @@ const MemorySettings = () => {
const handleSettingsCancel = () => {
setSettingsModalVisible(false)
form.resetFields()
- window.keyv.remove('memory.wait.settings')
+ cacheService.delete('memory.wait.settings')
}
const handleResetMemories = async (userId: string) => {
@@ -554,7 +555,7 @@ const MemorySettings = () => {
const handleGlobalMemoryToggle = async (enabled: boolean) => {
if (enabled && !embedderModel) {
- window.keyv.set('memory.wait.settings', true)
+ cacheService.set('memory.wait.settings', true)
return setSettingsModalVisible(true)
}
diff --git a/src/renderer/src/providers/WebSearchProvider/BaseWebSearchProvider.ts b/src/renderer/src/providers/WebSearchProvider/BaseWebSearchProvider.ts
index b3047dc7e1..0a02c71021 100644
--- a/src/renderer/src/providers/WebSearchProvider/BaseWebSearchProvider.ts
+++ b/src/renderer/src/providers/WebSearchProvider/BaseWebSearchProvider.ts
@@ -1,3 +1,4 @@
+import { cacheService } from '@data/CacheService'
import type { WebSearchState } from '@renderer/store/websearch'
import type { WebSearchProvider, WebSearchProviderResponse } from '@renderer/types'
@@ -38,16 +39,16 @@ export default abstract class BaseWebSearchProvider {
return keys[0]
}
- const lastUsedKey = window.keyv.get(keyName)
- if (!lastUsedKey) {
- window.keyv.set(keyName, keys[0])
+ const lastUsedKey = cacheService.getShared(keyName) as string | undefined
+ if (lastUsedKey === undefined) {
+ cacheService.setShared(keyName, keys[0])
return keys[0]
}
const currentIndex = keys.indexOf(lastUsedKey)
const nextIndex = (currentIndex + 1) % keys.length
const nextKey = keys[nextIndex]
- window.keyv.set(keyName, nextKey)
+ cacheService.setShared(keyName, nextKey)
return nextKey
}
diff --git a/src/renderer/src/services/MemoryProcessor.ts b/src/renderer/src/services/MemoryProcessor.ts
index 01ba5eeb77..c0d35de288 100644
--- a/src/renderer/src/services/MemoryProcessor.ts
+++ b/src/renderer/src/services/MemoryProcessor.ts
@@ -1,3 +1,4 @@
+import { cacheService } from '@data/CacheService'
import { loggerService } from '@logger'
import { getModel } from '@renderer/hooks/useModel'
import type { AssistantMessage } from '@renderer/types'
@@ -103,7 +104,7 @@ export class MemoryProcessor {
if (!memoryConfig.llmApiClient) {
throw new Error('No LLM model configured for memory processing')
}
- const existingMemoriesResult = (window.keyv.get(`memory-search-${lastMessageId}`) as MemoryItem[]) || []
+ const existingMemoriesResult = (cacheService.get(`memory-search-${lastMessageId}`) as MemoryItem[]) || []
const existingMemories = existingMemoriesResult.map((memory) => ({
id: memory.id,
diff --git a/src/renderer/src/services/ocr/clients/OcrBaseApiClient.ts b/src/renderer/src/services/ocr/clients/OcrBaseApiClient.ts
index a81a3f972a..7394f7b475 100644
--- a/src/renderer/src/services/ocr/clients/OcrBaseApiClient.ts
+++ b/src/renderer/src/services/ocr/clients/OcrBaseApiClient.ts
@@ -1,5 +1,5 @@
+import { cacheService } from '@data/CacheService'
import type { OcrApiProvider, OcrHandler } from '@renderer/types'
-
export abstract class OcrBaseApiClient {
public provider: OcrApiProvider
protected host: string
@@ -27,16 +27,16 @@ export abstract class OcrBaseApiClient {
return keys[0]
}
- const lastUsedKey = window.keyv.get(keyName)
- if (!lastUsedKey) {
- window.keyv.set(keyName, keys[0])
+ const lastUsedKey = cacheService.getShared(keyName) as string | undefined
+ if (lastUsedKey === undefined) {
+ cacheService.setShared(keyName, keys[0])
return keys[0]
}
const currentIndex = keys.indexOf(lastUsedKey)
const nextIndex = (currentIndex + 1) % keys.length
const nextKey = keys[nextIndex]
- window.keyv.set(keyName, nextKey)
+ cacheService.setShared(keyName, nextKey)
return nextKey
}
diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts
index 8d7004ecc7..d506ab25ef 100644
--- a/src/renderer/src/store/thunk/messageThunk.ts
+++ b/src/renderer/src/store/thunk/messageThunk.ts
@@ -1,3 +1,4 @@
+import { cacheService } from '@data/CacheService'
import { loggerService } from '@logger'
import db from '@renderer/databases'
import FileManager from '@renderer/services/FileManager'
@@ -610,8 +611,8 @@ export const resendMessageThunk =
// Clear cached search results for the user message being resent
// This ensures that the regenerated responses will not use stale search results
try {
- window.keyv.remove(`web-search-${userMessageToResend.id}`)
- window.keyv.remove(`knowledge-search-${userMessageToResend.id}`)
+ cacheService.delete(`web-search-${userMessageToResend.id}`)
+ cacheService.delete(`knowledge-search-${userMessageToResend.id}`)
} catch (error) {
logger.warn(`Failed to clear keyv cache for message ${userMessageToResend.id}:`, error as Error)
}
diff --git a/src/renderer/src/windows/mini/entryPoint.tsx b/src/renderer/src/windows/mini/entryPoint.tsx
index 5974350c06..14ee738c93 100644
--- a/src/renderer/src/windows/mini/entryPoint.tsx
+++ b/src/renderer/src/windows/mini/entryPoint.tsx
@@ -2,7 +2,6 @@ import '@renderer/assets/styles/index.css'
import '@renderer/assets/styles/tailwind.css'
import '@ant-design/v5-patch-for-react-19'
-import KeyvStorage from '@kangfenmao/keyv-storage'
import { loggerService } from '@logger'
import storeSyncService from '@renderer/services/StoreSyncService'
import { createRoot } from 'react-dom/client'
@@ -11,18 +10,6 @@ import MiniWindowApp from './MiniWindowApp'
loggerService.initWindowSource('MiniWindow')
-/**
- * This function is required for model API
- * eg. BaseProviders.ts
- * Although the coupling is too strong, we have no choice but to load it
- * In multi-window handling, decoupling is needed
- */
-function initKeyv() {
- window.keyv = new KeyvStorage()
- window.keyv.init()
-}
-initKeyv()
-
//subscribe to store sync
storeSyncService.subscribe()
diff --git a/src/renderer/src/windows/selection/action/entryPoint.tsx b/src/renderer/src/windows/selection/action/entryPoint.tsx
index 82cac5a5b2..a0f6d3459d 100644
--- a/src/renderer/src/windows/selection/action/entryPoint.tsx
+++ b/src/renderer/src/windows/selection/action/entryPoint.tsx
@@ -4,7 +4,6 @@ import '@ant-design/v5-patch-for-react-19'
import { getToastUtilities } from '@cherrystudio/ui'
import { preferenceService } from '@data/PreferenceService'
-import KeyvStorage from '@kangfenmao/keyv-storage'
import { loggerService } from '@logger'
import { ToastPortal } from '@renderer/components/ToastPortal'
import AntdProvider from '@renderer/context/AntdProvider'
@@ -33,17 +32,6 @@ await preferenceService.preload([
'feature.selection.action_window_opacity'
])
-/**
- * fetchChatCompletion depends on this,
- * which is not a good design, but we have to add it for now
- */
-function initKeyv() {
- window.keyv = new KeyvStorage()
- window.keyv.init()
-}
-
-initKeyv()
-
//subscribe to store sync
storeSyncService.subscribe()
diff --git a/yarn.lock b/yarn.lock
index 18ed0cd83d..d3e4b44dbb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7188,13 +7188,6 @@ __metadata:
languageName: node
linkType: hard
-"@kangfenmao/keyv-storage@npm:^0.1.0":
- version: 0.1.0
- resolution: "@kangfenmao/keyv-storage@npm:0.1.0"
- checksum: 10c0/647cf2d2f2e403ec91d1835546aa08bc6af1468a2823c3aa2cef883bacf67eb1a88bb97be1b4c0a09bc3ed69dba2ccbb8ecc3fd13242e84d4e234d5b77707156
- languageName: node
- linkType: hard
-
"@langchain/community@npm:^0.3.50":
version: 0.3.54
resolution: "@langchain/community@npm:0.3.54"
@@ -14952,7 +14945,6 @@ __metadata:
"@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch"
"@hello-pangea/dnd": "npm:^18.0.1"
"@heroui/react": "npm:^2.8.3"
- "@kangfenmao/keyv-storage": "npm:^0.1.0"
"@langchain/community": "npm:^0.3.50"
"@libsql/client": "npm:0.14.0"
"@libsql/win32-x64-msvc": "npm:^0.4.7"