diff --git a/client/src/components/Chat/Messages/Content/Part.tsx b/client/src/components/Chat/Messages/Content/Part.tsx index 56e2b22280..342d5bfe2a 100644 --- a/client/src/components/Chat/Messages/Content/Part.tsx +++ b/client/src/components/Chat/Messages/Content/Part.tsx @@ -372,6 +372,7 @@ const Part = memo(function Part({ isSubmitting={isSubmitting} runStepStatus={toolCall.runStepStatus} attachments={attachments} + hideAttachments={hideAttachments} isLast={isLast} onExpand={onToolExpand} /> diff --git a/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx b/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx index d4c661677f..6e982b66c6 100644 --- a/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx +++ b/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx @@ -24,6 +24,7 @@ import { isError, StackedToolIcons } from './ToolOutput'; import { isBashProgrammaticToolCall } from './routing'; import { ASK_USER_QUESTION } from '~/utils/approval'; import { useMCPIconMap, useMCPServerNames } from '~/hooks/MCP'; +import SearchVerticals from './verticals'; import { ROW_GLYPH_SLOT } from './rows'; import store from '~/store'; @@ -565,7 +566,10 @@ export default function ToolCallGroup({ )} {groupAttachments && groupAttachments.length > 0 && ( - + <> + + + )} ); diff --git a/client/src/components/Chat/Messages/Content/WebSearch.tsx b/client/src/components/Chat/Messages/Content/WebSearch.tsx index cf8f0883c0..6f9be452f7 100644 --- a/client/src/components/Chat/Messages/Content/WebSearch.tsx +++ b/client/src/components/Chat/Messages/Content/WebSearch.tsx @@ -1,7 +1,7 @@ import { useMemo, useState, useEffect } from 'react'; import { useRecoilValue } from 'recoil'; import { Tools } from 'librechat-data-provider'; -import { Globe, ChevronDown, Info, Star, MapPin } from 'lucide-react'; +import { Globe, ChevronDown, Info } from 'lucide-react'; import { HoverCard, HoverCardTrigger, HoverCardPortal, HoverCardContent } from '@librechat/client'; import type { TAttachment, @@ -9,9 +9,6 @@ import type { SearchResultData, PartMetadata, AnswerBoxResult, - ShoppingResult, - ImageResult, - PlaceResult, } from 'librechat-data-provider'; import { disclosureChevronClassName, toolPanelSpacingClassName } from './disclosure'; import { FaviconImage, getCleanDomain } from '~/components/Web/SourceHovercard'; @@ -20,6 +17,7 @@ import { StackedFavicons } from '~/components/Web/Sources'; import parseJsonField from './Parts/parseJsonField'; import { useToolCallIntent } from './Parts/intent'; import { useSearchContext } from '~/Providers'; +import SearchVerticals from './verticals'; import { ROW_GLYPH_SLOT } from './rows'; import cn from '~/utils/cn'; import store from '~/store'; @@ -69,133 +67,6 @@ function getUniqueDomainSources(sources: ValidSource[], max: number): ValidSourc return result; } -const MAX_VERTICAL_ITEMS = 8; -const MAX_PLACES = 5; - -function RatingBadge({ rating, ratingCount }: { rating?: number; ratingCount?: number }) { - if (rating == null) { - return null; - } - return ( - - - ); -} - -function ImageStrip({ images, label }: { images: ImageResult[]; label: string }) { - return ( -
    - {images.slice(0, MAX_VERTICAL_ITEMS).map((image, i) => { - const href = image.link || image.googleUrl || image.imageUrl; - const ratio = - image.thumbnailWidth && image.thumbnailHeight - ? `${image.thumbnailWidth} / ${image.thumbnailHeight}` - : '1 / 1'; - return ( -
  • - - {image.title - -
  • - ); - })} -
- ); -} - -function ShoppingStrip({ items, label }: { items: ShoppingResult[]; label: string }) { - return ( - - ); -} - -function PlaceList({ places, label }: { places: PlaceResult[]; label: string }) { - return ( -
    - {places.slice(0, MAX_PLACES).map((place, i) => ( -
  • 0 && 'border-t border-border-light', - )} - > -
  • - ))} -
- ); -} - function SourceFaviconStack({ sources }: { sources: ValidSource[] }) { const visible = getUniqueDomainSources(sources, MAX_VISIBLE_FAVICONS); return ( @@ -224,6 +95,7 @@ export default function WebSearch({ args, output, attachments, + hideAttachments = false, onExpand, runStepStatus, }: { @@ -233,6 +105,7 @@ export default function WebSearch({ output?: string | null; initialProgress: number; attachments?: TAttachment[]; + hideAttachments?: boolean; onExpand?: () => void; runStepStatus?: PartMetadata['runStepStatus']; }) { @@ -324,24 +197,6 @@ export default function WebSearch({ return undefined; }, [attachments]); - /** Provider verticals (Serper only): image/shopping/place results merged - * across this call's attachments. Attachment-only like the answer box. */ - const verticals = useMemo(() => { - const images: ImageResult[] = []; - const shopping: ShoppingResult[] = []; - const places: PlaceResult[] = []; - for (const att of attachments ?? []) { - const data = att.type === Tools.web_search ? att[Tools.web_search] : undefined; - if (!data) { - continue; - } - images.push(...(data.images ?? [])); - shopping.push(...(data.shopping ?? [])); - places.push(...(data.places ?? [])); - } - return { images, shopping, places }; - }, [attachments]); - // Show favicons from the raw SERP results immediately rather than waiting for // each source to flip to `processed`; the agents scrape barrier would otherwise // freeze the stack on "Searching the web" for the slowest scrape's duration. @@ -403,9 +258,6 @@ export default function WebSearch({ if (complete) { const hasSourceData = sourceCount > 0; - const hasVerticals = - verticals.images.length > 0 || verticals.shopping.length > 0 || verticals.places.length > 0; - const hasExpandable = hasSourceData || hasVerticals; const completedText = intent ?? localize('com_ui_web_searched'); const query = parseJsonField(args, 'query'); const hasDetails = !!query || !!answerBox; @@ -420,13 +272,13 @@ export default function WebSearch({ type="button" className={cn( 'tool-status-text group/disclosure flex h-5 min-w-0 items-center gap-2 rounded-full', - hasExpandable + hasSourceData ? 'text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-heavy' : 'pointer-events-none text-text-secondary', )} - disabled={!hasExpandable} - onClick={hasExpandable ? handleToggleSources : undefined} - aria-expanded={hasExpandable ? showSourceList : undefined} + disabled={!hasSourceData} + onClick={hasSourceData ? handleToggleSources : undefined} + aria-expanded={hasSourceData ? showSourceList : undefined} aria-label={ hasSourceData ? `${completedText} - ${localize(sourceCount === 1 ? 'com_ui_web_search_source' : 'com_ui_web_search_sources', { count: sourceCount })}` @@ -439,7 +291,7 @@ export default function WebSearch({