aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/web/app/(dash)/chat/chatWindow.tsx80
-rw-r--r--apps/web/app/(dash)/home/page.tsx37
-rw-r--r--apps/web/app/(dash)/home/queryinput.tsx96
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 "<justification>" 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"
>
<div className="w-full h-96">
- <QueryInput initialQuery={q} initialSpaces={[]} disabled />
+ <QueryInput
+ handleSubmit={() => {}}
+ initialQuery={q}
+ initialSpaces={[]}
+ disabled
+ />
</div>
</motion.div>
) : (
<div
- className="max-w-3xl flex mx-auto w-full flex-col mt-24"
+ className="max-w-3xl relative flex mx-auto w-full flex-col mt-24 pb-32"
key="chat"
>
{chatHistory.map((chat, idx) => (
<div
key={idx}
- className={`mt-8 ${idx != chatHistory.length - 1 ? "pb-2 border-b" : ""}`}
+ className={`mt-8 ${idx != chatHistory.length - 1 ? "pb-2 border-b border-b-gray-400" : ""}`}
>
<h2
className={cn(
- "text-white transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-2xl"
+ "text-white transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-2xl",
)}
>
{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(""),
)}
</Markdown>
</div>
@@ -307,6 +320,33 @@ function ChatWindow({
</div>
</div>
))}
+
+ <div className="fixed bottom-0 w-full max-w-3xl pb-4">
+ <QueryInput
+ mini
+ className="w-full shadow-md"
+ initialQuery={""}
+ initialSpaces={[]}
+ handleSubmit={async (q, spaces) => {
+ setChatHistory((prevChatHistory) => {
+ return [
+ ...prevChatHistory,
+ {
+ question: q,
+ answer: {
+ parts: [],
+ sources: [],
+ },
+ },
+ ];
+ });
+ await getAnswer(
+ q,
+ spaces.map((s) => `${s.id}`),
+ );
+ }}
+ />
+ </div>
</div>
)}
</AnimatePresence>
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<string, string | string[] | undefined>;
@@ -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 }[]>([]);
+
+ useEffect(() => {
+ getSpaces().then((res) => {
+ if (res.success && res.data) {
+ setSpaces(res.data);
+ }
+ // TODO: HANDLE ERROR
+ });
+ }, []);
- if (!spaces.success) {
- // TODO: handle this error properly.
- spaces.data = [];
- }
+ const { push } = useRouter();
return (
<div className="max-w-3xl h-full justify-center flex mx-auto w-full flex-col">
@@ -26,7 +33,17 @@ async function Page({
{/* <div className="">hi {firstTime ? 'first time' : ''}</div> */}
<div className="w-full h-96">
- <QueryInput initialSpaces={spaces.data} />
+ <QueryInput
+ handleSubmit={(q, spaces) => {
+ const newQ =
+ "/chat?q=" +
+ encodeURI(q) +
+ (spaces ? "&spaces=" + JSON.stringify(spaces) : "");
+
+ push(newQ);
+ }}
+ initialSpaces={spaces}
+ />
</div>
</div>
);
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<number[]>([]);
- 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 (
- <div>
- <div className="bg-secondary rounded-t-[24px]">
+ <div className={className}>
+ <div
+ className={`bg-secondary ${!mini ? "rounded-t-3xl" : "rounded-3xl"}`}
+ >
{/* input and action button */}
- <form action={async () => push(parseQ())} className="flex gap-4 p-3">
+ <form
+ action={async () => {
+ handleSubmit(q, preparedSpaces);
+ setQ("");
+ }}
+ className="flex gap-4 p-3"
+ >
<textarea
name="q"
cols={30}
- rows={4}
+ rows={mini ? 2 : 4}
className="bg-transparent pt-2.5 text-base text-[#989EA4] focus:text-foreground duration-200 tracking-[3%] outline-none resize-none w-full p-4"
placeholder="Ask your second brain..."
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
- if (!e.shiftKey) push(parseQ());
+ handleSubmit(q, preparedSpaces);
+ setQ("");
}
}}
onChange={(e) => setQ(e.target.value)}
@@ -84,24 +91,29 @@ function QueryInput({
<Image src={ArrowRightIcon} alt="Right arrow icon" />
</button>
</form>
-
- <Divider />
</div>
{/* selected sources */}
- <div className="flex items-center gap-6 p-2 h-auto bg-secondary rounded-b-[24px]">
- <MultipleSelector
- key={options.length}
- disabled={disabled}
- defaultOptions={options}
- onChange={(e) => setSelectedSpaces(e.map((x) => parseInt(x.value)))}
- placeholder="Focus on specific spaces..."
- emptyIndicator={
- <p className="text-center text-lg leading-10 text-gray-600 dark:text-gray-400">
- no results found.
- </p>
- }
- />
- </div>
+ {!mini && (
+ <>
+ <Divider />
+ <div className="flex items-center gap-6 p-2 h-auto bg-secondary rounded-b-3xl">
+ <MultipleSelector
+ key={options.length}
+ disabled={disabled}
+ defaultOptions={options}
+ onChange={(e) =>
+ setSelectedSpaces(e.map((x) => parseInt(x.value)))
+ }
+ placeholder="Focus on specific spaces..."
+ emptyIndicator={
+ <p className="text-center text-lg leading-10 text-gray-600 dark:text-gray-400">
+ no results found.
+ </p>
+ }
+ />
+ </div>
+ </>
+ )}
</div>
);
}