mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-13 21:57:30 +08:00
fix(memory): fix retrieval issues and enable database backup (#12073)
* fix(memory): fix retrieval issues and enable database backup - Fix memory retrieval by storing model references instead of API client configs (baseURL was missing v1 suffix causing retrieval failures) - Move memory database to DATA_PATH/Memory for proper backup support - Add migration to convert legacy embedderApiClient/llmApiClient to model references - Simplify IPC handlers by removing unnecessary async/await wrappers - Rename and relocate MemorySettingsModal for better organization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor(UserSelector): simplify user label rendering and remove unused dependencies - Update UserSelector component to directly use user IDs as labels instead of rendering them through a function. - Remove unnecessary dependency on the renderLabel function to streamline the code. * refactor(UserSelector): remove unused dependencies and simplify user avatar logic - Eliminate the getUserAvatar function and directly use user IDs for rendering. - Remove the HStack and Avatar components from the renderLabel function to streamline the UserSelector component. * refactor(ipc): simplify IPC handler for deleting all memories for a user and streamline error logging - Remove unnecessary async/await from the Memory_DeleteAllMemoriesForUser handler. - Simplify error logging in useAppInit hook for memory service configuration updates. - Update persisted reducer version from 191 to 189 in the store configuration. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6bdaba8a15
commit
6815ab65d1
@ -318,6 +318,7 @@ export enum IpcChannel {
|
||||
Memory_DeleteUser = 'memory:delete-user',
|
||||
Memory_DeleteAllMemoriesForUser = 'memory:delete-all-memories-for-user',
|
||||
Memory_GetUsersList = 'memory:get-users-list',
|
||||
Memory_MigrateMemoryDb = 'memory:migrate-memory-db',
|
||||
|
||||
// TRACE
|
||||
TRACE_SAVE_DATA = 'trace:saveData',
|
||||
|
||||
@ -686,36 +686,19 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
ipcMain.handle(IpcChannel.KnowledgeBase_Check_Quota, KnowledgeService.checkQuota.bind(KnowledgeService))
|
||||
|
||||
// memory
|
||||
ipcMain.handle(IpcChannel.Memory_Add, async (_, messages, config) => {
|
||||
return await memoryService.add(messages, config)
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_Search, async (_, query, config) => {
|
||||
return await memoryService.search(query, config)
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_List, async (_, config) => {
|
||||
return await memoryService.list(config)
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_Delete, async (_, id) => {
|
||||
return await memoryService.delete(id)
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_Update, async (_, id, memory, metadata) => {
|
||||
return await memoryService.update(id, memory, metadata)
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_Get, async (_, memoryId) => {
|
||||
return await memoryService.get(memoryId)
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_SetConfig, async (_, config) => {
|
||||
memoryService.setConfig(config)
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_DeleteUser, async (_, userId) => {
|
||||
return await memoryService.deleteUser(userId)
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_DeleteAllMemoriesForUser, async (_, userId) => {
|
||||
return await memoryService.deleteAllMemoriesForUser(userId)
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_GetUsersList, async () => {
|
||||
return await memoryService.getUsersList()
|
||||
})
|
||||
ipcMain.handle(IpcChannel.Memory_Add, (_, messages, config) => memoryService.add(messages, config))
|
||||
ipcMain.handle(IpcChannel.Memory_Search, (_, query, config) => memoryService.search(query, config))
|
||||
ipcMain.handle(IpcChannel.Memory_List, (_, config) => memoryService.list(config))
|
||||
ipcMain.handle(IpcChannel.Memory_Delete, (_, id) => memoryService.delete(id))
|
||||
ipcMain.handle(IpcChannel.Memory_Update, (_, id, memory, metadata) => memoryService.update(id, memory, metadata))
|
||||
ipcMain.handle(IpcChannel.Memory_Get, (_, memoryId) => memoryService.get(memoryId))
|
||||
ipcMain.handle(IpcChannel.Memory_SetConfig, (_, config) => memoryService.setConfig(config))
|
||||
ipcMain.handle(IpcChannel.Memory_DeleteUser, (_, userId) => memoryService.deleteUser(userId))
|
||||
ipcMain.handle(IpcChannel.Memory_DeleteAllMemoriesForUser, (_, userId) =>
|
||||
memoryService.deleteAllMemoriesForUser(userId)
|
||||
)
|
||||
ipcMain.handle(IpcChannel.Memory_GetUsersList, () => memoryService.getUsersList())
|
||||
ipcMain.handle(IpcChannel.Memory_MigrateMemoryDb, () => memoryService.migrateMemoryDb())
|
||||
|
||||
// window
|
||||
ipcMain.handle(IpcChannel.Windows_SetMinimumSize, (_, width: number, height: number) => {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import type { Client } from '@libsql/client'
|
||||
import { createClient } from '@libsql/client'
|
||||
import { loggerService } from '@logger'
|
||||
import { DATA_PATH } from '@main/config'
|
||||
import Embeddings from '@main/knowledge/embedjs/embeddings/Embeddings'
|
||||
import { makeSureDirExists } from '@main/utils'
|
||||
import type {
|
||||
AddMemoryOptions,
|
||||
AssistantMessage,
|
||||
@ -13,6 +15,7 @@ import type {
|
||||
} from '@types'
|
||||
import crypto from 'crypto'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import { MemoryQueries } from './queries'
|
||||
@ -71,6 +74,21 @@ export class MemoryService {
|
||||
return MemoryService.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate the memory database from the old path to the new path
|
||||
* If the old memory database exists, rename it to the new path
|
||||
*/
|
||||
public migrateMemoryDb(): void {
|
||||
const oldMemoryDbPath = path.join(app.getPath('userData'), 'memories.db')
|
||||
const memoryDbPath = path.join(DATA_PATH, 'Memory', 'memories.db')
|
||||
|
||||
makeSureDirExists(path.dirname(memoryDbPath))
|
||||
|
||||
if (fs.existsSync(oldMemoryDbPath)) {
|
||||
fs.renameSync(oldMemoryDbPath, memoryDbPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the database connection and create tables
|
||||
*/
|
||||
@ -80,11 +98,12 @@ export class MemoryService {
|
||||
}
|
||||
|
||||
try {
|
||||
const userDataPath = app.getPath('userData')
|
||||
const dbPath = path.join(userDataPath, 'memories.db')
|
||||
const memoryDbPath = path.join(DATA_PATH, 'Memory', 'memories.db')
|
||||
|
||||
makeSureDirExists(path.dirname(memoryDbPath))
|
||||
|
||||
this.db = createClient({
|
||||
url: `file:${dbPath}`,
|
||||
url: `file:${memoryDbPath}`,
|
||||
intMode: 'number'
|
||||
})
|
||||
|
||||
@ -168,12 +187,13 @@ export class MemoryService {
|
||||
|
||||
// Generate embedding if model is configured
|
||||
let embedding: number[] | null = null
|
||||
const embedderApiClient = this.config?.embedderApiClient
|
||||
if (embedderApiClient) {
|
||||
const embeddingModel = this.config?.embeddingModel
|
||||
|
||||
if (embeddingModel) {
|
||||
try {
|
||||
embedding = await this.generateEmbedding(trimmedMemory)
|
||||
logger.debug(
|
||||
`Generated embedding for restored memory with dimension: ${embedding.length} (target: ${this.config?.embedderDimensions || MemoryService.UNIFIED_DIMENSION})`
|
||||
`Generated embedding for restored memory with dimension: ${embedding.length} (target: ${this.config?.embeddingDimensions || MemoryService.UNIFIED_DIMENSION})`
|
||||
)
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate embedding for restored memory:', error as Error)
|
||||
@ -211,11 +231,11 @@ export class MemoryService {
|
||||
|
||||
// Generate embedding if model is configured
|
||||
let embedding: number[] | null = null
|
||||
if (this.config?.embedderApiClient) {
|
||||
if (this.config?.embeddingModel) {
|
||||
try {
|
||||
embedding = await this.generateEmbedding(trimmedMemory)
|
||||
logger.debug(
|
||||
`Generated embedding with dimension: ${embedding.length} (target: ${this.config?.embedderDimensions || MemoryService.UNIFIED_DIMENSION})`
|
||||
`Generated embedding with dimension: ${embedding.length} (target: ${this.config?.embeddingDimensions || MemoryService.UNIFIED_DIMENSION})`
|
||||
)
|
||||
|
||||
// Check for similar memories using vector similarity
|
||||
@ -300,7 +320,7 @@ export class MemoryService {
|
||||
|
||||
try {
|
||||
// If we have an embedder model configured, use vector search
|
||||
if (this.config?.embedderApiClient) {
|
||||
if (this.config?.embeddingModel) {
|
||||
try {
|
||||
const queryEmbedding = await this.generateEmbedding(query)
|
||||
return await this.hybridSearch(query, queryEmbedding, { limit, userId, agentId, filters })
|
||||
@ -497,11 +517,11 @@ export class MemoryService {
|
||||
|
||||
// Generate new embedding if model is configured
|
||||
let embedding: number[] | null = null
|
||||
if (this.config?.embedderApiClient) {
|
||||
if (this.config?.embeddingModel) {
|
||||
try {
|
||||
embedding = await this.generateEmbedding(memory)
|
||||
logger.debug(
|
||||
`Updated embedding with dimension: ${embedding.length} (target: ${this.config?.embedderDimensions || MemoryService.UNIFIED_DIMENSION})`
|
||||
`Updated embedding with dimension: ${embedding.length} (target: ${this.config?.embeddingDimensions || MemoryService.UNIFIED_DIMENSION})`
|
||||
)
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate embedding for update:', error as Error)
|
||||
@ -710,21 +730,22 @@ export class MemoryService {
|
||||
* Generate embedding for text
|
||||
*/
|
||||
private async generateEmbedding(text: string): Promise<number[]> {
|
||||
if (!this.config?.embedderApiClient) {
|
||||
if (!this.config?.embeddingModel) {
|
||||
throw new Error('Embedder model not configured')
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize embeddings instance if needed
|
||||
if (!this.embeddings) {
|
||||
if (!this.config.embedderApiClient) {
|
||||
if (!this.config.embeddingApiClient) {
|
||||
throw new Error('Embedder provider not configured')
|
||||
}
|
||||
|
||||
this.embeddings = new Embeddings({
|
||||
embedApiClient: this.config.embedderApiClient,
|
||||
dimensions: this.config.embedderDimensions
|
||||
embedApiClient: this.config.embeddingApiClient,
|
||||
dimensions: this.config.embeddingDimensions
|
||||
})
|
||||
|
||||
await this.embeddings.init()
|
||||
}
|
||||
|
||||
|
||||
@ -310,7 +310,8 @@ const api = {
|
||||
deleteUser: (userId: string) => ipcRenderer.invoke(IpcChannel.Memory_DeleteUser, userId),
|
||||
deleteAllMemoriesForUser: (userId: string) =>
|
||||
ipcRenderer.invoke(IpcChannel.Memory_DeleteAllMemoriesForUser, userId),
|
||||
getUsersList: () => ipcRenderer.invoke(IpcChannel.Memory_GetUsersList)
|
||||
getUsersList: () => ipcRenderer.invoke(IpcChannel.Memory_GetUsersList),
|
||||
migrateMemoryDb: () => ipcRenderer.invoke(IpcChannel.Memory_MigrateMemoryDb)
|
||||
},
|
||||
window: {
|
||||
setMinimumSize: (width: number, height: number) =>
|
||||
|
||||
@ -24,7 +24,8 @@ export const memorySearchTool = () => {
|
||||
}
|
||||
|
||||
const memoryConfig = selectMemoryConfig(store.getState())
|
||||
if (!memoryConfig.llmApiClient || !memoryConfig.embedderApiClient) {
|
||||
|
||||
if (!memoryConfig.llmModel || !memoryConfig.embeddingModel) {
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
@ -268,9 +268,7 @@ export function useAppInit() {
|
||||
// Update memory service configuration when it changes
|
||||
useEffect(() => {
|
||||
const memoryService = MemoryService.getInstance()
|
||||
memoryService.updateConfig().catch((error) => {
|
||||
logger.error('Failed to update memory config:', error)
|
||||
})
|
||||
memoryService.updateConfig().catch((error) => logger.error('Failed to update memory config:', error))
|
||||
}, [memoryConfig])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { InfoCircleOutlined } from '@ant-design/icons'
|
||||
import { loggerService } from '@logger'
|
||||
import { Box } from '@renderer/components/Layout'
|
||||
import MemoriesSettingsModal from '@renderer/pages/memory/settings-modal'
|
||||
import MemoriesSettingsModal from '@renderer/pages/settings/MemorySettings/MemorySettingsModal'
|
||||
import MemoryService from '@renderer/services/MemoryService'
|
||||
import { selectGlobalMemoryEnabled, selectMemoryConfig } from '@renderer/store/memory'
|
||||
import type { Assistant, AssistantSettings } from '@renderer/types'
|
||||
@ -68,7 +68,7 @@ const AssistantMemorySettings: React.FC<Props> = ({ assistant, updateAssistant,
|
||||
window.location.hash = '#/settings/memory'
|
||||
}
|
||||
|
||||
const isMemoryConfigured = memoryConfig.embedderApiClient && memoryConfig.llmApiClient
|
||||
const isMemoryConfigured = memoryConfig.embeddingModel && memoryConfig.llmModel
|
||||
const isMemoryEnabled = globalMemoryEnabled && isMemoryConfigured
|
||||
|
||||
return (
|
||||
@ -130,16 +130,16 @@ const AssistantMemorySettings: React.FC<Props> = ({ assistant, updateAssistant,
|
||||
<Text strong>{t('memory.stored_memories')}: </Text>
|
||||
<Text>{memoryStats.loading ? t('common.loading') : memoryStats.count}</Text>
|
||||
</div>
|
||||
{memoryConfig.embedderApiClient && (
|
||||
{memoryConfig.embeddingModel && (
|
||||
<div>
|
||||
<Text strong>{t('memory.embedding_model')}: </Text>
|
||||
<Text code>{memoryConfig.embedderApiClient.model}</Text>
|
||||
<Text code>{memoryConfig.embeddingModel.id}</Text>
|
||||
</div>
|
||||
)}
|
||||
{memoryConfig.llmApiClient && (
|
||||
{memoryConfig.llmModel && (
|
||||
<div>
|
||||
<Text strong>{t('memory.llm_model')}: </Text>
|
||||
<Text code>{memoryConfig.llmApiClient.model}</Text>
|
||||
<Text code>{memoryConfig.llmModel.id}</Text>
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
|
||||
@ -5,7 +5,6 @@ import { HStack } from '@renderer/components/Layout'
|
||||
import TextBadge from '@renderer/components/TextBadge'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useModel } from '@renderer/hooks/useModel'
|
||||
import MemoriesSettingsModal from '@renderer/pages/memory/settings-modal'
|
||||
import MemoryService from '@renderer/services/MemoryService'
|
||||
import {
|
||||
selectCurrentUserId,
|
||||
@ -34,6 +33,7 @@ import {
|
||||
SettingTitle
|
||||
} from '../index'
|
||||
import { DEFAULT_USER_ID } from './constants'
|
||||
import MemorySettingsModal from './MemorySettingsModal'
|
||||
import UserSelector from './UserSelector'
|
||||
|
||||
const logger = loggerService.withContext('MemorySettings')
|
||||
@ -154,23 +154,17 @@ const EditMemoryModal: React.FC<EditMemoryModalProps> = ({ visible, memory, onCa
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
width={600}
|
||||
centered
|
||||
transitionName="animation-move-down"
|
||||
okButtonProps={{ loading: loading, title: t('common.save'), onClick: () => form.submit() }}
|
||||
styles={{
|
||||
header: {
|
||||
borderBottom: '0.5px solid var(--color-border)',
|
||||
paddingBottom: 16
|
||||
},
|
||||
body: {
|
||||
paddingTop: 24
|
||||
paddingBottom: 16,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0
|
||||
}
|
||||
}}
|
||||
footer={[
|
||||
<Button key="cancel" size="large" onClick={onCancel}>
|
||||
{t('common.cancel')}
|
||||
</Button>,
|
||||
<Button key="submit" type="primary" size="large" loading={loading} onClick={() => form.submit()}>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
]}>
|
||||
}}>
|
||||
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
||||
<Form.Item
|
||||
label={t('memory.memory_content')}
|
||||
@ -548,10 +542,10 @@ const MemorySettings = () => {
|
||||
}
|
||||
|
||||
const memoryConfig = useSelector(selectMemoryConfig)
|
||||
const embedderModel = useModel(memoryConfig.embedderApiClient?.model, memoryConfig.embedderApiClient?.provider)
|
||||
const embeddingModel = useModel(memoryConfig.embeddingModel?.id, memoryConfig.embeddingModel?.provider)
|
||||
|
||||
const handleGlobalMemoryToggle = async (enabled: boolean) => {
|
||||
if (enabled && !embedderModel) {
|
||||
if (enabled && !embeddingModel) {
|
||||
window.keyv.set('memory.wait.settings', true)
|
||||
return setSettingsModalVisible(true)
|
||||
}
|
||||
@ -799,7 +793,7 @@ const MemorySettings = () => {
|
||||
existingUsers={[...uniqueUsers, DEFAULT_USER_ID]}
|
||||
/>
|
||||
|
||||
<MemoriesSettingsModal
|
||||
<MemorySettingsModal
|
||||
visible={settingsModalVisible}
|
||||
onSubmit={async () => await handleSettingsSubmit()}
|
||||
onCancel={handleSettingsCancel}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { loggerService } from '@logger'
|
||||
import AiProvider from '@renderer/aiCore'
|
||||
import InputEmbeddingDimension from '@renderer/components/InputEmbeddingDimension'
|
||||
import ModelSelector from '@renderer/components/ModelSelector'
|
||||
import { InfoTooltip } from '@renderer/components/TooltipIcons'
|
||||
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||
import { useModel } from '@renderer/hooks/useModel'
|
||||
import { getModel, useModel } from '@renderer/hooks/useModel'
|
||||
import { useProviders } from '@renderer/hooks/useProvider'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
import { selectMemoryConfig, updateMemoryConfig } from '@renderer/store/memory'
|
||||
@ -12,12 +11,12 @@ import type { Model } from '@renderer/types'
|
||||
import { Flex, Form, Modal } from 'antd'
|
||||
import { t } from 'i18next'
|
||||
import type { FC } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
const logger = loggerService.withContext('MemoriesSettingsModal')
|
||||
const logger = loggerService.withContext('MemorySettingsModal')
|
||||
|
||||
interface MemoriesSettingsModalProps {
|
||||
interface MemorySettingsModalProps {
|
||||
visible: boolean
|
||||
onSubmit: (values: any) => void
|
||||
onCancel: () => void
|
||||
@ -26,78 +25,57 @@ interface MemoriesSettingsModalProps {
|
||||
|
||||
type formValue = {
|
||||
llmModel: string
|
||||
embedderModel: string
|
||||
embedderDimensions: number
|
||||
embeddingModel: string
|
||||
embeddingDimensions: number
|
||||
}
|
||||
|
||||
const MemoriesSettingsModal: FC<MemoriesSettingsModalProps> = ({ visible, onSubmit, onCancel, form }) => {
|
||||
const MemorySettingsModal: FC<MemorySettingsModalProps> = ({ visible, onSubmit, onCancel, form }) => {
|
||||
const { providers } = useProviders()
|
||||
const dispatch = useDispatch()
|
||||
const memoryConfig = useSelector(selectMemoryConfig)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// Get all models for lookup
|
||||
const allModels = useMemo(() => providers.flatMap((p) => p.models), [providers])
|
||||
const llmModel = useModel(memoryConfig.llmApiClient?.model, memoryConfig.llmApiClient?.provider)
|
||||
const embedderModel = useModel(memoryConfig.embedderApiClient?.model, memoryConfig.embedderApiClient?.provider)
|
||||
|
||||
const findModelById = useCallback(
|
||||
(id: string | undefined) => (id ? allModels.find((m) => getModelUniqId(m) === id) : undefined),
|
||||
[allModels]
|
||||
)
|
||||
const llmModel = useModel(memoryConfig.llmModel?.id, memoryConfig.llmModel?.provider)
|
||||
const embeddingModel = useModel(memoryConfig.embeddingModel?.id, memoryConfig.embeddingModel?.provider)
|
||||
|
||||
// Initialize form with current memory config when modal opens
|
||||
useEffect(() => {
|
||||
if (visible && memoryConfig) {
|
||||
form.setFieldsValue({
|
||||
llmModel: getModelUniqId(llmModel),
|
||||
embedderModel: getModelUniqId(embedderModel),
|
||||
embedderDimensions: memoryConfig.embedderDimensions
|
||||
embeddingModel: getModelUniqId(embeddingModel),
|
||||
embeddingDimensions: memoryConfig.embeddingDimensions
|
||||
// customFactExtractionPrompt: memoryConfig.customFactExtractionPrompt,
|
||||
// customUpdateMemoryPrompt: memoryConfig.customUpdateMemoryPrompt
|
||||
})
|
||||
}
|
||||
}, [visible, memoryConfig, form, llmModel, embedderModel])
|
||||
}, [embeddingModel, form, llmModel, memoryConfig, visible])
|
||||
|
||||
const handleFormSubmit = async (values: formValue) => {
|
||||
try {
|
||||
// Convert model IDs back to Model objects
|
||||
const llmModel = findModelById(values.llmModel)
|
||||
const llmProvider = providers.find((p) => p.id === llmModel?.provider)
|
||||
const aiLlmProvider = new AiProvider(llmProvider!)
|
||||
const embedderModel = findModelById(values.embedderModel)
|
||||
const embedderProvider = providers.find((p) => p.id === embedderModel?.provider)
|
||||
const aiEmbedderProvider = new AiProvider(embedderProvider!)
|
||||
if (embedderModel) {
|
||||
const llmModel = getModel(values.llmModel)
|
||||
const embeddingModel = getModel(values.embeddingModel)
|
||||
|
||||
if (embeddingModel) {
|
||||
setLoading(true)
|
||||
const provider = providers.find((p) => p.id === embedderModel.provider)
|
||||
const provider = providers.find((p) => p.id === embeddingModel.provider)
|
||||
|
||||
if (!provider) {
|
||||
return
|
||||
}
|
||||
|
||||
const finalDimensions =
|
||||
typeof values.embedderDimensions === 'string'
|
||||
? parseInt(values.embedderDimensions)
|
||||
: values.embedderDimensions
|
||||
typeof values.embeddingDimensions === 'string'
|
||||
? parseInt(values.embeddingDimensions)
|
||||
: values.embeddingDimensions
|
||||
|
||||
const updatedConfig = {
|
||||
...memoryConfig,
|
||||
llmApiClient: {
|
||||
model: llmModel?.id ?? '',
|
||||
provider: llmProvider?.id ?? '',
|
||||
apiKey: aiLlmProvider.getApiKey(),
|
||||
baseURL: aiLlmProvider.getBaseURL(),
|
||||
apiVersion: llmProvider?.apiVersion
|
||||
},
|
||||
embedderApiClient: {
|
||||
model: embedderModel?.id ?? '',
|
||||
provider: embedderProvider?.id ?? '',
|
||||
apiKey: aiEmbedderProvider.getApiKey(),
|
||||
baseURL: aiEmbedderProvider.getBaseURL(),
|
||||
apiVersion: embedderProvider?.apiVersion
|
||||
},
|
||||
embedderDimensions: finalDimensions
|
||||
llmModel,
|
||||
embeddingModel,
|
||||
embeddingDimensions: finalDimensions
|
||||
// customFactExtractionPrompt: values.customFactExtractionPrompt,
|
||||
// customUpdateMemoryPrompt: values.customUpdateMemoryPrompt
|
||||
}
|
||||
@ -150,7 +128,7 @@ const MemoriesSettingsModal: FC<MemoriesSettingsModalProps> = ({ visible, onSubm
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('memory.embedding_model')}
|
||||
name="embedderModel"
|
||||
name="embeddingModel"
|
||||
rules={[{ required: true, message: t('memory.please_select_embedding_model') }]}>
|
||||
<ModelSelector
|
||||
providers={providers}
|
||||
@ -160,10 +138,10 @@ const MemoriesSettingsModal: FC<MemoriesSettingsModalProps> = ({ visible, onSubm
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) => prevValues.embedderModel !== currentValues.embedderModel}>
|
||||
shouldUpdate={(prevValues, currentValues) => prevValues.embeddingModel !== currentValues.embeddingModel}>
|
||||
{({ getFieldValue }) => {
|
||||
const embedderModelId = getFieldValue('embedderModel')
|
||||
const embedderModel = findModelById(embedderModelId)
|
||||
const embeddingModelId = getFieldValue('embeddingModel')
|
||||
const embeddingModel = getModel(embeddingModelId)
|
||||
return (
|
||||
<Form.Item
|
||||
label={
|
||||
@ -172,7 +150,7 @@ const MemoriesSettingsModal: FC<MemoriesSettingsModalProps> = ({ visible, onSubm
|
||||
<InfoTooltip title={t('knowledge.dimensions_size_tooltip')} />
|
||||
</Flex>
|
||||
}
|
||||
name="embedderDimensions"
|
||||
name="embeddingDimensions"
|
||||
rules={[
|
||||
{
|
||||
validator(_, value) {
|
||||
@ -183,7 +161,7 @@ const MemoriesSettingsModal: FC<MemoriesSettingsModalProps> = ({ visible, onSubm
|
||||
}
|
||||
}
|
||||
]}>
|
||||
<InputEmbeddingDimension model={embedderModel} disabled={!embedderModel} />
|
||||
<InputEmbeddingDimension model={embeddingModel} disabled={!embeddingModel} />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
@ -199,4 +177,4 @@ const MemoriesSettingsModal: FC<MemoriesSettingsModalProps> = ({ visible, onSubm
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoriesSettingsModal
|
||||
export default MemorySettingsModal
|
||||
@ -1,7 +1,6 @@
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { Avatar, Button, Select, Space, Tooltip } from 'antd'
|
||||
import { Button, Select, Space, Tooltip } from 'antd'
|
||||
import { UserRoundPlus } from 'lucide-react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { DEFAULT_USER_ID } from './constants'
|
||||
@ -16,39 +15,18 @@ interface UserSelectorProps {
|
||||
const UserSelector: React.FC<UserSelectorProps> = ({ currentUser, uniqueUsers, onUserSwitch, onAddUser }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const getUserAvatar = useCallback((user: string) => {
|
||||
return user === DEFAULT_USER_ID ? user.slice(0, 1).toUpperCase() : user.slice(0, 2).toUpperCase()
|
||||
}, [])
|
||||
|
||||
const renderLabel = useCallback(
|
||||
(userId: string, userName: string) => {
|
||||
return (
|
||||
<HStack alignItems="center" gap={10}>
|
||||
<Avatar size={20} style={{ background: 'var(--color-primary)' }}>
|
||||
{getUserAvatar(userId)}
|
||||
</Avatar>
|
||||
<span>{userName}</span>
|
||||
</HStack>
|
||||
)
|
||||
},
|
||||
[getUserAvatar]
|
||||
)
|
||||
|
||||
const options = useMemo(() => {
|
||||
const defaultOption = {
|
||||
value: DEFAULT_USER_ID,
|
||||
label: renderLabel(DEFAULT_USER_ID, t('memory.default_user'))
|
||||
label: t('memory.default_user')
|
||||
}
|
||||
|
||||
const userOptions = uniqueUsers
|
||||
.filter((user) => user !== DEFAULT_USER_ID)
|
||||
.map((user) => ({
|
||||
value: user,
|
||||
label: renderLabel(user, user)
|
||||
}))
|
||||
.map((user) => ({ value: user, label: user }))
|
||||
|
||||
return [defaultOption, ...userOptions]
|
||||
}, [renderLabel, t, uniqueUsers])
|
||||
}, [t, uniqueUsers])
|
||||
|
||||
return (
|
||||
<Space.Compact>
|
||||
|
||||
@ -40,7 +40,7 @@ export class MemoryProcessor {
|
||||
try {
|
||||
const { memoryConfig } = config
|
||||
|
||||
if (!memoryConfig.llmApiClient) {
|
||||
if (!memoryConfig.llmModel) {
|
||||
throw new Error('No LLM model configured for memory processing')
|
||||
}
|
||||
|
||||
@ -53,8 +53,9 @@ export class MemoryProcessor {
|
||||
const responseContent = await fetchGenerate({
|
||||
prompt: systemPrompt,
|
||||
content: userPrompt,
|
||||
model: getModel(memoryConfig.llmApiClient.model, memoryConfig.llmApiClient.provider)
|
||||
model: getModel(memoryConfig.llmModel.id, memoryConfig.llmModel.provider)
|
||||
})
|
||||
|
||||
if (!responseContent || responseContent.trim() === '') {
|
||||
return []
|
||||
}
|
||||
@ -100,9 +101,10 @@ export class MemoryProcessor {
|
||||
|
||||
const { memoryConfig, assistantId, userId, lastMessageId } = config
|
||||
|
||||
if (!memoryConfig.llmApiClient) {
|
||||
if (!memoryConfig.llmModel) {
|
||||
throw new Error('No LLM model configured for memory processing')
|
||||
}
|
||||
|
||||
const existingMemoriesResult = (window.keyv.get(`memory-search-${lastMessageId}`) as MemoryItem[]) || []
|
||||
|
||||
const existingMemories = existingMemoriesResult.map((memory) => ({
|
||||
@ -123,7 +125,7 @@ export class MemoryProcessor {
|
||||
const responseContent = await fetchGenerate({
|
||||
prompt: updateMemorySystemPrompt,
|
||||
content: updateMemoryUserPrompt,
|
||||
model: getModel(memoryConfig.llmApiClient.model, memoryConfig.llmApiClient.provider)
|
||||
model: getModel(memoryConfig.llmModel.id, memoryConfig.llmModel.provider)
|
||||
})
|
||||
if (!responseContent || responseContent.trim() === '') {
|
||||
return []
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { getModel } from '@renderer/hooks/useModel'
|
||||
import store from '@renderer/store'
|
||||
import { selectMemoryConfig } from '@renderer/store/memory'
|
||||
import type {
|
||||
AddMemoryOptions,
|
||||
AssistantMessage,
|
||||
KnowledgeBase,
|
||||
MemoryHistoryItem,
|
||||
MemoryListOptions,
|
||||
MemorySearchOptions,
|
||||
MemorySearchResult
|
||||
} from '@types'
|
||||
import { now } from 'lodash'
|
||||
|
||||
import { getKnowledgeBaseParams } from './KnowledgeService'
|
||||
|
||||
const logger = loggerService.withContext('MemoryService')
|
||||
|
||||
@ -203,16 +208,24 @@ class MemoryService {
|
||||
}
|
||||
|
||||
const memoryConfig = selectMemoryConfig(store.getState())
|
||||
const embedderApiClient = memoryConfig.embedderApiClient
|
||||
const llmApiClient = memoryConfig.llmApiClient
|
||||
const embeddingModel = memoryConfig.embeddingModel
|
||||
|
||||
const configWithProviders = {
|
||||
// Get knowledge base params for memory
|
||||
const { embedApiClient: embeddingApiClient } = getKnowledgeBaseParams({
|
||||
id: 'memory',
|
||||
name: 'Memory',
|
||||
model: getModel(embeddingModel?.id, embeddingModel?.provider),
|
||||
dimensions: memoryConfig.embeddingDimensions,
|
||||
items: [],
|
||||
created_at: now(),
|
||||
updated_at: now(),
|
||||
version: 1
|
||||
} as KnowledgeBase)
|
||||
|
||||
return window.api.memory.setConfig({
|
||||
...memoryConfig,
|
||||
embedderApiClient,
|
||||
llmApiClient
|
||||
}
|
||||
|
||||
return window.api.memory.setConfig(configWithProviders)
|
||||
embeddingApiClient
|
||||
})
|
||||
} catch (error) {
|
||||
logger.warn('Failed to update memory config:', error as Error)
|
||||
return
|
||||
|
||||
@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 188,
|
||||
version: 189,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -17,7 +17,7 @@ export interface MemoryState {
|
||||
|
||||
// Default memory configuration to avoid undefined errors
|
||||
const defaultMemoryConfig: MemoryConfig = {
|
||||
embedderDimensions: 1536,
|
||||
embeddingDimensions: undefined,
|
||||
isAutoDimensions: true,
|
||||
customFactExtractionPrompt: factExtractionPrompt,
|
||||
customUpdateMemoryPrompt: updateMemorySystemPrompt
|
||||
|
||||
@ -18,6 +18,7 @@ import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
||||
import { SYSTEM_PROVIDERS } from '@renderer/config/providers'
|
||||
import { DEFAULT_SIDEBAR_ICONS } from '@renderer/config/sidebar'
|
||||
import db from '@renderer/databases'
|
||||
import { getModel } from '@renderer/hooks/useModel'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { DEFAULT_ASSISTANT_SETTINGS } from '@renderer/services/AssistantService'
|
||||
import { defaultPreprocessProviders } from '@renderer/store/preprocess'
|
||||
@ -3068,6 +3069,35 @@ const migrateConfig = {
|
||||
logger.error('migrate 188 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
// 1.7.7
|
||||
'189': (state: RootState) => {
|
||||
try {
|
||||
window.api.memory.migrateMemoryDb()
|
||||
// @ts-ignore
|
||||
const memoryLlmApiClient = state?.memory?.memoryConfig?.llmApiClient
|
||||
// @ts-ignore
|
||||
const memoryEmbeddingApiClient = state?.memory?.memoryConfig?.embedderApiClient
|
||||
|
||||
if (memoryLlmApiClient) {
|
||||
state.memory.memoryConfig.llmModel = getModel(memoryLlmApiClient.model, memoryLlmApiClient.provider)
|
||||
// @ts-ignore
|
||||
delete state.memory.memoryConfig.llmApiClient
|
||||
}
|
||||
|
||||
if (memoryEmbeddingApiClient) {
|
||||
state.memory.memoryConfig.embeddingModel = getModel(
|
||||
memoryEmbeddingApiClient.model,
|
||||
memoryEmbeddingApiClient.provider
|
||||
)
|
||||
// @ts-ignore
|
||||
delete state.memory.memoryConfig.embedderApiClient
|
||||
}
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 189 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -915,17 +915,11 @@ export * from './tool'
|
||||
// Memory Service Types
|
||||
// ========================================================================
|
||||
export interface MemoryConfig {
|
||||
/**
|
||||
* @deprecated use embedderApiClient instead
|
||||
*/
|
||||
embedderModel?: Model
|
||||
embedderDimensions?: number
|
||||
/**
|
||||
* @deprecated use llmApiClient instead
|
||||
*/
|
||||
embeddingDimensions?: number
|
||||
embeddingModel?: Model
|
||||
llmModel?: Model
|
||||
embedderApiClient?: ApiClient
|
||||
llmApiClient?: ApiClient
|
||||
// Dynamically retrieved, not persistently stored
|
||||
embeddingApiClient?: ApiClient
|
||||
customFactExtractionPrompt?: string
|
||||
customUpdateMemoryPrompt?: string
|
||||
/** Indicates whether embedding dimensions are automatically detected */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user