From 074ea24565395fc7c2be5e32c25ca2448541b646 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Wed, 19 Jun 2024 22:34:21 -0500 Subject: [PATCH] added multi-turn conversations --- apps/web/app/(dash)/chat/chatWindow.tsx | 80 +++++++++++++++------ apps/web/app/(dash)/home/page.tsx | 37 +++++++--- apps/web/app/(dash)/home/queryinput.tsx | 96 ++++++++++++++----------- 3 files changed, 141 insertions(+), 72 deletions(-) diff --git a/apps/web/app/(dash)/chat/chatWindow.tsx b/apps/web/app/(dash)/chat/chatWindow.tsx index 6189b874..23f49554 100644 --- a/apps/web/app/(dash)/chat/chatWindow.tsx +++ b/apps/web/app/(dash)/chat/chatWindow.tsx @@ -1,7 +1,7 @@ "use client"; import { AnimatePresence } from "framer-motion"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import QueryInput from "../home/queryinput"; import { cn } from "@repo/ui/lib/utils"; import { motion } from "framer-motion"; @@ -36,15 +36,12 @@ function ChatWindow({ { question: q, answer: { - parts: [ - // { - // text: `It seems like there might be a typo in your question. Could you please clarify or provide more context? If you meant "interesting," please let me know what specific information or topic you find interesting, and I can help you with that.`, - // }, - ], + parts: [], sources: [], }, }, ]); + const [isAutoScroll, setIsAutoScroll] = useState(true); const removeJustificationFromText = (text: string) => { // remove everything after the first "" word @@ -68,7 +65,7 @@ function ChatWindow({ { method: "POST", body: JSON.stringify({ chatHistory }), - } + }, ); // TODO: handle this properly @@ -89,11 +86,15 @@ function ChatWindow({ } setChatHistory((prevChatHistory) => { + window.scrollTo({ + top: document.documentElement.scrollHeight, + behavior: "smooth", + }); const newChatHistory = [...prevChatHistory]; const lastAnswer = newChatHistory[newChatHistory.length - 1]; if (!lastAnswer) return prevChatHistory; const filteredSourceUrls = new Set( - sourcesParsed.data.metadata.map((source) => source.url) + sourcesParsed.data.metadata.map((source) => source.url), ); const uniqueSources = sourcesParsed.data.metadata.filter((source) => { if (filteredSourceUrls.has(source.url)) { @@ -106,9 +107,9 @@ function ChatWindow({ title: source.title ?? "Untitled", type: source.type ?? "page", source: source.url ?? "https://supermemory.ai", - content: source.content ?? "No content available", + content: source.description ?? "No content available", numChunks: sourcesParsed.data.metadata.filter( - (f) => f.url === source.url + (f) => f.url === source.url, ).length, })); return newChatHistory; @@ -129,7 +130,16 @@ function ChatWindow({ const newChatHistory = [...prevChatHistory]; const lastAnswer = newChatHistory[newChatHistory.length - 1]; if (!lastAnswer) return prevChatHistory; - lastAnswer.answer.parts.push({ text: new TextDecoder().decode(value) }); + const txt = new TextDecoder().decode(value); + + if (isAutoScroll) { + window.scrollTo({ + top: document.documentElement.scrollHeight, + behavior: "smooth", + }); + } + + lastAnswer.answer.parts.push({ text: txt }); return newChatHistory; }); } @@ -137,13 +147,11 @@ function ChatWindow({ useEffect(() => { if (q.trim().length > 0) { + setLayout("chat"); getAnswer( q, - spaces.map((s) => s.id) + spaces.map((s) => s.id), ); - setTimeout(() => { - setLayout("chat"); - }, 300); } else { router.push("/home"); } @@ -159,22 +167,27 @@ function ChatWindow({ className="max-w-3xl h-full justify-center items-center flex mx-auto w-full flex-col" >
- + {}} + initialQuery={q} + initialSpaces={[]} + disabled + />
) : (
{chatHistory.map((chat, idx) => (

{chat.question} @@ -273,7 +286,7 @@ function ChatWindow({ className="flex flex-col gap-2" > {removeJustificationFromText( - chat.answer.parts.map((part) => part.text).join("") + chat.answer.parts.map((part) => part.text).join(""), )}

@@ -307,6 +320,33 @@ function ChatWindow({
))} + +
+ { + setChatHistory((prevChatHistory) => { + return [ + ...prevChatHistory, + { + question: q, + answer: { + parts: [], + sources: [], + }, + }, + ]; + }); + await getAnswer( + q, + spaces.map((s) => `${s.id}`), + ); + }} + /> +
)} diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx index 55f2928e..6fe26513 100644 --- a/apps/web/app/(dash)/home/page.tsx +++ b/apps/web/app/(dash)/home/page.tsx @@ -1,11 +1,12 @@ -import React from "react"; -import Menu from "../menu"; -import Header from "../header"; +"use client"; + +import React, { useEffect, useState } from "react"; import QueryInput from "./queryinput"; import { homeSearchParamsCache } from "@/lib/searchParams"; import { getSpaces } from "@/app/actions/fetchers"; +import { useRouter } from "next/navigation"; -async function Page({ +function Page({ searchParams, }: { searchParams: Record; @@ -13,12 +14,18 @@ async function Page({ // TODO: use this to show a welcome page/modal const { firstTime } = homeSearchParamsCache.parse(searchParams); - let spaces = await getSpaces(); + const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]); - if (!spaces.success) { - // TODO: handle this error properly. - spaces.data = []; - } + useEffect(() => { + getSpaces().then((res) => { + if (res.success && res.data) { + setSpaces(res.data); + } + // TODO: HANDLE ERROR + }); + }, []); + + const { push } = useRouter(); return (
@@ -26,7 +33,17 @@ async function Page({ {/*
hi {firstTime ? 'first time' : ''}
*/}
- + { + const newQ = + "/chat?q=" + + encodeURI(q) + + (spaces ? "&spaces=" + JSON.stringify(spaces) : ""); + + push(newQ); + }} + initialSpaces={spaces} + />
); diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx index d0c27b8d..ce45e36b 100644 --- a/apps/web/app/(dash)/home/queryinput.tsx +++ b/apps/web/app/(dash)/home/queryinput.tsx @@ -12,6 +12,9 @@ function QueryInput({ initialQuery = "", initialSpaces = [], disabled = false, + className, + mini = false, + handleSubmit, }: { initialQuery?: string; initialSpaces?: { @@ -19,32 +22,14 @@ function QueryInput({ name: string; }[]; disabled?: boolean; + className?: string; + mini?: boolean; + handleSubmit: (q: string, spaces: { id: number; name: string }[]) => void; }) { const [q, setQ] = useState(initialQuery); const [selectedSpaces, setSelectedSpaces] = useState([]); - const { push } = useRouter(); - - const parseQ = () => { - // preparedSpaces is list of spaces selected by user, with id and name - const preparedSpaces = initialSpaces - .filter((x) => selectedSpaces.includes(x.id)) - .map((x) => { - return { - id: x.id, - name: x.name, - }; - }); - - const newQ = - "/chat?q=" + - encodeURI(q) + - (selectedSpaces ? "&spaces=" + JSON.stringify(preparedSpaces) : ""); - - return newQ; - }; - const options = useMemo( () => initialSpaces.map((x) => ({ @@ -54,21 +39,43 @@ function QueryInput({ [initialSpaces], ); + const preparedSpaces = useMemo( + () => + initialSpaces + .filter((x) => selectedSpaces.includes(x.id)) + .map((x) => { + return { + id: x.id, + name: x.name, + }; + }), + [selectedSpaces, initialSpaces], + ); + return ( -
-
+
+
{/* input and action button */} -
push(parseQ())} className="flex gap-4 p-3"> + { + handleSubmit(q, preparedSpaces); + setQ(""); + }} + className="flex gap-4 p-3" + >