From 475dc8e8b1b2a6a00a514b72518d1d2741d4ade1 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 31 Jul 2026 02:26:06 +0200 Subject: [PATCH] refactor(client): align memory updates with the standard tool row Rebuild MemoryArtifacts on the shared disclosure row (useExpandCollapse grid animation, hover-reveal chevron, h-5 header) in place of its bespoke ResizeObserver height animation, and simplify the expanded panel: section headings dropped in favor of bold memory keys, a single p-3 inset instead of double padding, and the now-unused heading translation keys removed. --- .../Chat/Messages/Content/MemoryArtifacts.tsx | 175 +++++++----------- .../Chat/Messages/Content/MemoryInfo.tsx | 52 ++---- .../__tests__/MemoryArtifacts.test.tsx | 34 +++- .../Content/__tests__/MemoryInfo.test.tsx | 14 +- client/src/locales/en/translation.json | 2 - 5 files changed, 120 insertions(+), 157 deletions(-) diff --git a/client/src/components/Chat/Messages/Content/MemoryArtifacts.tsx b/client/src/components/Chat/Messages/Content/MemoryArtifacts.tsx index 6209414221..6c88572ec5 100644 --- a/client/src/components/Chat/Messages/Content/MemoryArtifacts.tsx +++ b/client/src/components/Chat/Messages/Content/MemoryArtifacts.tsx @@ -1,17 +1,17 @@ +import { useId, useMemo, useState } from 'react'; +import { ChevronDown } from 'lucide-react'; import { Tools } from 'librechat-data-provider'; -import { useState, useRef, useMemo, useLayoutEffect, useEffect } from 'react'; import type { MemoryArtifact, TAttachment } from 'librechat-data-provider'; +import { disclosureChevronClassName, toolPanelSpacingClassName } from './disclosure'; +import { useExpandCollapse, useLocalize } from '~/hooks'; import MemoryInfo from './MemoryInfo'; -import { useLocalize } from '~/hooks'; import { cn } from '~/utils'; export default function MemoryArtifacts({ attachments }: { attachments?: TAttachment[] }) { + const contentId = useId(); const localize = useLocalize(); const [showInfo, setShowInfo] = useState(false); - const contentRef = useRef(null); - const [contentHeight, setContentHeight] = useState(0); - const [isAnimating, setIsAnimating] = useState(false); - const prevShowInfoRef = useRef(showInfo); + const { style: expandStyle, ref: expandRef } = useExpandCollapse(showInfo); const { hasErrors, memoryArtifacts } = useMemo(() => { let hasErrors = false; @@ -34,122 +34,75 @@ export default function MemoryArtifacts({ attachments }: { attachments?: TAttach return { hasErrors, memoryArtifacts: result }; }, [attachments]); - useLayoutEffect(() => { - if (showInfo !== prevShowInfoRef.current) { - prevShowInfoRef.current = showInfo; - setIsAnimating(true); - - if (showInfo && contentRef.current) { - requestAnimationFrame(() => { - if (contentRef.current) { - const height = contentRef.current.scrollHeight; - setContentHeight(height + 4); - } - }); - } else { - setContentHeight(0); - } - - const timer = setTimeout(() => { - setIsAnimating(false); - }, 400); - - return () => clearTimeout(timer); - } - }, [showInfo]); - - useEffect(() => { - if (!contentRef.current) { - return; - } - const resizeObserver = new ResizeObserver((entries) => { - if (showInfo && !isAnimating) { - for (const entry of entries) { - if (entry.target === contentRef.current) { - setContentHeight(entry.contentRect.height + 4); - } - } - } - }); - resizeObserver.observe(contentRef.current); - return () => { - resizeObserver.disconnect(); - }; - }, [showInfo, isAnimating]); - if (!memoryArtifacts || memoryArtifacts.length === 0) { return null; } return ( <> -
-
- -
+ +
-
-
+
+
{showInfo && }
diff --git a/client/src/components/Chat/Messages/Content/MemoryInfo.tsx b/client/src/components/Chat/Messages/Content/MemoryInfo.tsx index d00fac68c6..8910d02a56 100644 --- a/client/src/components/Chat/Messages/Content/MemoryInfo.tsx +++ b/client/src/components/Chat/Messages/Content/MemoryInfo.tsx @@ -1,5 +1,5 @@ -import type { MemoryArtifact } from 'librechat-data-provider'; import { useMemo } from 'react'; +import type { MemoryArtifact } from 'librechat-data-provider'; import { useLocalize } from '~/hooks'; export default function MemoryInfo({ memoryArtifacts }: { memoryArtifacts: MemoryArtifact[] }) { @@ -46,44 +46,32 @@ export default function MemoryInfo({ memoryArtifacts }: { memoryArtifacts: Memor } return ( -
+
{updatedMemories.length > 0 && ( -
-

- {localize('com_ui_memory_updated_items')} -

-
- {updatedMemories.map((artifact) => ( -
-
- {artifact.key} -
-
- {artifact.value} -
+
+ {updatedMemories.map((artifact) => ( +
+
+ {artifact.key}
- ))} -
+
{artifact.value}
+
+ ))}
)} {deletedMemories.length > 0 && ( -
-

- {localize('com_ui_memory_deleted_items')} -

-
- {deletedMemories.map((artifact) => ( -
-
- {artifact.key} -
-
- {localize('com_ui_memory_deleted')} -
+
+ {deletedMemories.map((artifact) => ( +
+
+ {artifact.key}
- ))} -
+
+ {localize('com_ui_memory_deleted')} +
+
+ ))}
)} diff --git a/client/src/components/Chat/Messages/Content/__tests__/MemoryArtifacts.test.tsx b/client/src/components/Chat/Messages/Content/__tests__/MemoryArtifacts.test.tsx index 875750a54f..c8ed3ab3d9 100644 --- a/client/src/components/Chat/Messages/Content/__tests__/MemoryArtifacts.test.tsx +++ b/client/src/components/Chat/Messages/Content/__tests__/MemoryArtifacts.test.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import MemoryArtifacts from '../MemoryArtifacts'; -import type { TAttachment, MemoryArtifact } from 'librechat-data-provider'; import { Tools } from 'librechat-data-provider'; +import type { TAttachment, MemoryArtifact } from 'librechat-data-provider'; +import MemoryArtifacts from '../MemoryArtifacts'; // Mock the localize hook jest.mock('~/hooks', () => ({ @@ -14,6 +14,14 @@ jest.mock('~/hooks', () => ({ }; return translations[key] || key; }, + useExpandCollapse: (isExpanded: boolean) => ({ + style: { + display: 'grid', + gridTemplateRows: isExpanded ? '1fr' : '0fr', + opacity: isExpanded ? 1 : 0, + }, + ref: { current: null }, + }), })); // Mock the MemoryInfo component @@ -69,8 +77,7 @@ describe('MemoryArtifacts', () => { render(); const button = screen.getByRole('button'); - expect(button).toHaveClass('text-text-secondary-alt'); - expect(button).toHaveClass('hover:text-text-primary'); + expect(button).toHaveClass('text-text-secondary'); expect(button).not.toHaveClass('text-red-500'); }); @@ -127,6 +134,25 @@ describe('MemoryArtifacts', () => { }); describe('Collapse/Expand Functionality', () => { + test('uses the standard tool row and expanded panel styling', () => { + const attachments = [createMemoryAttachment('update', 'memory1')]; + + render(); + + const button = screen.getByRole('button'); + expect(button.parentElement).toHaveClass('my-1', 'h-5'); + expect(button).toHaveClass('group/disclosure'); + + fireEvent.click(button); + + expect(screen.getByTestId('memory-info').parentElement).toHaveClass( + 'mb-2', + 'mt-0', + 'rounded-lg', + 'bg-surface-secondary', + ); + }); + test('toggles memory info visibility on button click', () => { const attachments = [createMemoryAttachment('update', 'memory1')]; diff --git a/client/src/components/Chat/Messages/Content/__tests__/MemoryInfo.test.tsx b/client/src/components/Chat/Messages/Content/__tests__/MemoryInfo.test.tsx index ba8247484c..04e707b7f7 100644 --- a/client/src/components/Chat/Messages/Content/__tests__/MemoryInfo.test.tsx +++ b/client/src/components/Chat/Messages/Content/__tests__/MemoryInfo.test.tsx @@ -1,15 +1,13 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import MemoryInfo from '../MemoryInfo'; import type { MemoryArtifact } from 'librechat-data-provider'; +import MemoryInfo from '../MemoryInfo'; // Mock the localize hook jest.mock('~/hooks', () => ({ useLocalize: () => (key: string, params?: Record) => { const translations: Record = { - com_ui_memory_updated_items: 'Updated Memories', - com_ui_memory_deleted_items: 'Deleted Memories', com_ui_memory_already_exceeded: `Memory storage already full - exceeded by ${params?.tokens || 0} tokens. Delete existing memories before adding new ones.`, com_ui_memory_would_exceed: `Cannot save - would exceed limit by ${params?.tokens || 0} tokens. Delete existing memories to make space.`, com_ui_memory_deleted: 'This memory has been deleted', @@ -140,9 +138,9 @@ describe('MemoryInfo', () => { render(); - // Check all sections are present - expect(screen.getByText('Updated Memories')).toBeInTheDocument(); - expect(screen.getByText('Deleted Memories')).toBeInTheDocument(); + // Entries render without section headings; only the error section keeps one + expect(screen.queryByText('Updated Memories')).not.toBeInTheDocument(); + expect(screen.queryByText('Deleted Memories')).not.toBeInTheDocument(); expect(screen.getByText('Memory Storage Full')).toBeInTheDocument(); // Check content @@ -244,7 +242,7 @@ describe('MemoryInfo', () => { render(); - expect(screen.getByText('Updated Memories')).toBeInTheDocument(); + expect(screen.queryByText('Updated Memories')).not.toBeInTheDocument(); expect(screen.getByText('preferences')).toBeInTheDocument(); expect(screen.getByText('User prefers dark mode')).toBeInTheDocument(); expect(screen.getByText('location')).toBeInTheDocument(); @@ -259,7 +257,7 @@ describe('MemoryInfo', () => { render(); - expect(screen.getByText('Deleted Memories')).toBeInTheDocument(); + expect(screen.queryByText('Deleted Memories')).not.toBeInTheDocument(); expect(screen.getByText('old_preference')).toBeInTheDocument(); expect(screen.getByText('outdated_info')).toBeInTheDocument(); }); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index bde38d04f2..f57e30e441 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -1450,14 +1450,12 @@ "com_ui_memory_already_exceeded": "Memory storage already full - exceeded by {{tokens}} tokens. Delete existing memories before adding new ones.", "com_ui_memory_created": "Memory created successfully", "com_ui_memory_deleted": "Memory deleted", - "com_ui_memory_deleted_items": "Deleted Memories", "com_ui_memory_error": "Memory Error", "com_ui_memory_key_exists": "A memory with this key already exists. Please use a different key.", "com_ui_memory_key_hint": "Use lowercase letters and underscores only", "com_ui_memory_key_validation": "Memory key must only contain lowercase letters and underscores.", "com_ui_memory_storage_full": "Memory Storage Full", "com_ui_memory_updated": "Updated saved memory", - "com_ui_memory_updated_items": "Updated Memories", "com_ui_memory_would_exceed": "Cannot save - would exceed limit by {{tokens}} tokens. Delete existing memories to make space.", "com_ui_mention": "Mention an endpoint, assistant, or preset to quickly switch to it", "com_ui_mermaid": "mermaid",