From d1f84fa4a4f813b7784ed5a6ff9c800dc8752887 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 31 Jul 2026 04:19:25 +0200 Subject: [PATCH] feat(client): image, shopping, and place verticals in web search results Serper vertical results were parsed into the artifact and dropped. The expanded panel now renders them when present: an image strip of rounded thumbnails at true aspect ratios (sized from thumbnail dimensions, no layout shift), a shopping card carousel with product image, title, price and store, delivery note, and star rating, and a place list with name, category, address, and rating. All link out, use semantic lists with localized labels, and cap item counts. Expansion now keys off any renderable content, so a verticals-only search still opens. --- .../Chat/Messages/Content/WebSearch.tsx | 270 ++++++++++++++---- .../Content/__tests__/WebSearch.test.tsx | 62 ++++ client/src/locales/en/translation.json | 3 + 3 files changed, 285 insertions(+), 50 deletions(-) diff --git a/client/src/components/Chat/Messages/Content/WebSearch.tsx b/client/src/components/Chat/Messages/Content/WebSearch.tsx index d7df66a0bb..a57d3e5ae4 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 } from 'lucide-react'; +import { Globe, ChevronDown, Info, Star, MapPin } from 'lucide-react'; import { HoverCard, HoverCardTrigger, HoverCardPortal, HoverCardContent } from '@librechat/client'; import type { TAttachment, @@ -9,6 +9,9 @@ import type { SearchResultData, PartMetadata, AnswerBoxResult, + ShoppingResult, + ImageResult, + PlaceResult, } from 'librechat-data-provider'; import { disclosureChevronClassName, toolPanelSpacingClassName } from './disclosure'; import { FaviconImage, getCleanDomain } from '~/components/Web/SourceHovercard'; @@ -66,6 +69,133 @@ 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 ( + + + {rating} + {ratingCount != null && ({ratingCount})} + + ); +} + +function ImageStrip({ images, label }: { images: ImageResult[]; label: string }) { + return ( +