From ac45132743b061c5a3269db42e7fa501ecb14d2e Mon Sep 17 00:00:00 2001 From: Lia Date: Mon, 12 Oct 2026 13:29:35 +0000 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=AA=A1=20style:=20Separate=20Attachme?= =?UTF-8?q?nts=20from=20Message=20Bubbles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Chat/Input/Files/FileContainer.tsx | 2 +- .../Chat/Messages/Content/Container.tsx | 13 +- .../Chat/Messages/Content/EditMessage.tsx | 2 +- .../Chat/Messages/Content/Files.tsx | 3 + .../Messages/Content/__tests__/Files.test.tsx | 34 +++++- .../components/Chat/Messages/MessageParts.tsx | 1 + .../components/Chat/Messages/PendingTurn.tsx | 1 + .../Chat/Messages/SearchMessage.tsx | 1 + .../Chat/Messages/ui/MessageRender.tsx | 1 + .../Chat/Messages/ui/MessageRow.tsx | 61 +++++++--- .../Messages/ui/__tests__/MessageRow.spec.tsx | 65 ++++++++++ client/src/components/Share/Message.tsx | 1 + .../scenarios/message-attachments.spec.ts | 112 ++++++++++++++++++ 13 files changed, 261 insertions(+), 36 deletions(-) create mode 100644 e2e/specs/mock/scenarios/message-attachments.spec.ts diff --git a/client/src/components/Chat/Input/Files/FileContainer.tsx b/client/src/components/Chat/Input/Files/FileContainer.tsx index f146277e051..2386394a132 100644 --- a/client/src/components/Chat/Input/Files/FileContainer.tsx +++ b/client/src/components/Chat/Input/Files/FileContainer.tsx @@ -80,7 +80,7 @@ const FileContainer = ({ 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-text-primary'; const details = ( -
+
diff --git a/client/src/components/Chat/Messages/Content/Container.tsx b/client/src/components/Chat/Messages/Content/Container.tsx index 945d8ca1d51..878a4d4346d 100644 --- a/client/src/components/Chat/Messages/Content/Container.tsx +++ b/client/src/components/Chat/Messages/Content/Container.tsx @@ -1,18 +1,8 @@ import { TMessage } from 'librechat-data-provider'; import MessageQuotes from './MessageQuotes'; import SkillPills from './SkillPills'; -import Files from './Files'; -const Container = ({ - children, - message, - hideFiles = false, -}: { - children: React.ReactNode; - message?: TMessage; - /** The message editor renders the files itself, as chips that can be removed. */ - hideFiles?: boolean; -}) => ( +const Container = ({ children, message }: { children: React.ReactNode; message?: TMessage }) => (
- {!hideFiles && } diff --git a/client/src/components/Chat/Messages/Content/EditMessage.tsx b/client/src/components/Chat/Messages/Content/EditMessage.tsx index 1c9c397f5ea..e46a81fc90e 100644 --- a/client/src/components/Chat/Messages/Content/EditMessage.tsx +++ b/client/src/components/Chat/Messages/Content/EditMessage.tsx @@ -250,7 +250,7 @@ const EditMessage = ({ ); return ( - +
{ setSelectedFile(file)} /> ))} @@ -44,6 +46,7 @@ const Files = ({ message }: { message?: TMessage }) => { ({ jest.mock( '~/components/Chat/Input/Files/FileContainer', () => - ({ onClick }: { onClick: () => void }) => , + ({ onClick, buttonClassName }: { onClick: () => void; buttonClassName?: string }) => ( + + ), +); +jest.mock( + '../Image', + () => + ({ alignRight, className }: { alignRight?: boolean; className?: string }) => ( +
+ image preview +
+ ), ); -jest.mock('../Image', () => ({ alignRight }: { alignRight?: boolean }) => ( -
- image preview -
-)); jest.mock( '../FilePreviewDialog', () => @@ -40,6 +52,16 @@ it('exposes extracted image text without changing ordinary image previews', () = ); expect(screen.getAllByText('image preview')).toHaveLength(1); expect(screen.getByTestId('image-preview')).toHaveAttribute('data-aligned-right', 'true'); + expect(screen.getByText('file action')).toHaveClass( + 'border-surface-user-message', + 'max-w-full', + 'bg-transparent', + ); + expect(screen.getByTestId('image-preview')).toHaveClass( + 'border-surface-user-message', + 'mt-0', + 'shadow-none', + ); fireEvent.click(screen.getByText('file action')); expect(screen.getByText('preview: text')).toBeInTheDocument(); }); diff --git a/client/src/components/Chat/Messages/MessageParts.tsx b/client/src/components/Chat/Messages/MessageParts.tsx index 2c6b0315018..f5a8f782a60 100644 --- a/client/src/components/Chat/Messages/MessageParts.tsx +++ b/client/src/components/Chat/Messages/MessageParts.tsx @@ -116,6 +116,7 @@ function MessageParts(props: TMessageProps) { >
} label={label} timestamp={reveal.revealedAt} diff --git a/client/src/components/Chat/Messages/SearchMessage.tsx b/client/src/components/Chat/Messages/SearchMessage.tsx index 13f0bbf05a7..f0bda98fda4 100644 --- a/client/src/components/Chat/Messages/SearchMessage.tsx +++ b/client/src/components/Chat/Messages/SearchMessage.tsx @@ -121,6 +121,7 @@ function SearchMessage({ message }: Pick) { className={cn('border-border-light mx-auto border-b px-4 py-4', getMessageRowWidthClass())} > } label={messageLabel} diff --git a/client/src/components/Chat/Messages/ui/MessageRender.tsx b/client/src/components/Chat/Messages/ui/MessageRender.tsx index 077a0724394..7777666ae50 100644 --- a/client/src/components/Chat/Messages/ui/MessageRender.tsx +++ b/client/src/components/Chat/Messages/ui/MessageRender.tsx @@ -168,6 +168,7 @@ const MessageRender = memo(function MessageRender({ } label={messageLabel ?? ''} diff --git a/client/src/components/Chat/Messages/ui/MessageRow.tsx b/client/src/components/Chat/Messages/ui/MessageRow.tsx index b6885eda2f3..a71d43344ec 100644 --- a/client/src/components/Chat/Messages/ui/MessageRow.tsx +++ b/client/src/components/Chat/Messages/ui/MessageRow.tsx @@ -1,11 +1,15 @@ import { Zap } from 'lucide-react'; +import { ContentTypes } from 'librechat-data-provider'; +import type { TMessage } from 'librechat-data-provider'; import type { ReactNode } from 'react'; import MessageTimestamp from './MessageTimestamp'; import HeaderLabel from './HeaderLabel'; +import Files from '../Content/Files'; import { cn } from '~/utils'; type MessageRowProps = { id?: string; + message?: TMessage; label: string; hoverLabel?: string | null; icon: ReactNode; @@ -88,6 +92,7 @@ export function getMessageRowWidthClass({ export default function MessageRow({ id, + message, icon, label, hoverLabel, @@ -110,6 +115,20 @@ export default function MessageRow({ const widthClass = getMessageRowWidthClass({ fullWidth, hasParallelContent }); const isSystem = systemLabel != null && systemLabel !== ''; const isUserSide = isCreatedByUser && !isSystem; + const hasFiles = isUserSide && !isEditing && (message?.files?.length ?? 0) > 0; + const hasBody = + !hasFiles || + Boolean( + message?.text?.trim() || + message?.content?.some( + (part) => part != null && (part.type !== ContentTypes.TEXT || part.text?.trim()), + ) || + message?.error || + message?.unfinished || + message?.quotes?.length || + message?.alwaysAppliedSkills?.length || + message?.manualSkills?.length, + ); return (
-
- {children} -
+ {hasFiles && ( +
+ +
+ )} + {hasBody && ( +
+ {children} +
+ )}
{footer}
diff --git a/client/src/components/Chat/Messages/ui/__tests__/MessageRow.spec.tsx b/client/src/components/Chat/Messages/ui/__tests__/MessageRow.spec.tsx index 2c95e2e2b45..74376b5af3a 100644 --- a/client/src/components/Chat/Messages/ui/__tests__/MessageRow.spec.tsx +++ b/client/src/components/Chat/Messages/ui/__tests__/MessageRow.spec.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import type { TMessage } from 'librechat-data-provider'; import MessageRow from '../MessageRow'; jest.mock('../MessageTimestamp', () => ({ @@ -8,10 +9,24 @@ jest.mock('../MessageTimestamp', () => ({ ), })); +jest.mock('../../Content/Files', () => ({ + __esModule: true, + default: () =>