diff --git a/api/server/routes/memories.js b/api/server/routes/memories.js index fe520de000..a136bc8e61 100644 --- a/api/server/routes/memories.js +++ b/api/server/routes/memories.js @@ -172,40 +172,68 @@ router.patch('/preferences', checkMemoryOptOut, async (req, res) => { /** * PATCH /memories/:key * Updates the value of an existing memory entry for the authenticated user. - * Body: { value: string } + * Body: { key?: string, value: string } * Returns 200 and { updated: true, memory: } when successful. */ router.patch('/:key', checkMemoryUpdate, async (req, res) => { - const { key } = req.params; - const { value } = req.body || {}; + const { key: urlKey } = req.params; + const { key: bodyKey, value } = req.body || {}; if (typeof value !== 'string' || value.trim() === '') { return res.status(400).json({ error: 'Value is required and must be a non-empty string.' }); } + // Use the key from the body if provided, otherwise use the key from the URL + const newKey = bodyKey || urlKey; + try { const tokenCount = Tokenizer.getTokenCount(value, 'o200k_base'); const memories = await getAllUserMemories(req.user.id); - const existingMemory = memories.find((m) => m.key === key); + const existingMemory = memories.find((m) => m.key === urlKey); if (!existingMemory) { return res.status(404).json({ error: 'Memory not found.' }); } - const result = await setMemory({ - userId: req.user.id, - key, - value, - tokenCount, - }); + // If the key is changing, we need to handle it specially + if (newKey !== urlKey) { + const keyExists = memories.find((m) => m.key === newKey); + if (keyExists) { + return res.status(409).json({ error: 'Memory with this key already exists.' }); + } - if (!result.ok) { - return res.status(500).json({ error: 'Failed to update memory.' }); + const createResult = await createMemory({ + userId: req.user.id, + key: newKey, + value, + tokenCount, + }); + + if (!createResult.ok) { + return res.status(500).json({ error: 'Failed to create new memory.' }); + } + + const deleteResult = await deleteMemory({ userId: req.user.id, key: urlKey }); + if (!deleteResult.ok) { + return res.status(500).json({ error: 'Failed to delete old memory.' }); + } + } else { + // Key is not changing, just update the value + const result = await setMemory({ + userId: req.user.id, + key: newKey, + value, + tokenCount, + }); + + if (!result.ok) { + return res.status(500).json({ error: 'Failed to update memory.' }); + } } const updatedMemories = await getAllUserMemories(req.user.id); - const updatedMemory = updatedMemories.find((m) => m.key === key); + const updatedMemory = updatedMemories.find((m) => m.key === newKey); res.json({ updated: true, memory: updatedMemory }); } catch (error) { diff --git a/client/src/components/SidePanel/Memories/MemoryCreateDialog.tsx b/client/src/components/SidePanel/Memories/MemoryCreateDialog.tsx index 1670ba6f60..3f916872cb 100644 --- a/client/src/components/SidePanel/Memories/MemoryCreateDialog.tsx +++ b/client/src/components/SidePanel/Memories/MemoryCreateDialog.tsx @@ -52,6 +52,10 @@ export default function MemoryCreateDialog({ if (axiosError.response?.status === 409 || errorMessage.includes('already exists')) { errorMessage = localize('com_ui_memory_key_exists'); } + // Check for key validation error (lowercase and underscores only) + else if (errorMessage.includes('lowercase letters and underscores')) { + errorMessage = localize('com_ui_memory_key_validation'); + } } } else if (error.message) { errorMessage = error.message; diff --git a/client/src/components/SidePanel/Memories/MemoryEditDialog.tsx b/client/src/components/SidePanel/Memories/MemoryEditDialog.tsx index db6a0ab68e..6793bf3d6b 100644 --- a/client/src/components/SidePanel/Memories/MemoryEditDialog.tsx +++ b/client/src/components/SidePanel/Memories/MemoryEditDialog.tsx @@ -44,9 +44,29 @@ export default function MemoryEditDialog({ status: 'success', }); }, - onError: () => { + onError: (error: Error) => { + let errorMessage = localize('com_ui_error'); + + if (error && typeof error === 'object' && 'response' in error) { + const axiosError = error as any; + if (axiosError.response?.data?.error) { + errorMessage = axiosError.response.data.error; + + // Check for duplicate key error + if (axiosError.response?.status === 409 || errorMessage.includes('already exists')) { + errorMessage = localize('com_ui_memory_key_exists'); + } + // Check for key validation error (lowercase and underscores only) + else if (errorMessage.includes('lowercase letters and underscores')) { + errorMessage = localize('com_ui_memory_key_validation'); + } + } + } else if (error.message) { + errorMessage = error.message; + } + showToast({ - message: localize('com_ui_error'), + message: errorMessage, status: 'error', }); }, diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index de3721c779..0288678e05 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -856,6 +856,7 @@ "com_ui_memory_deleted": "Memory deleted", "com_ui_memory_deleted_items": "Deleted Memories", "com_ui_memory_key_exists": "A memory with this key already exists. Please use a different key.", + "com_ui_memory_key_validation": "Memory key must only contain lowercase letters and underscores.", "com_ui_memory_updated": "Updated saved memory", "com_ui_memory_updated_items": "Updated Memories", "com_ui_mention": "Mention an endpoint, assistant, or preset to quickly switch to it",