diff --git a/clients/search-component/example/src/routes/ecommerce.tsx b/clients/search-component/example/src/routes/ecommerce.tsx index 2cf52831ef..596404af15 100644 --- a/clients/search-component/example/src/routes/ecommerce.tsx +++ b/clients/search-component/example/src/routes/ecommerce.tsx @@ -56,6 +56,7 @@ export default function ECommerce() { baseUrl={baseUrl} datasetId={datasetId} problemLink={problemLink} + theme={theme} brandLogoImgSrcUrl={brandLogoSrcUrl} brandName={brandName} brandColor={brandColor} diff --git a/clients/search-component/src/TrieveModal/Chat/AIInitalMessage.tsx b/clients/search-component/src/TrieveModal/Chat/AIInitalMessage.tsx index 53e7ac4743..2731f145be 100644 --- a/clients/search-component/src/TrieveModal/Chat/AIInitalMessage.tsx +++ b/clients/search-component/src/TrieveModal/Chat/AIInitalMessage.tsx @@ -3,9 +3,10 @@ import { useModalState } from "../../utils/hooks/modal-context"; import { AIIcon } from "../icons"; import { SuggestedQuestions } from "./SuggestedQuestions"; import { useChatState } from "../../utils/hooks/chat-context"; +import { GroupChatImgCarousel } from "./GroupChatImgCarousel"; export const AIInitialMessage = () => { - const { props } = useModalState(); + const { props, currentGroup } = useModalState(); const { messages } = useChatState(); return ( @@ -46,11 +47,12 @@ export const AIInitialMessage = () => { }} className="brand-name" > - {props.brandName || "Trieve"} + {currentGroup?.name || props.brandName || "Trieve"}

+ - {!messages.length ? : null} + {!messages.length && !currentGroup ? : null} ); }; diff --git a/clients/search-component/src/TrieveModal/Chat/ChatMessage.tsx b/clients/search-component/src/TrieveModal/Chat/ChatMessage.tsx index 216fbf2405..7d06da4f15 100644 --- a/clients/search-component/src/TrieveModal/Chat/ChatMessage.tsx +++ b/clients/search-component/src/TrieveModal/Chat/ChatMessage.tsx @@ -183,7 +183,7 @@ export const Message = ({ (chunk.metadata.heading || chunk.metadata.title || chunk.metadata.page_title) && - chunk.link + chunk.link, ) .map((chunk) => [ chunk.metadata.heading || @@ -194,7 +194,7 @@ export const Message = ({ .filter( (link, index, array) => array.findIndex((item) => item[0] === link[0]) === - index && link[0] + index && link[0], ) .map((link, index) => ( diff --git a/clients/search-component/src/TrieveModal/Chat/ChatMode.tsx b/clients/search-component/src/TrieveModal/Chat/ChatMode.tsx index e8ff4353e3..aca08613b4 100644 --- a/clients/search-component/src/TrieveModal/Chat/ChatMode.tsx +++ b/clients/search-component/src/TrieveModal/Chat/ChatMode.tsx @@ -1,5 +1,5 @@ import React, { Suspense } from "react"; -import { BackIcon } from "../icons"; +import { BackIcon, CloseIcon } from "../icons"; import { useModalState } from "../../utils/hooks/modal-context"; import { AIInitialMessage } from "./AIInitalMessage"; import { useChatState } from "../../utils/hooks/chat-context"; @@ -7,11 +7,21 @@ import { ChatMessage } from "./ChatMessage"; import { Tags } from "../Tags"; export const ChatMode = () => { - const { props, setMode, modalRef, open, setOpen, mode } = useModalState(); + const { + props, + setMode, + modalRef, + open, + setOpen, + mode, + currentGroup, + setCurrentGroup, + } = useModalState(); const { askQuestion, messages, currentQuestion, + cancelGroupChat, setCurrentQuestion, clearConversation, isDoneReading, @@ -56,21 +66,41 @@ export const ChatMode = () => {
- {messages.map((chat, i) => ( -
- {chat.map((message, idx) => ( - - ))} -
- ))}
+ {messages.map((chat, i) => ( +
+ {chat.map((message, idx) => ( + + ))} +
+ ))}
+ {currentGroup && ( +
+
Chatting with {currentGroup.name}
+ +
+ )}
-
{ ref={chatInput} value={currentQuestion} onChange={(e) => setCurrentQuestion(e.target.value)} - placeholder="Ask me anything" + placeholder="Ask me anything about" />
diff --git a/clients/search-component/src/TrieveModal/Chat/GroupChatImgCarousel.tsx b/clients/search-component/src/TrieveModal/Chat/GroupChatImgCarousel.tsx new file mode 100644 index 0000000000..ef92c2eb71 --- /dev/null +++ b/clients/search-component/src/TrieveModal/Chat/GroupChatImgCarousel.tsx @@ -0,0 +1,50 @@ +import { useEffect, useState } from "react"; +import React from "react"; +import { useModalState } from "../../utils/hooks/modal-context"; +import { cached } from "../../utils/cache"; +import { getAllChunksForGroup } from "../../utils/trieve"; + +export const GroupChatImgCarousel = () => { + const { currentGroup, trieveSDK } = useModalState(); + + const [groupCarouselItems, setGroupCarouselItems] = useState< + string[] | null + >(); + + useEffect(() => { + const setGroupCarousel = async () => { + if (currentGroup) { + const groupChunks = await cached(() => { + return getAllChunksForGroup(currentGroup.id, trieveSDK); + }, `chunk-ids-${currentGroup.id}`); + + const images = groupChunks + .map((chunk) => { + return chunk.image_urls?.[0] || undefined; + }) + .filter(Boolean) as string[]; + // Deduplicate with set + const uniqueImages = [...new Set(images)]; + setGroupCarouselItems(uniqueImages); + } else { + setGroupCarouselItems(null); + } + }; + + setGroupCarousel(); + }, [currentGroup]); + + return ( + <> + {currentGroup && groupCarouselItems ? ( +
+ {groupCarouselItems.map((image) => ( +
+ +
+ ))} +
+ ) : undefined} + + ); +}; diff --git a/clients/search-component/src/TrieveModal/OpenModalButton.tsx b/clients/search-component/src/TrieveModal/OpenModalButton.tsx index 67c9ccbcca..8cd51314a3 100644 --- a/clients/search-component/src/TrieveModal/OpenModalButton.tsx +++ b/clients/search-component/src/TrieveModal/OpenModalButton.tsx @@ -32,7 +32,6 @@ export const OpenModalButton = ({ setOpen }: OpenModalButtonProps) => { className={`${props.theme} ${ props.responsive ?? false ? "responsive" : "" }`} - key="open-button-container" >
{
{keyCombo.map((key) => ( -
+
{key.ctrl ? ( diff --git a/clients/search-component/src/TrieveModal/Search/ProductGroupItem.tsx b/clients/search-component/src/TrieveModal/Search/ProductGroupItem.tsx index 3bd3510bbc..194bf76680 100644 --- a/clients/search-component/src/TrieveModal/Search/ProductGroupItem.tsx +++ b/clients/search-component/src/TrieveModal/Search/ProductGroupItem.tsx @@ -1,27 +1,47 @@ -import React, { useState, useMemo } from "react"; +import React, { useMemo } from "react"; import { ProductItem } from "./ProductItem"; import { GroupChunk } from "../../utils/types"; - +import { findCommonName, guessTitleAndDesc } from "../../utils/estimation"; type Props = { requestID: string; - // Group of Groups (with subvariants) group: GroupChunk[]; index: number; -} +}; export const ProductGroupItem = ({ index, group, requestID }: Props) => { + const selectedItem = useMemo( + () => group[0].chunks[0], + [], + ); + + // Shopify + const betterGroupName = useMemo( + () => { + const productNames: string[] = []; + group.forEach( + g => g.chunks.forEach(c => { + const {title} = guessTitleAndDesc(c); + productNames.push(title) + }) + ) + + // Calculate the overlap of the strings + const commonName = findCommonName(productNames); + return commonName || undefined; + }, + [group] + ) - const [groupItemIndex] = useState(0); - const selectedItem = useMemo(() => group[groupItemIndex].chunks[0], [groupItemIndex]); return ( ); }; - diff --git a/clients/search-component/src/TrieveModal/Search/ProductItem.tsx b/clients/search-component/src/TrieveModal/Search/ProductItem.tsx index 6afd8ee0f9..f830a1b522 100644 --- a/clients/search-component/src/TrieveModal/Search/ProductItem.tsx +++ b/clients/search-component/src/TrieveModal/Search/ProductItem.tsx @@ -1,74 +1,56 @@ import { Chunk, ChunkWithHighlights } from "../../utils/types"; -import React, { useRef, useState } from "react"; +import React, { useMemo, useRef, useState } from "react"; import { useModalState } from "../../utils/hooks/modal-context"; import { sendCtrData } from "../../utils/trieve"; +import { ChunkGroup } from "trieve-ts-sdk"; +import { ChatIcon } from "../icons"; +import { guessTitleAndDesc, uniquifyVariants } from "../../utils/estimation"; +import { useChatState } from "../../utils/hooks/chat-context"; type Props = { item: ChunkWithHighlights; requestID: string; index: number; className?: string; + group?: ChunkGroup; + betterGroupName?: string; }; -export const ProductItem = ({ item, requestID, index, className }: Props) => { +export const ProductItem = ({ + item, + requestID, + index, + className, + group, + betterGroupName, +}: Props) => { const { props, trieveSDK } = useModalState(); + const { chatWithGroup } = useChatState(); const Component = item.chunk.link ? "a" : "button"; // eslint-disable-next-line @typescript-eslint/no-explicit-any const itemRef = useRef(null); - let descriptionHtml = item.highlights - ? item.highlights.join("...") - : item.chunk.chunk_html || ""; - const $descriptionHtml = document.createElement("div"); - $descriptionHtml.innerHTML = descriptionHtml; - $descriptionHtml.querySelectorAll("b").forEach((b) => { - return b.replaceWith(b.textContent || ""); - }); - descriptionHtml = $descriptionHtml.innerHTML; - - const chunkHtmlHeadingsDiv = document.createElement("div"); - chunkHtmlHeadingsDiv.innerHTML = item.chunk.chunk_html || ""; - const chunkHtmlHeadings = chunkHtmlHeadingsDiv.querySelectorAll( - "h1, h2, h3, h4, h5, h6" + const { title, descriptionHtml } = useMemo( + () => guessTitleAndDesc(item), + [item], ); - const $firstHeading = chunkHtmlHeadings[0] ?? document.createElement("h1"); - $firstHeading?.querySelectorAll(":not(mark)")?.forEach((tag) => { - return tag.replaceWith(tag.textContent || ""); - }); - const firstHeadingIdDiv = document.createElement("div"); - firstHeadingIdDiv.innerHTML = $firstHeading.id; - const cleanFirstHeading = $firstHeading?.innerHTML; - - descriptionHtml = descriptionHtml - .replace(" ", " ") - .replace(cleanFirstHeading || "", ""); - - for (const heading of chunkHtmlHeadings) { - const curHeadingText = heading.textContent; - - descriptionHtml = descriptionHtml.replace(curHeadingText || "", ""); - } - descriptionHtml = descriptionHtml.replace(/([.,!?;:])/g, "$1 "); const [shownImage, setShownImage] = useState( - item.chunk?.image_urls?.[0] || "" + item.chunk?.image_urls?.[0] || "", ); - const title = `${cleanFirstHeading || - item.chunk.metadata?.title || - item.chunk.metadata?.page_title || - item.chunk.metadata?.name - }`; - const formatPrice = (price: number | null | undefined) => { return price - ? `${props.currencyPosition === "before" ? props.defaultCurrency ?? "$" : "" - }${price}${props.currencyPosition === "after" ? props.defaultCurrency ?? "$" : "" - }` - : "" + ? `${ + props.currencyPosition === "before" + ? props.defaultCurrency ?? "$" + : "" + }${price}${ + props.currencyPosition === "after" ? props.defaultCurrency ?? "$" : "" + }` + : ""; }; - const formatedPrice = formatPrice(item.chunk.num_value); let priceMin = item.chunk.num_value ?? 0; @@ -98,7 +80,9 @@ export const ProductItem = ({ item, requestID, index, className }: Props) => { } } } - const formatedPriceRange = `${formatPrice(priceMin)} - ${formatPrice(priceMax)}`; + const formatedPriceRange = `${formatPrice(priceMin)} - ${formatPrice( + priceMax, + )}`; if (!title.trim() || title == "undefined") { return null; @@ -106,7 +90,7 @@ export const ProductItem = ({ item, requestID, index, className }: Props) => { const onResultClick = async ( chunk: Chunk & { position: number }, - requestID: string + requestID: string, ) => { if (props.onResultClick) { props.onResultClick(chunk); @@ -134,7 +118,7 @@ export const ProductItem = ({ item, requestID, index, className }: Props) => { ...item.chunk, position: index, }, - requestID + requestID, ) } href={item.chunk.link ?? ""} @@ -156,12 +140,27 @@ export const ProductItem = ({ item, requestID, index, className }: Props) => {

-

- {priceMin !== priceMax ? formatedPriceRange : formatedPrice} -
+
+
+ {priceMin !== priceMax ? formatedPriceRange : formatedPrice} +
+ {group && ( + + )} +

{ {item.chunk.metadata?.variants?.length > 1 ? (

Variants: - {( + {uniquifyVariants( item.chunk.metadata.variants as unknown as { featured_image: { src: string }; title: string; - }[] + }[], )?.map((variant) => (