From f4760f796ddb7f46d3e5adc95a276cfa6b39fe90 Mon Sep 17 00:00:00 2001 From: Mat Jordan Date: Wed, 18 Dec 2024 13:42:52 -0500 Subject: [PATCH] Wire up the reset of search term. --- components/Chat/Chat.test.tsx | 17 ++-- components/Chat/Chat.tsx | 83 +++++++++++++------ .../Chat/Response/Interstitial.styled.tsx | 6 +- components/Chat/Response/Interstitial.tsx | 16 +++- components/Chat/Response/Response.styled.tsx | 2 +- components/Chat/Response/Response.tsx | 34 ++++++-- lib/chat-helpers.ts | 9 +- pages/search.tsx | 5 +- 8 files changed, 121 insertions(+), 51 deletions(-) diff --git a/components/Chat/Chat.test.tsx b/components/Chat/Chat.test.tsx index d0758c9a..44964a48 100644 --- a/components/Chat/Chat.test.tsx +++ b/components/Chat/Chat.test.tsx @@ -72,18 +72,21 @@ describe("Chat component", () => { , ); + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const el = screen.getByTestId("mock-chat-response"); expect(el).toBeInTheDocument(); const dataProps = el.getAttribute("data-props"); - expect(JSON.parse(dataProps!)).toEqual({ - isStreamingComplete: false, - searchTerm: "tell me about boats", - message: { - answer: "fake-answer-1", - end: "stop", - }, + 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); }); it("sends a websocket message when the search term changes", () => { diff --git a/components/Chat/Chat.tsx b/components/Chat/Chat.tsx index 96a2deab..0666c194 100644 --- a/components/Chat/Chat.tsx +++ b/components/Chat/Chat.tsx @@ -15,12 +15,11 @@ import Container from "@/components/Shared/Container"; import { prepareQuestion } from "@/lib/chat-helpers"; import useChatSocket from "@/hooks/useChatSocket"; import useQueryParams from "@/hooks/useQueryParams"; +import { v4 as uuidv4 } from "uuid"; const Chat = ({ - totalResults, viewResultsCallback, }: { - totalResults?: number; viewResultsCallback?: () => void; }) => { const { searchTerm = "" } = useQueryParams(); @@ -37,35 +36,42 @@ const Chat = ({ searchState: { chat }, searchDispatch, } = useSearchState(); - const { question } = chat; + const { question, answer } = chat; const [isStreamingComplete, setIsStreamingComplete] = useState(false); useEffect(() => { - if (!isStreamingComplete && isConnected && authToken && searchTerm) { + if ( + !isStreamingComplete && + isConnected && + authToken && + searchTerm && + conversationRef + ) { resetChat(); - const preparedQuestion = prepareQuestion(searchTerm, authToken); - setConversationRef(preparedQuestion.ref); + const preparedQuestion = prepareQuestion( + searchTerm, + authToken, + conversationRef, + ); sendMessage(preparedQuestion); } - }, [authToken, isStreamingComplete, isConnected, searchTerm, sendMessage]); + }, [ + authToken, + isStreamingComplete, + isConnected, + searchTerm, + conversationRef, + sendMessage, + ]); useEffect(() => { - if (!message) return; - - // const updateChat = () => { - // searchDispatch({ - // chat: { - // answer: message.answer || "", - // documents: sourceDocuments, - // question: searchTerm || "", - // ref: message.ref, - // }, - // type: "updateChat", - // }); - // }; - - if (message.type === "final_message") setIsStreamingComplete(true); + setIsStreamingComplete(false); + setConversationRef(uuidv4()); + }, [searchTerm]); + + useEffect(() => { + if (!message || !conversationRef) return; }, [message]); function handleNewQuestion() { @@ -90,13 +96,42 @@ const Chat = ({ ); + const handleResponseCallback = (content: any) => { + if (!conversationRef) return; + + setIsStreamingComplete(true); + searchDispatch({ + chat: { + // content here is now a react element + // once continued conversations ar e in place + // see note below for question refactor + answer: content, + + // documents should be eventually removed as + // they are now integrated into content + // doing so will require some careful refactoring + // as the documents are used in feedback form + documents: [], + + // question should become an entry[] with + // entry[n].question and entry[n].content + question: searchTerm || "", + + ref: conversationRef, + }, + type: "updateChat", + }); + }; + return ( <> {streamingError && ( diff --git a/components/Chat/Response/Interstitial.styled.tsx b/components/Chat/Response/Interstitial.styled.tsx index 9af3de0d..acde30d4 100644 --- a/components/Chat/Response/Interstitial.styled.tsx +++ b/components/Chat/Response/Interstitial.styled.tsx @@ -43,10 +43,14 @@ const StyledInterstitialIcon = styled("div", { const StyledInterstitial = styled("div", { color: "$black", fontFamily: "$northwesternSansBold", - fontSize: "$gr3", + fontSize: "$gr4", display: "flex", alignItems: "center", gap: "$gr2", + + em: { + color: "$purple", + }, }); export { StyledInterstitial, StyledInterstitialIcon }; diff --git a/components/Chat/Response/Interstitial.tsx b/components/Chat/Response/Interstitial.tsx index 168c90bf..da0447e7 100644 --- a/components/Chat/Response/Interstitial.tsx +++ b/components/Chat/Response/Interstitial.tsx @@ -15,17 +15,25 @@ const ResponseInterstitial: React.FC = ({ message, }) => { const { tool, input } = message; - let text = ""; + let text: React.ReactElement = <>; switch (tool) { case "aggregate": - text = `Aggregating ${input.agg_field} by ${input.term_field} ${input.term}`; + text = ( + <> + Aggregating {input.agg_field} by {input.term_field} {input.term} + + ); break; case "discover_fields": - text = "Discovering fields"; + text = <>Discovering fields; break; case "search": - text = `Searching for: ${input.query}`; + text = ( + <> + Searching for {input.query} + + ); break; default: console.warn("Unknown tool_start message", message); diff --git a/components/Chat/Response/Response.styled.tsx b/components/Chat/Response/Response.styled.tsx index 62100316..1083c9e9 100644 --- a/components/Chat/Response/Response.styled.tsx +++ b/components/Chat/Response/Response.styled.tsx @@ -79,7 +79,7 @@ const StyledQuestion = styled("h3", { fontSize: "$gr6", letterSpacing: "-0.012em", lineHeight: "1.35em", - margin: "0", + margin: "0 0 $gr4", padding: "0", color: "$black", }); diff --git a/components/Chat/Response/Response.tsx b/components/Chat/Response/Response.tsx index 39b852fa..11454628 100644 --- a/components/Chat/Response/Response.tsx +++ b/components/Chat/Response/Response.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { use, useEffect, useState } from "react"; import { StyledQuestion, StyledResponse, @@ -14,16 +14,18 @@ import { StreamingMessage } from "@/types/components/chat"; interface ChatResponseProps { conversationRef?: string; - message?: StreamingMessage; - searchTerm: string; isStreamingComplete: boolean; + message?: StreamingMessage; + question: string; + responseCallback?: (renderedMessage: any) => void; } const ChatResponse: React.FC = ({ conversationRef, - message, - searchTerm, isStreamingComplete, + message, + question, + responseCallback, }) => { const [renderedMessage, setRenderedMessage] = useState(); const [streamedMessage, setStreamedMessage] = useState(""); @@ -83,17 +85,35 @@ const ChatResponse: React.FC = ({ )); } + + /** + * Final message is the last message in the response + * and is used to trigger the responseCallback + * to store this response. + */ + if (type === "final_message") { + if (responseCallback) responseCallback(renderedMessage); + } }, [message]); + useEffect(() => { + resetRenderedMessage(); + resetStreamedMessage(); + }, [conversationRef]); + function resetStreamedMessage() { setStreamedMessage(""); } + function resetRenderedMessage() { + setRenderedMessage(undefined); + } + return ( - {searchTerm} + {question} {renderedMessage} {streamedMessage && } {!isStreamingComplete && } @@ -103,4 +123,4 @@ const ChatResponse: React.FC = ({ ); }; -export default React.memo(ChatResponse); +export default ChatResponse; diff --git a/lib/chat-helpers.ts b/lib/chat-helpers.ts index 87cec0b4..467d3f52 100644 --- a/lib/chat-helpers.ts +++ b/lib/chat-helpers.ts @@ -1,14 +1,17 @@ import axios, { AxiosError } from "axios"; import { DCAPI_CHAT_FEEDBACK } from "./constants/endpoints"; -import { v4 as uuidv4 } from "uuid"; -const prepareQuestion = (questionString: string, authToken: string) => { +const prepareQuestion = ( + questionString: string, + authToken: string, + conversationRef: string, +) => { return { auth: authToken, message: "chat", question: questionString, - ref: uuidv4(), + ref: conversationRef, }; }; diff --git a/pages/search.tsx b/pages/search.tsx index 568886cb..b27958b7 100644 --- a/pages/search.tsx +++ b/pages/search.tsx @@ -254,10 +254,7 @@ const SearchPage: NextPage = () => { renderTabList={showStreamedResponse} /> - +