From b1a3330654bdbbdab7bd5c8c28995580701b2985 Mon Sep 17 00:00:00 2001 From: Davide Mauri Date: Mon, 25 Mar 2024 20:20:45 -0700 Subject: [PATCH] improved chat experience --- client/src/api/chat.ts | 48 ++++++++++----------- client/src/pages/Chat.tsx | 87 ++++++++++++++++++++++++++++++++++----- 2 files changed, 98 insertions(+), 37 deletions(-) diff --git a/client/src/api/chat.ts b/client/src/api/chat.ts index 540bb75..b0ecddd 100644 --- a/client/src/api/chat.ts +++ b/client/src/api/chat.ts @@ -1,4 +1,5 @@ import { AskResponse } from "../models"; +import { json } from 'react-router-dom'; type ChatTurn = { userPrompt: string; @@ -10,10 +11,6 @@ type UserQuestion = { askedOn: Date; }; -function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - let questionAndAnswers: Record = {}; export const ask = async (prompt: string) => { @@ -25,33 +22,32 @@ export const ask = async (prompt: string) => { }; questionAndAnswers[currentMessageId] = [currentQuestion, undefined]; - for (let id in questionAndAnswers) { - const [question, answer] = questionAndAnswers[id]; - history.push({ - userPrompt: question.question, - responseMessage: answer?.answer, - }); - } - + history.push({ + userPrompt: currentQuestion.question + }); + const response = await fetch("/api/ask", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(history), - }); - - const askResponse: AskResponse = await response.json(); - - questionAndAnswers[currentMessageId] = [ - currentQuestion, - { - answer: askResponse.answer, - thoughts: askResponse.thoughts, - dataPoints: askResponse.dataPoints, - citationBaseUrl: askResponse.citationBaseUrl, - }, - ]; - + }) + + if (response.ok) { + const askResponse: AskResponse = await response.json(); + questionAndAnswers[currentMessageId] = [ + currentQuestion, + { + answer: askResponse.answer, + thoughts: askResponse.thoughts, + dataPoints: askResponse.dataPoints, + citationBaseUrl: askResponse.citationBaseUrl, + } + ]; + } else { + throw json(response.statusText, response.status); + } + return await Promise.resolve(questionAndAnswers); }; diff --git a/client/src/pages/Chat.tsx b/client/src/pages/Chat.tsx index 5782ccc..8a47d0b 100644 --- a/client/src/pages/Chat.tsx +++ b/client/src/pages/Chat.tsx @@ -1,23 +1,50 @@ import { - Body1, Card, - CardHeader, Textarea, TextareaProps, - Text, makeStyles, - CardFooter, Spinner, Title2 } from "@fluentui/react-components"; import { SendRegular } from "@fluentui/react-icons"; import { useState } from "react"; -import { ActionFunctionArgs, useFetcher } from "react-router-dom"; +import { ActionFunctionArgs, isRouteErrorResponse, useFetcher, useRouteError } from "react-router-dom"; import { ask } from "../api/chat"; import { FancyText } from "../components/FancyText"; import { PrimaryButton } from "../components/PrimaryButton"; import ReactMarkdown from "react-markdown"; +var isThinking:boolean = false; +var intervalId = 0 +var thinkingTicker = 0; +var thinkingMessages:string[] = [ + "Analyzing the question...", + "Thinking...", + "Querying the database...", + "Extracting embeddings...", + "Finding vectors in the latent space...", + "Identifying context...", + "Analyzing results...", + "Finding the best answer...", + "Formulating response...", + "Double checking the answer...", + "Correcting spelling...", + "Doing an internal review...", + "Checking for errors...", + "Validating the answer...", + "Adding more context...", + "Analyzing potential response...", + "Re-reading the original question...", + "Adding more details...", + "Improving the answer...", + "Making it nice and polished...", + "Removing typos...", + "Adding punctuation...", + "Checking grammar...", + "Adding context...", + "Sending response..." +] + const useClasses = makeStyles({ container: {}, chatArea: {}, @@ -73,11 +100,12 @@ export const Chat = () => { const fetcher = useFetcher>>(); const classes = useClasses(); + const [thinking, setThinking] = useState(thinkingMessages[0]); + const [prompt, setPrompt] = useState(""); + const submitting = fetcher.state !== "idle"; const data = fetcher.data; - const [prompt, setPrompt] = useState(""); - const onChange: TextareaProps["onChange"] = (_, data) => setPrompt(() => data.value); @@ -93,6 +121,24 @@ export const Chat = () => { } }; + if (submitting && !isThinking) { + isThinking = true; + thinkingTicker = 0; + setThinking(thinkingMessages[thinkingTicker]); + const updateThinking = () => { + thinkingTicker += 1; + var i = thinkingTicker > thinkingMessages.length - 1 ? 0 : thinkingTicker; + setThinking(thinkingMessages[i]); + } + intervalId = setInterval(updateThinking, 2000); + } + + if (!submitting && isThinking) { + isThinking = false; + clearInterval(intervalId); + setThinking(thinkingMessages[0]); + } + return (
@@ -101,8 +147,7 @@ export const Chat = () => { Ask questions to the AI model in natural language and get meaningful answers to help you navigate the conferences sessions and find the best ones for you. Thanks to Prompt Engineering and Retrieval Augmented Generation (RAG) finding - details and recommendations on what session to attend is easier than ever. Please note that the AI model will remember your questions and answers to improve the quality of the answers and get more context on your requests. - If you want to start from scratch, click here or refresh the page. + details and recommendations on what session to attend is easier than ever.
@@ -126,8 +171,8 @@ export const Chat = () => { > Ask - {submitting && } - + {submitting && } +
{!submitting && data && } @@ -135,3 +180,23 @@ export const Chat = () => {
); }; + +export const ChatError = () => { + const error = useRouteError(); + console.error(error); + if (isRouteErrorResponse(error)) { + return( +
+ + {error.status} - {error.statusText} {error.data.statusText} + + + Sorry, there was a problem while processing your request. Please try again. + +
+ ) + } + else { + throw error; + } +} \ No newline at end of file