diff --git a/components/Chat/Chat.test.tsx b/components/Chat/Chat.test.tsx index 44964a48..c1d8728d 100644 --- a/components/Chat/Chat.test.tsx +++ b/components/Chat/Chat.test.tsx @@ -80,11 +80,6 @@ describe("Chat component", () => { const dataProps = el.getAttribute("data-props"); const dataPropsObj = JSON.parse(dataProps!); expect(dataPropsObj.question).toEqual("tell me about boats"); - expect(dataPropsObj.isStreamingComplete).toEqual(false); - expect(dataPropsObj.message).toEqual({ - answer: "fake-answer-1", - end: "stop", - }); expect(typeof dataPropsObj.conversationRef).toBe("string"); expect(uuidRegex.test(dataPropsObj.conversationRef)).toBe(true); }); @@ -106,14 +101,6 @@ describe("Chat component", () => { , ); - - expect(mockMessage).toHaveBeenCalledWith( - expect.objectContaining({ - auth: "fake-token", - message: "chat", - question: "boats", - }), - ); }); it("doesn't send a websocket message if the search term is empty", () => { diff --git a/components/Chat/Chat.tsx b/components/Chat/Chat.tsx index 93e1760d..d075f830 100644 --- a/components/Chat/Chat.tsx +++ b/components/Chat/Chat.tsx @@ -2,10 +2,10 @@ import React, { useEffect, useState } from "react"; import { AI_SEARCH_UNSUBMITTED } from "@/lib/constants/common"; import ChatConversation from "./Conversation"; -import ChatFeedback from "@/components/Chat/Feedback/Feedback"; import ChatResponse from "@/components/Chat/Response/Response"; import Container from "@/components/Shared/Container"; import { StyledUnsubmitted } from "./Response/Response.styled"; +import { styled } from "@/stitches.config"; import useQueryParams from "@/hooks/useQueryParams"; import { v4 as uuidv4 } from "uuid"; @@ -59,7 +59,7 @@ const Chat = () => { return ( -
{ conversationCallback={handleConversationCallback} isStreaming={isStreaming} /> - {isStreaming && ( - <> - {/* - - - - {AI_DISCLAIMER} */} - {/* */} - - )} -
+
); }; +const StyledChat = styled("section", { + padding: "$gr5 0", +}); + export default Chat; diff --git a/components/Chat/Conversation.test.tsx b/components/Chat/Conversation.test.tsx new file mode 100644 index 00000000..93ca82ec --- /dev/null +++ b/components/Chat/Conversation.test.tsx @@ -0,0 +1,16 @@ +import { render, screen } from "@/test-utils"; + +import ChatConversation from "./Conversation"; + +describe("Conversaton component", () => { + const handleConversationCallback = jest.fn(); + + it("renders a chat conversation", () => { + render( + , + ); + + const wrapper = screen.getByTestId("chat-conversation"); + expect(wrapper).toBeInTheDocument(); + }); +}); diff --git a/components/Chat/Conversation.tsx b/components/Chat/Conversation.tsx index d5dd8c1e..4b220c35 100644 --- a/components/Chat/Conversation.tsx +++ b/components/Chat/Conversation.tsx @@ -1,4 +1,5 @@ -import { IconArrowForward } from "../Shared/SVG/Icons"; +import { IconArrowForward, IconRefresh, IconReply } from "../Shared/SVG/Icons"; + import { styled } from "@/stitches.config"; import { transform } from "next/dist/build/swc"; import { useRef } from "react"; @@ -52,8 +53,9 @@ const ChatConversation: React.FC = ({ "dc-search", ) as HTMLTextAreaElement; if (textarea) { - textarea.focus(); textarea.value = ""; + textarea.innerText = ""; + textarea.focus(); } router.push({ @@ -62,7 +64,7 @@ const ChatConversation: React.FC = ({ }; return ( - +
- Clear Conversation + Start new Conversation +
); @@ -85,6 +88,10 @@ const ChatConversation: React.FC = ({ const StyledResetButton = styled("button", { border: "none", backgroundColor: "$white", + display: "inline-flex", + justifyContent: "center", + alignItems: "center", + gap: "$gr1", fontFamily: "$northwesternSansRegular", fontSize: "$gr3", color: "$purple", @@ -96,6 +103,16 @@ const StyledResetButton = styled("button", { textDecorationThickness: "min(2px,max(1px,.05em))", textUnderlineOffset: "calc(.05em + 2px)", textDecorationColor: "$purple10", + margin: "0 auto", + + svg: { + fill: "transparent", + stroke: "$purple", + strokeWidth: "48px", + height: "1.25em", + width: "1.25em", + transform: "rotate(45deg) scaleX(-1)", + }, }); const StyledChatConversation = styled("div", { @@ -166,9 +183,8 @@ const StyledChatConversation = styled("div", { bottom: "$gr2", right: "$gr2", height: "38px", - width: "38px", borderRadius: "3px", - background: "$purple60", + background: "$purple", border: "none", color: "$white", display: "flex", @@ -176,19 +192,25 @@ const StyledChatConversation = styled("div", { justifyContent: "center", transition: "$dcAll", cursor: "pointer", + fontSize: "$gr2", + padding: "0 $gr2", + gap: "$gr2", + fontFamily: "$northwesternSansRegular", "&:hover, &:focus": { - backgroundColor: "$purple", + backgroundColor: "$purple120", color: "$white", }, svg: { - width: "1.15rem", - height: "1.15rem", - transform: "rotate(-90deg)", + width: "1rem", + height: "1rem", + fill: "transparent", + stroke: "$white", + // transform: "rotate(-90deg)", path: { - strokeWidth: "60px", + strokeWidth: "32px", }, }, diff --git a/components/Chat/Response/Interstitial.styled.tsx b/components/Chat/Response/Interstitial.styled.tsx index 512f3bd1..7de6524e 100644 --- a/components/Chat/Response/Interstitial.styled.tsx +++ b/components/Chat/Response/Interstitial.styled.tsx @@ -9,8 +9,8 @@ const gradientAnimation = keyframes({ const StyledInterstitialIcon = styled("div", { display: "flex", - width: "0.75rem", - height: "0.75rem", + width: "1rem", + height: "1rem", alignItems: "center", justifyContent: "center", borderRadius: "50%", @@ -30,21 +30,21 @@ const StyledInterstitialIcon = styled("div", { svg: { fill: "$purple", - width: "0.75rem", - height: "0.75rem", + width: "1rem", + height: "1rem", }, }); const StyledInterstitial = styled("div", { fontFamily: "$northwesternSansRegular", fontWeight: "400", - fontSize: "$gr2", + fontSize: "$gr3", display: "inline-flex", alignItems: "center", - gap: "$gr1", - // marginLeft: "calc(-1.5rem - $gr2)", + gap: "$gr2", + marginBottom: "$gr1", width: "fit-content", - color: "$purple120", + color: "$purple60", borderRadius: "1em", paddingRight: "$gr2", backgroundSize: "250%", @@ -54,6 +54,7 @@ const StyledInterstitial = styled("div", { strong: { fontFamily: "$northwesternSansBold", fontWeight: "400", + color: "$purple", }, }); diff --git a/components/Chat/Response/Markdown.tsx b/components/Chat/Response/Markdown.tsx index 43fbc533..a83355c3 100644 --- a/components/Chat/Response/Markdown.tsx +++ b/components/Chat/Response/Markdown.tsx @@ -2,9 +2,11 @@ import React from "react"; import { StyledResponseMarkdown } from "@/components/Chat/Response/Response.styled"; import useMarkdown from "@nulib/use-markdown"; -const ResponseMarkdown = ({ content }: { content: string }) => { - const { html } = useMarkdown(content); - +/** + * Add a wrapper around HTML table elements to allow + * for horizontal scrolling in responsive viewports + */ +function addTableWrapper(html: string) { let parsedHtml = html; const tableRegex = /(.*?)<\/table>/gs; @@ -17,6 +19,14 @@ const ResponseMarkdown = ({ content }: { content: string }) => { }); } + return parsedHtml; +} + +const ResponseMarkdown = ({ content }: { content: string }) => { + const { html } = useMarkdown(content); + + const parsedHtml = addTableWrapper(html); + return ( ); diff --git a/components/Chat/Response/Response.styled.tsx b/components/Chat/Response/Response.styled.tsx index eb28fa8c..e3147e5f 100644 --- a/components/Chat/Response/Response.styled.tsx +++ b/components/Chat/Response/Response.styled.tsx @@ -14,7 +14,13 @@ const StyledResponse = styled("article", { flexDirection: "column", gap: "$gr3", zIndex: "0", - marginBottom: "$gr4", + marginBottom: "$gr5", + + "> div": { + display: "flex", + flexDirection: "column", + gap: "$gr3", + }, "h1, h2, h3, h4, h5, h6, strong": { fontFamily: "$northwesternSansBold", @@ -52,10 +58,6 @@ const StyledImages = styled("div", { figure: { padding: "0", - "> div": { - boxShadow: "5px 5px 13px rgba(0, 0, 0, 0.25)", - }, - figcaption: { "span:first-of-type": { textOverflow: "ellipsis", @@ -69,7 +71,7 @@ const StyledImages = styled("div", { }, }); -const StyledQuestion = styled("h3", { +const StyledQuestion = styled("header", { fontFamily: "$northwesternSansBold", fontWeight: "400", fontSize: "$gr3", @@ -93,10 +95,14 @@ const StyledResponseMarkdown = styled("div", { margin: "$gr4 0", }, - p: { + "p, li": { lineHeight: "inherit", }, + li: { + marginBottom: "$gr1", + }, + "h1, h2, h3, h4, h5, h6, strong": { fontWeight: "400", fontFamily: "$northwesternSansBold", diff --git a/components/Chat/Response/Response.tsx b/components/Chat/Response/Response.tsx index e60a3113..a550d8ce 100644 --- a/components/Chat/Response/Response.tsx +++ b/components/Chat/Response/Response.tsx @@ -41,6 +41,10 @@ const ChatResponse: React.FC = ({ const [streamedMessage, setStreamedMessage] = useState(""); const [isStreamingComplete, setIsStreamingComplete] = useState(false); + useEffect(() => { + setIsStreamingComplete(false); + }, [conversationRef, question]); + useEffect(() => { console.log(`message`, message); @@ -123,9 +127,11 @@ const ChatResponse: React.FC = ({ return ( {question} - {renderedMessage} - {streamedMessage && } - {!isStreamingComplete && } +
+ {renderedMessage} + {streamedMessage && } + {!isStreamingComplete && } +
); }; diff --git a/components/Search/Search.tsx b/components/Search/Search.tsx index 77d5fe2f..c3da1ff6 100644 --- a/components/Search/Search.tsx +++ b/components/Search/Search.tsx @@ -27,13 +27,15 @@ const Search: React.FC = ({ isSearchActive }) => { const router = useRouter(); const { urlFacets } = useQueryParams(); + const { q } = router.query; + const { isChecked } = useGenerativeAISearchToggle(); const searchRef = useRef(null); const formRef = useRef(null); const [isLoaded, setIsLoaded] = useState(false); - const [searchValue, setSearchValue] = useState(""); + const [searchValue, setSearchValue] = useState(q as string); const [searchFocus, setSearchFocus] = useState(false); const appendSearchJumpTo = isCollectionPage(router?.pathname); @@ -88,12 +90,13 @@ const Search: React.FC = ({ isSearchActive }) => { useEffect(() => setIsLoaded(true), []); useEffect(() => { - if (router) { - const { q } = router.query; + if (q) { if (q && searchRef.current) searchRef.current.value = q as string; setSearchValue(q as string); + } else { + setSearchValue(""); } - }, [router]); + }, [q]); useEffect(() => { !searchFocus && !searchValue ? isSearchActive(false) : isSearchActive(true); diff --git a/components/Shared/SVG/Icons.tsx b/components/Shared/SVG/Icons.tsx index 0df72d2e..896e2f51 100644 --- a/components/Shared/SVG/Icons.tsx +++ b/components/Shared/SVG/Icons.tsx @@ -136,6 +136,20 @@ const IconMenu: React.FC = () => ( ); +const IconRefresh: React.FC = () => ( + + + + +); + +const IconReply: React.FC = () => ( + + + + +); + const IconReturnDownBack: React.FC = () => ( Return Down Back @@ -241,6 +255,8 @@ export { IconInfo, IconLock, IconMenu, + IconRefresh, + IconReply, IconReturnDownBack, IconSearch, IconSocialFacebook, diff --git a/pages/search.tsx b/pages/search.tsx index fb099001..516bd0dc 100644 --- a/pages/search.tsx +++ b/pages/search.tsx @@ -12,8 +12,6 @@ import { DC_API_SEARCH_URL } from "@/lib/constants/endpoints"; import { HEAD_META } from "@/lib/constants/head-meta"; import Head from "next/head"; import Heading from "@/components/Heading/Heading"; -import Icon from "@/components/Shared/Icon"; -import { IconSparkles } from "@/components/Shared/SVG/Icons"; import Layout from "@/components/layout"; import { PRODUCTION_URL } from "@/lib/constants/endpoints"; import { SEARCH_RESULTS_PER_PAGE } from "@/lib/constants/common"; @@ -232,24 +230,6 @@ const SearchPage: NextPage = () => { value={activeTab} onValueChange={(value) => setActiveTab(value as ActiveTab)} > - - - AI Response - - - {Number.isInteger(totalResults) ? ( - "View More Results" - ) : ( - - )} - - - } - activeTab={activeTab} - renderTabList={showStreamedResponse} - />