diff --git a/client/src/components/Chat/Messages/Content/RetrievalCall.tsx b/client/src/components/Chat/Messages/Content/RetrievalCall.tsx index ea596b97fe..93c66f52d1 100644 --- a/client/src/components/Chat/Messages/Content/RetrievalCall.tsx +++ b/client/src/components/Chat/Messages/Content/RetrievalCall.tsx @@ -7,6 +7,7 @@ import type { TAttachment, TFile, PartMetadata } from 'librechat-data-provider'; import { useLocalize, useProgress, useExpandCollapse } from '~/hooks'; import { ToolIcon, OutputRenderer, isError } from './ToolOutput'; import { resolveToolCallPhase } from '~/utils/toolCallPhase'; +import { toolPanelSpacingClassName } from './disclosure'; import FilePreviewDialog from './FilePreviewDialog'; import { sortPagesByRelevance, cn } from '~/utils'; import { useToolCallIntent } from './Parts/intent'; @@ -380,8 +381,6 @@ export default function RetrievalCall({ }); const hasOutput = !!output && !isError(output); const autoExpand = useRecoilValue(store.autoExpandTools); - const [showOutput, setShowOutput] = useState(() => autoExpand && hasOutput); - const { style: expandStyle, ref: expandRef } = useExpandCollapse(showOutput); const fileSources = useMemo(() => extractFileSources(attachments), [attachments]); const parsedResults = useMemo( @@ -402,6 +401,9 @@ export default function RetrievalCall({ ); const hasResults = displayResults.length > 0; + const hasExpandableContent = phase !== 'failed' && hasResults; + const [showOutput, setShowOutput] = useState(() => autoExpand && hasExpandableContent); + const { style: expandStyle, ref: expandRef } = useExpandCollapse(showOutput); const [previewIndex, setPreviewIndex] = useState(null); @@ -437,10 +439,14 @@ export default function RetrievalCall({ }, [displayResults, previewIndex]); useEffect(() => { - if (autoExpand && hasOutput) { + if (!hasExpandableContent) { + setShowOutput(false); + return; + } + if (autoExpand) { setShowOutput(true); } - }, [autoExpand, hasOutput]); + }, [autoExpand, hasExpandableContent]); const handleToggleOutput = useCallback(() => { setShowOutput((prev) => { @@ -453,7 +459,7 @@ export default function RetrievalCall({ }, [onExpand]); return ( -
+
{(() => { if (phase === 'running') { @@ -474,7 +480,7 @@ export default function RetrievalCall({
} - hasInput={hasOutput} + hasInput={hasExpandableContent} isExpanded={showOutput} />
- {hasOutput && hasResults && ( -
+ {hasExpandableContent && ( +
{displayResults.map((item, i) => { return (
({ jest.mock('~/utils', () => ({ cn: (...classes: (string | boolean | undefined)[]) => classes.filter(Boolean).join(' '), + sortPagesByRelevance: (pages: number[]) => pages, logger: { error: jest.fn(), debug: jest.fn() }, })); @@ -168,6 +169,51 @@ describe('RetrievalCall - LGCY-02: Collapsible output panel', () => { expect(screen.queryByText('file results here')).not.toBeInTheDocument(); }); + it('does not offer expansion when output has no renderable results', () => { + renderRetrievalCall({ + output: 'Search completed without matches', + initialProgress: 1, + isSubmitting: false, + }); + + const progressText = screen.getByTestId('progress-text'); + expect(progressText).toHaveAttribute('data-has-input', 'false'); + + fireEvent.click(progressText); + expect(progressText).toHaveAttribute('data-expanded', 'false'); + }); + + it('offers attachment results even when the tool has no text output', () => { + renderRetrievalCall({ + initialProgress: 1, + isSubmitting: false, + attachments: [ + { + type: 'file_search', + toolCallId: 'call-1', + file_search: { + sources: [ + { + fileId: 'file-123', + fileName: 'notes.txt', + relevance: 0.8, + content: 'Attachment result content', + pages: [], + pageRelevance: {}, + }, + ], + }, + }, + ] as unknown as TAttachment[], + }); + + const progressText = screen.getByTestId('progress-text'); + expect(progressText).toHaveAttribute('data-has-input', 'true'); + + fireEvent.click(progressText); + expect(screen.getByText('Attachment result content')).toBeInTheDocument(); + }); + it('does not show panel when output contains error', () => { renderRetrievalCall({ output: 'error processing tool',