Compare commits

...

4 Commits

Author SHA1 Message Date
one
630c8e4d38 refactor: improve UserSelector with tag 2025-08-05 11:32:47 +08:00
one
3fd53572dd Merge branch 'main' into feature/per-assistant-memory-config 2025-08-05 11:23:01 +08:00
Vaayne
3aec08d650 feat: improve memory service logging and per-assistant configuration
- Enhance memory search logging with structured context in MemoryProcessor
- Add per-assistant memory configuration support in MemoryService
- Reduce log verbosity in StreamProcessingService (debug to silly)
- Fix memory search to use assistant's memoryUserId when available

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-02 11:42:48 +08:00
Vaayne
7328664edf feat: add per-assistant memory configuration
- Add memory configuration options per assistant
- Update UI for memory settings management
- Add toggle for assistant-specific memory usage
- Update translation files for memory settings
- Enhance MemoryService for per-assistant functionality

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-7ea2038f-9b07-485f-b489-e29d3953323f
2025-07-31 00:00:18 +08:00
13 changed files with 354 additions and 39 deletions

View File

@@ -998,6 +998,7 @@
}, },
"memory": { "memory": {
"actions": "Actions", "actions": "Actions",
"active_memory_user": "Active Memory User",
"add_failed": "Failed to add memory", "add_failed": "Failed to add memory",
"add_first_memory": "Add Your First Memory", "add_first_memory": "Add Your First Memory",
"add_memory": "Add Memory", "add_memory": "Add Memory",

View File

@@ -998,6 +998,7 @@
}, },
"memory": { "memory": {
"actions": "アクション", "actions": "アクション",
"active_memory_user": "アクティブメモリユーザー",
"add_failed": "メモリーの追加に失敗しました", "add_failed": "メモリーの追加に失敗しました",
"add_first_memory": "最初のメモリを追加", "add_first_memory": "最初のメモリを追加",
"add_memory": "メモリーを追加", "add_memory": "メモリーを追加",

View File

@@ -998,6 +998,7 @@
}, },
"memory": { "memory": {
"actions": "Действия", "actions": "Действия",
"active_memory_user": "Активный пользователь памяти",
"add_failed": "Не удалось добавить память", "add_failed": "Не удалось добавить память",
"add_first_memory": "Добавить первое воспоминание", "add_first_memory": "Добавить первое воспоминание",
"add_memory": "Добавить память", "add_memory": "Добавить память",

View File

@@ -998,6 +998,7 @@
}, },
"memory": { "memory": {
"actions": "操作", "actions": "操作",
"active_memory_user": "活跃记忆用户",
"add_failed": "添加记忆失败", "add_failed": "添加记忆失败",
"add_first_memory": "添加您的第一条记忆", "add_first_memory": "添加您的第一条记忆",
"add_memory": "添加记忆", "add_memory": "添加记忆",

View File

@@ -998,6 +998,7 @@
}, },
"memory": { "memory": {
"actions": "操作", "actions": "操作",
"active_memory_user": "活躍記憶使用者",
"add_failed": "新增記憶失敗", "add_failed": "新增記憶失敗",
"add_first_memory": "新增您的第一個記憶", "add_first_memory": "新增您的第一個記憶",
"add_memory": "新增記憶", "add_memory": "新增記憶",

View File

@@ -3,9 +3,9 @@ import { loggerService } from '@logger'
import { Box } from '@renderer/components/Layout' import { Box } from '@renderer/components/Layout'
import MemoriesSettingsModal from '@renderer/pages/memory/settings-modal' import MemoriesSettingsModal from '@renderer/pages/memory/settings-modal'
import MemoryService from '@renderer/services/MemoryService' import MemoryService from '@renderer/services/MemoryService'
import { selectGlobalMemoryEnabled, selectMemoryConfig } from '@renderer/store/memory' import { selectCurrentUserId, selectGlobalMemoryEnabled, selectMemoryConfig } from '@renderer/store/memory'
import { Assistant, AssistantSettings } from '@renderer/types' import { Assistant, AssistantSettings } from '@renderer/types'
import { Alert, Button, Card, Space, Switch, Tooltip, Typography } from 'antd' import { Alert, Button, Card, Select, Space, Switch, Tooltip, Typography } from 'antd'
import { useForm } from 'antd/es/form/Form' import { useForm } from 'antd/es/form/Form'
import { Settings2 } from 'lucide-react' import { Settings2 } from 'lucide-react'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
@@ -28,19 +28,36 @@ const AssistantMemorySettings: React.FC<Props> = ({ assistant, updateAssistant,
const { t } = useTranslation() const { t } = useTranslation()
const memoryConfig = useSelector(selectMemoryConfig) const memoryConfig = useSelector(selectMemoryConfig)
const globalMemoryEnabled = useSelector(selectGlobalMemoryEnabled) const globalMemoryEnabled = useSelector(selectGlobalMemoryEnabled)
const currentUserId = useSelector(selectCurrentUserId)
const [memoryStats, setMemoryStats] = useState<{ count: number; loading: boolean }>({ const [memoryStats, setMemoryStats] = useState<{ count: number; loading: boolean }>({
count: 0, count: 0,
loading: true loading: true
}) })
const [availableUsers, setAvailableUsers] = useState<
{ userId: string; memoryCount: number; lastMemoryDate: string }[]
>([])
const [settingsModalVisible, setSettingsModalVisible] = useState(false) const [settingsModalVisible, setSettingsModalVisible] = useState(false)
const memoryService = MemoryService.getInstance() const memoryService = MemoryService.getInstance()
const form = useForm() const form = useForm()
// Load available memory users
const loadUsers = useCallback(async () => {
try {
const users = await memoryService.getUsersList()
setAvailableUsers(users)
} catch (error) {
logger.error('Failed to load memory users:', error as Error)
setAvailableUsers([])
}
}, [memoryService])
// Load memory statistics for this assistant // Load memory statistics for this assistant
const loadMemoryStats = useCallback(async () => { const loadMemoryStats = useCallback(async () => {
setMemoryStats((prev) => ({ ...prev, loading: true })) setMemoryStats((prev) => ({ ...prev, loading: true }))
try { try {
const effectiveUserId = memoryService.getEffectiveUserId(assistant, currentUserId)
const result = await memoryService.list({ const result = await memoryService.list({
userId: effectiveUserId,
agentId: assistant.id, agentId: assistant.id,
limit: 1000 limit: 1000
}) })
@@ -49,16 +66,25 @@ const AssistantMemorySettings: React.FC<Props> = ({ assistant, updateAssistant,
logger.error('Failed to load memory stats:', error as Error) logger.error('Failed to load memory stats:', error as Error)
setMemoryStats({ count: 0, loading: false }) setMemoryStats({ count: 0, loading: false })
} }
}, [assistant.id, memoryService]) }, [assistant, currentUserId, memoryService])
useEffect(() => { useEffect(() => {
loadUsers()
loadMemoryStats() loadMemoryStats()
}, [loadMemoryStats]) }, [loadUsers, loadMemoryStats])
const handleMemoryToggle = (enabled: boolean) => { const handleMemoryToggle = (enabled: boolean) => {
updateAssistant({ ...assistant, enableMemory: enabled }) updateAssistant({ ...assistant, enableMemory: enabled })
} }
const handleMemoryUserChange = (value: string) => {
// 'global' means use global default (undefined)
const memoryUserId = value === 'global' ? undefined : value
updateAssistant({ ...assistant, memoryUserId })
// Reload stats after changing user
setTimeout(() => loadMemoryStats(), 100)
}
const handleNavigateToMemory = () => { const handleNavigateToMemory = () => {
// Close current modal/page first // Close current modal/page first
if (onClose) { if (onClose) {
@@ -70,6 +96,8 @@ const AssistantMemorySettings: React.FC<Props> = ({ assistant, updateAssistant,
const isMemoryConfigured = memoryConfig.embedderApiClient && memoryConfig.llmApiClient const isMemoryConfigured = memoryConfig.embedderApiClient && memoryConfig.llmApiClient
const isMemoryEnabled = globalMemoryEnabled && isMemoryConfigured const isMemoryEnabled = globalMemoryEnabled && isMemoryConfigured
const effectiveUserId = memoryService.getEffectiveUserId(assistant, currentUserId)
const currentMemoryUser = assistant.memoryUserId || 'global'
return ( return (
<Container> <Container>
@@ -124,12 +152,41 @@ const AssistantMemorySettings: React.FC<Props> = ({ assistant, updateAssistant,
/> />
)} )}
{/* Memory User Selection */}
{assistant.enableMemory && isMemoryEnabled && (
<Card size="small" style={{ marginBottom: 16 }}>
<Space direction="vertical" style={{ width: '100%' }}>
<div>
<Text strong>{t('memory.active_memory_user')}: </Text>
<Select
value={currentMemoryUser}
onChange={handleMemoryUserChange}
style={{ width: 200, marginLeft: 8 }}
disabled={!assistant.enableMemory}>
<Select.Option value="global">
{t('memory.default_user')} ({currentUserId})
</Select.Option>
{availableUsers.map((user) => (
<Select.Option key={user.userId} value={user.userId}>
{user.userId} ({user.memoryCount} memories)
</Select.Option>
))}
</Select>
</div>
</Space>
</Card>
)}
<Card size="small" style={{ marginBottom: 16 }}> <Card size="small" style={{ marginBottom: 16 }}>
<Space direction="vertical" style={{ width: '100%' }}> <Space direction="vertical" style={{ width: '100%' }}>
<div> <div>
<Text strong>{t('memory.stored_memories')}: </Text> <Text strong>{t('memory.stored_memories')}: </Text>
<Text>{memoryStats.loading ? t('common.loading') : memoryStats.count}</Text> <Text>{memoryStats.loading ? t('common.loading') : memoryStats.count}</Text>
</div> </div>
<div>
<Text strong>{t('memory.active_memory_user')}: </Text>
<Text code>{effectiveUserId}</Text>
</div>
{memoryConfig.embedderApiClient && ( {memoryConfig.embedderApiClient && (
<div> <div>
<Text strong>{t('memory.embedding_model')}: </Text> <Text strong>{t('memory.embedding_model')}: </Text>

View File

@@ -458,7 +458,7 @@ const MemorySettings = () => {
try { try {
// Create the user by adding an initial memory with the userId // Create the user by adding an initial memory with the userId
// This implicitly creates the user in the system // This implicitly creates the user in the system
await memoryService.setCurrentUser(userId) memoryService.setCurrentUser(userId)
await memoryService.add(t('memory.initial_memory_content'), { userId }) await memoryService.add(t('memory.initial_memory_content'), { userId })
// Refresh the users list from the database to persist the new user // Refresh the users list from the database to persist the new user

View File

@@ -1,4 +1,6 @@
import CustomTag from '@renderer/components/CustomTag'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { useAssistants } from '@renderer/hooks/useAssistant'
import { Avatar, Button, Select, Space, Tooltip } from 'antd' import { Avatar, Button, Select, Space, Tooltip } from 'antd'
import { UserRoundPlus } from 'lucide-react' import { UserRoundPlus } from 'lucide-react'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
@@ -15,23 +17,50 @@ interface UserSelectorProps {
const UserSelector: React.FC<UserSelectorProps> = ({ currentUser, uniqueUsers, onUserSwitch, onAddUser }) => { const UserSelector: React.FC<UserSelectorProps> = ({ currentUser, uniqueUsers, onUserSwitch, onAddUser }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { assistants } = useAssistants()
const getUserAvatar = useCallback((user: string) => { const getUserAvatar = useCallback((user: string) => {
return user === DEFAULT_USER_ID ? user.slice(0, 1).toUpperCase() : user.slice(0, 2).toUpperCase() return user === DEFAULT_USER_ID ? user.slice(0, 1).toUpperCase() : user.slice(0, 2).toUpperCase()
}, []) }, [])
// Get assistants linked to a specific memory user
const getAssistantsForUser = useCallback(
(userId: string) => {
return assistants.filter(
(assistant) =>
// Assistant uses this user if either:
// 1. memoryUserId explicitly matches
// 2. memoryUserId is undefined and this is the current global user
assistant.memoryUserId === userId || (!assistant.memoryUserId && userId === currentUser)
)
},
[assistants, currentUser]
)
const renderLabel = useCallback( const renderLabel = useCallback(
(userId: string, userName: string) => { (userId: string, userName: string) => {
const linkedAssistants = getAssistantsForUser(userId)
return ( return (
<HStack alignItems="center" gap={10}> <HStack alignItems="center" justifyContent="space-between" style={{ width: '100%' }}>
<Avatar size={20} style={{ background: 'var(--color-primary)' }}> <HStack alignItems="center" gap={8} style={{ minWidth: 0 }}>
{getUserAvatar(userId)} <Avatar size={20} style={{ background: 'var(--color-primary)' }}>
</Avatar> {getUserAvatar(userId)}
<span>{userName}</span> </Avatar>
<span style={{ whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }}>{userName}</span>
</HStack>
{linkedAssistants.length > 0 && (
<CustomTag
color="#8c8c8c"
size={10}
tooltip={`Linked Assistants: ${linkedAssistants.map((a) => a.name).join(', ')}`}>
{linkedAssistants.length}
</CustomTag>
)}
</HStack> </HStack>
) )
}, },
[getUserAvatar] [getUserAvatar, getAssistantsForUser]
) )
const options = useMemo(() => { const options = useMemo(() => {

View File

@@ -67,6 +67,7 @@ import {
} from './AssistantService' } from './AssistantService'
import { processKnowledgeSearch } from './KnowledgeService' import { processKnowledgeSearch } from './KnowledgeService'
import { MemoryProcessor } from './MemoryProcessor' import { MemoryProcessor } from './MemoryProcessor'
import MemoryService from './MemoryService'
import { import {
filterContextMessages, filterContextMessages,
filterEmptyMessages, filterEmptyMessages,
@@ -232,10 +233,12 @@ async function fetchExternalTool(
} }
if (memoryConfig.llmApiClient && memoryConfig.embedderApiClient) { if (memoryConfig.llmApiClient && memoryConfig.embedderApiClient) {
const currentUserId = selectCurrentUserId(store.getState()) const globalUserId = selectCurrentUserId(store.getState())
// Search for relevant memories const memoryService = MemoryService.getInstance()
const processorConfig = MemoryProcessor.getProcessorConfig(memoryConfig, assistant.id, currentUserId) const effectiveUserId = memoryService.getEffectiveUserId(assistant, globalUserId)
logger.info(`Searching for relevant memories with content: ${content}`) // Search for relevant memories using effective user ID
const processorConfig = MemoryProcessor.getProcessorConfig(memoryConfig, assistant.id, effectiveUserId)
logger.info(`Searching for relevant memories with content: ${content} for effective user: ${effectiveUserId}`)
const memoryProcessor = new MemoryProcessor() const memoryProcessor = new MemoryProcessor()
const relevantMemories = await memoryProcessor.searchRelevantMemories( const relevantMemories = await memoryProcessor.searchRelevantMemories(
content, content,
@@ -557,7 +560,9 @@ async function processConversationMemory(messages: Message[], assistant: Assista
// return // return
// } // }
const currentUserId = selectCurrentUserId(store.getState()) const globalUserId = selectCurrentUserId(store.getState())
const memoryService = MemoryService.getInstance()
const effectiveUserId = memoryService.getEffectiveUserId(assistant, globalUserId)
// Create updated memory config with resolved models // Create updated memory config with resolved models
const updatedMemoryConfig = { const updatedMemoryConfig = {
@@ -582,7 +587,7 @@ async function processConversationMemory(messages: Message[], assistant: Assista
const processorConfig = MemoryProcessor.getProcessorConfig( const processorConfig = MemoryProcessor.getProcessorConfig(
updatedMemoryConfig, updatedMemoryConfig,
assistant.id, assistant.id,
currentUserId, effectiveUserId,
lastUserMessage?.id lastUserMessage?.id
) )

View File

@@ -238,12 +238,10 @@ export class MemoryProcessor {
limit limit
}) })
logger.debug( logger.debug('Searching memories successful', { query, userId, assistantId, result })
`Searching memories with query: ${query} for user: ${userId} and assistant: ${assistantId} result: ${result}`
)
return result.results return result.results
} catch (error) { } catch (error) {
logger.error('Error searching memories:', error as Error) logger.error('Searching memories error:', { error })
return [] return []
} }
} }

View File

@@ -1,8 +1,10 @@
import { loggerService } from '@logger' import { loggerService } from '@logger'
import type { RootState } from '@renderer/store'
import store from '@renderer/store' import store from '@renderer/store'
import { selectMemoryConfig } from '@renderer/store/memory' import { selectCurrentUserId, selectMemoryConfig } from '@renderer/store/memory'
import { import {
AddMemoryOptions, AddMemoryOptions,
Assistant,
AssistantMessage, AssistantMessage,
MemoryHistoryItem, MemoryHistoryItem,
MemoryListOptions, MemoryListOptions,
@@ -10,6 +12,8 @@ import {
MemorySearchResult MemorySearchResult
} from '@types' } from '@types'
import { getAssistantById } from './AssistantService'
const logger = loggerService.withContext('MemoryService') const logger = loggerService.withContext('MemoryService')
// Main process SearchResult type (matches what the IPC actually returns) // Main process SearchResult type (matches what the IPC actually returns)
@@ -26,8 +30,10 @@ interface SearchResult {
class MemoryService { class MemoryService {
private static instance: MemoryService | null = null private static instance: MemoryService | null = null
private currentUserId: string = 'default-user' private currentUserId: string = 'default-user'
private getStateFunction: () => RootState
constructor() { constructor(getStateFunction: () => RootState = () => store.getState()) {
this.getStateFunction = getStateFunction
this.init() this.init()
} }
@@ -68,6 +74,37 @@ class MemoryService {
return this.currentUserId return this.currentUserId
} }
/**
* Gets the effective memory user ID for an assistant using dependency injection
* Falls back to global currentUserId when assistant has no specific memoryUserId
* @param assistant - The assistant object containing memoryUserId
* @param globalUserId - The global user ID to fall back to
* @returns The effective user ID to use for memory operations
*/
public getEffectiveUserId(assistant: Assistant, globalUserId: string): string {
return assistant.memoryUserId || globalUserId
}
/**
* Private helper to resolve user ID for context operations
* @param assistant - Optional assistant object to determine effective user ID
* @returns The resolved user ID to use for memory operations
*/
private resolveUserId(assistant?: Assistant): string {
let globalUserId = this.currentUserId
if (this.getStateFunction) {
try {
globalUserId = selectCurrentUserId(this.getStateFunction())
} catch (error) {
logger.warn('Failed to get state, falling back to internal currentUserId:', error as Error)
globalUserId = this.currentUserId
}
}
return assistant ? this.getEffectiveUserId(assistant, globalUserId) : globalUserId
}
/** /**
* Lists all stored memories * Lists all stored memories
* @param config - Optional configuration for filtering memories * @param config - Optional configuration for filtering memories
@@ -110,12 +147,32 @@ class MemoryService {
* @returns Promise resolving to search results of added memories * @returns Promise resolving to search results of added memories
*/ */
public async add(messages: string | AssistantMessage[], options: AddMemoryOptions): Promise<MemorySearchResult> { public async add(messages: string | AssistantMessage[], options: AddMemoryOptions): Promise<MemorySearchResult> {
options.userId = this.currentUserId const optionsWithUser = {
const result: SearchResult = await window.api.memory.add(messages, options) ...options,
// Convert SearchResult to MemorySearchResult for consistency userId: this.currentUserId
return { }
results: result.memories,
relations: [] try {
const result: SearchResult = await window.api.memory.add(messages, optionsWithUser)
// Handle error responses from main process
if (result.error) {
logger.error(`Memory service error: ${result.error}`)
throw new Error(result.error)
}
// Convert SearchResult to MemorySearchResult for consistency
return {
results: result.memories || [],
relations: []
}
} catch (error) {
logger.error('Failed to add memories:', error as Error)
// Return empty result on error to prevent UI crashes
return {
results: [],
relations: []
}
} }
} }
@@ -126,12 +183,42 @@ class MemoryService {
* @returns Promise resolving to search results matching the query * @returns Promise resolving to search results matching the query
*/ */
public async search(query: string, options: MemorySearchOptions): Promise<MemorySearchResult> { public async search(query: string, options: MemorySearchOptions): Promise<MemorySearchResult> {
options.userId = this.currentUserId const optionsWithUser = {
const result: SearchResult = await window.api.memory.search(query, options) ...options,
// Convert SearchResult to MemorySearchResult for consistency userId: this.currentUserId
return { }
results: result.memories,
relations: [] // If agentId is provided, resolve userId from assistant's memoryUserId
if (optionsWithUser.agentId) {
const assistant = getAssistantById(optionsWithUser.agentId)
if (assistant) {
optionsWithUser.userId = assistant.memoryUserId || this.currentUserId
}
}
logger.debug('Searching memories start with options', { query: query, options: optionsWithUser })
try {
const result: SearchResult = await window.api.memory.search(query, optionsWithUser)
// Handle error responses from main process
if (result.error) {
logger.error(`Memory service error: ${result.error}`)
throw new Error(result.error)
}
// Convert SearchResult to MemorySearchResult for consistency
return {
results: result.memories || [],
relations: []
}
} catch (error) {
logger.error('Failed to search memories:', error as Error)
// Return empty result on error to prevent UI crashes
return {
results: [],
relations: []
}
} }
} }
@@ -197,12 +284,13 @@ class MemoryService {
*/ */
public async updateConfig(): Promise<void> { public async updateConfig(): Promise<void> {
try { try {
if (!store || !store.getState) { if (!this.getStateFunction) {
logger.warn('Store not available, skipping memory config update') logger.warn('State function not available, skipping memory config update')
return return
} }
const memoryConfig = selectMemoryConfig(store.getState()) const state = this.getStateFunction()
const memoryConfig = selectMemoryConfig(state)
const embedderApiClient = memoryConfig.embedderApiClient const embedderApiClient = memoryConfig.embedderApiClient
const llmApiClient = memoryConfig.llmApiClient const llmApiClient = memoryConfig.llmApiClient
@@ -218,6 +306,138 @@ class MemoryService {
return return
} }
} }
// Enhanced methods with assistant context support
/**
* Lists stored memories with assistant context support
* Automatically resolves the effective user ID based on assistant's memoryUserId
* @param config - Configuration for filtering memories
* @param assistant - Optional assistant object to determine effective user ID
* @returns Promise resolving to search results containing filtered memories
*/
public async listWithContext(
config?: Omit<MemoryListOptions, 'userId'>,
assistant?: Assistant
): Promise<MemorySearchResult> {
const effectiveUserId = this.resolveUserId(assistant)
const configWithUser = {
...config,
userId: effectiveUserId
}
try {
const result: SearchResult = await window.api.memory.list(configWithUser)
// Handle error responses from main process
if (result.error) {
logger.error(`Memory service error: ${result.error}`)
throw new Error(result.error)
}
// Convert SearchResult to MemorySearchResult for consistency
return {
results: result.memories || [],
relations: []
}
} catch (error) {
logger.error('Failed to list memories with context:', error as Error)
// Return empty result on error to prevent UI crashes
return {
results: [],
relations: []
}
}
}
/**
* Adds new memory entries with assistant context support
* Automatically resolves the effective user ID based on assistant's memoryUserId
* @param messages - String content or array of assistant messages to store as memory
* @param options - Configuration options for adding memory (without userId)
* @param assistant - Optional assistant object to determine effective user ID
* @returns Promise resolving to search results of added memories
*/
public async addWithContext(
messages: string | AssistantMessage[],
options: Omit<AddMemoryOptions, 'userId'>,
assistant?: Assistant
): Promise<MemorySearchResult> {
const effectiveUserId = this.resolveUserId(assistant)
const optionsWithUser = {
...options,
userId: effectiveUserId
}
try {
const result: SearchResult = await window.api.memory.add(messages, optionsWithUser)
// Handle error responses from main process
if (result.error) {
logger.error(`Memory service error: ${result.error}`)
throw new Error(result.error)
}
// Convert SearchResult to MemorySearchResult for consistency
return {
results: result.memories || [],
relations: []
}
} catch (error) {
logger.error('Failed to add memories with context:', error as Error)
// Return empty result on error to prevent UI crashes
return {
results: [],
relations: []
}
}
}
/**
* Searches stored memories with assistant context support
* Automatically resolves the effective user ID based on assistant's memoryUserId
* @param query - Search query string to find relevant memories
* @param options - Configuration options for memory search (without userId)
* @param assistant - Optional assistant object to determine effective user ID
* @returns Promise resolving to search results matching the query
*/
public async searchWithContext(
query: string,
options: Omit<MemorySearchOptions, 'userId'>,
assistant?: Assistant
): Promise<MemorySearchResult> {
const effectiveUserId = this.resolveUserId(assistant)
const optionsWithUser = {
...options,
userId: effectiveUserId
}
try {
const result: SearchResult = await window.api.memory.search(query, optionsWithUser)
// Handle error responses from main process
if (result.error) {
logger.error(`Memory service error: ${result.error}`)
throw new Error(result.error)
}
// Convert SearchResult to MemorySearchResult for consistency
return {
results: result.memories || [],
relations: []
}
} catch (error) {
logger.error('Failed to search memories with context:', error as Error)
// Return empty result on error to prevent UI crashes
return {
results: [],
relations: []
}
}
}
} }
export default MemoryService export default MemoryService

View File

@@ -51,7 +51,7 @@ export function createStreamProcessor(callbacks: StreamProcessorCallbacks = {})
return (chunk: Chunk) => { return (chunk: Chunk) => {
try { try {
const data = chunk const data = chunk
logger.debug('data: ', data) logger.silly('data: ', data)
switch (data.type) { switch (data.type) {
case ChunkType.BLOCK_COMPLETE: { case ChunkType.BLOCK_COMPLETE: {
if (callbacks.onComplete) callbacks.onComplete(AssistantMessageStatus.SUCCESS, data?.response) if (callbacks.onComplete) callbacks.onComplete(AssistantMessageStatus.SUCCESS, data?.response)

View File

@@ -32,6 +32,7 @@ export type Assistant = {
regularPhrases?: QuickPhrase[] // Added for regular phrase regularPhrases?: QuickPhrase[] // Added for regular phrase
tags?: string[] // 助手标签 tags?: string[] // 助手标签
enableMemory?: boolean enableMemory?: boolean
memoryUserId?: string // 绑定的记忆用户ID当未指定时使用全局记忆用户
} }
export type TranslateAssistant = Assistant & { export type TranslateAssistant = Assistant & {