aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src/components
diff options
context:
space:
mode:
authorDhravya <[email protected]>2024-04-09 19:10:22 -0700
committerDhravya <[email protected]>2024-04-09 19:10:22 -0700
commitd72f902579815b14360d7cea756616bb981545f0 (patch)
treeb1672a97c13d049bff319817a74130f6cff921c3 /apps/web/src/components
parenthave a initial search query (diff)
downloadarchived-supermemory-d72f902579815b14360d7cea756616bb981545f0.tar.xz
archived-supermemory-d72f902579815b14360d7cea756616bb981545f0.zip
new-ui WORKS AND DONE AND DUSTED
Diffstat (limited to 'apps/web/src/components')
-rw-r--r--apps/web/src/components/Main.tsx193
1 files changed, 90 insertions, 103 deletions
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx
index 9efa9a81..3d0fc18a 100644
--- a/apps/web/src/components/Main.tsx
+++ b/apps/web/src/components/Main.tsx
@@ -1,32 +1,19 @@
-"use client";
-import { useCallback, useEffect, useRef, useState } from "react";
-import { FilterCombobox } from "./Sidebar/FilterCombobox";
-import { Textarea2 } from "./ui/textarea";
-import { ArrowRight, ArrowUp } from "lucide-react";
-import { MemoryDrawer } from "./MemoryDrawer";
-import useViewport from "@/hooks/useViewport";
-import { AnimatePresence, motion } from "framer-motion";
-import { cn, countLines } from "@/lib/utils";
-import { ChatHistory } from "../../types/memory";
-import { ChatAnswer, ChatMessage, ChatQuestion } from "./ChatMessage";
-import { useSession } from "next-auth/react";
-import { useRouter, useSearchParams } from "next/navigation";
-
-const dummyChatHistory: ChatHistory = {
- question: "who is dhravya?",
- answer: {
- parts: [
- {
- text: "Dhravya Shah is an 18-year-old full-stack developer based in Arizona, USA. He is a passionate developer who focuses on creating products that people love. Dhravya has a background in entrepreneurship, having been a 2x acquired founder and a participant in various hackathons. He is also involved in open-source contributions, content creation to inspire others in coding, and has a growing community of developers. Dhravya's work spans from creating AI-powered note-taking apps to personalized music companions and educational tools. Additionally, he is a guitarist, student, and active in sharing his experiences as a developer and entrepreneur",
- },
- ],
- sources: ["dhravya.dev"],
- },
-};
+'use client';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import { FilterCombobox } from './Sidebar/FilterCombobox';
+import { Textarea2 } from './ui/textarea';
+import { ArrowRight, ArrowUp } from 'lucide-react';
+import { MemoryDrawer } from './MemoryDrawer';
+import useViewport from '@/hooks/useViewport';
+import { AnimatePresence, motion } from 'framer-motion';
+import { cn, countLines } from '@/lib/utils';
+import { ChatHistory } from '../../types/memory';
+import { ChatAnswer, ChatMessage, ChatQuestion } from './ChatMessage';
+import { useRouter, useSearchParams } from 'next/navigation';
function supportsDVH() {
try {
- return CSS.supports("height: 100dvh");
+ return CSS.supports('height: 100dvh');
} catch {
return false;
}
@@ -37,37 +24,25 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
const router = useRouter();
const [hide, setHide] = useState(false);
- const [layout, setLayout] = useState<"chat" | "initial">("initial");
- const [value, setValue] = useState("");
+ const [layout, setLayout] = useState<'chat' | 'initial'>('initial');
+ const [value, setValue] = useState('');
const { width } = useViewport();
- const [searchResults, setSearchResults] = useState<string[]>([]);
const [isAiLoading, setIsAiLoading] = useState(false);
- const { data: session } = useSession();
-
// Variable to keep track of the chat history in this session
const [chatHistory, setChatHistory] = useState<ChatHistory[]>([]);
- // TEMPORARY solution: Basically this is to just keep track of the sources used for each chat message
- // Not a great solution
- const [chatTextSourceDict, setChatTextSourceDict] = useState<
- Record<string, string[]>
- >({});
-
- // This is the streamed AI response we get from the server.
- const [aiResponse, setAIResponse] = useState("");
-
- const [toBeParsed, setToBeParsed] = useState("");
+ const [toBeParsed, setToBeParsed] = useState('');
const textArea = useRef<HTMLDivElement>(null);
const main = useRef<HTMLDivElement>(null);
useEffect(() => {
- const search = searchParams.get("q");
+ const search = searchParams.get('q');
if (search && search.trim().length > 0) {
setValue(search);
onSend();
- router.push("/");
+ router.push('/');
}
}, []);
@@ -86,47 +61,38 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
}
}
- window.visualViewport?.addEventListener("resize", onResize);
+ window.visualViewport?.addEventListener('resize', onResize);
return () => {
- window.visualViewport?.removeEventListener("resize", onResize);
+ window.visualViewport?.removeEventListener('resize', onResize);
};
}, []);
- const handleStreamData = (newChunk: string) => {
- // Append the new chunk to the existing data to be parsed
- setToBeParsed((prev) => prev + newChunk);
- };
-
useEffect(() => {
// Define a function to try parsing the accumulated data
const tryParseAccumulatedData = () => {
// Attempt to parse the "toBeParsed" state as JSON
try {
// Split the accumulated data by the known delimiter "\n\n"
- const parts = toBeParsed.split("\n\n");
- let remainingData = "";
+ const parts = toBeParsed.split('\n\n');
+ let remainingData = '';
// Process each part to extract JSON objects
parts.forEach((part, index) => {
try {
- const parsedPart = JSON.parse(part.replace("data: ", "")); // Try to parse the part as JSON
+ const parsedPart = JSON.parse(part.replace('data: ', '')); // Try to parse the part as JSON
// If the part is the last one and couldn't be parsed, keep it to accumulate more data
if (index === parts.length - 1 && !parsedPart) {
remainingData = part;
} else if (parsedPart && parsedPart.response) {
- // If the part is parsable and has the "response" field, update the AI response state
- // setAIResponse((prev) => prev + parsedPart.response);
- // appendToChatHistory('model', parsedPart.response);
-
// Append to chat history in this way:
// If the last message was from the model, append to that message
// Otherwise, Start a new message from the model and append to that
if (chatHistory.length > 0) {
- setChatHistory((prev: any) => {
+ setChatHistory((prev: ChatHistory[]) => {
const lastMessage = prev[prev.length - 1];
const newParts = [
- ...lastMessage.parts,
+ ...lastMessage.answer.parts,
{ text: parsedPart.response },
];
return [
@@ -146,7 +112,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
} catch (error) {
// If parsing fails and it's not the last part, it's a malformed JSON
if (index !== parts.length - 1) {
- console.error("Malformed JSON part: ", part);
+ console.error('Malformed JSON part: ', part);
} else {
// If it's the last part, it may be incomplete, so keep it
remainingData = part;
@@ -159,7 +125,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
setToBeParsed(remainingData);
}
} catch (error) {
- console.error("Error parsing accumulated data: ", error);
+ console.error('Error parsing accumulated data: ', error);
}
};
@@ -169,23 +135,45 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
}
}, [toBeParsed]);
+ const modifyChatHistory = useCallback((old: ChatHistory[]) => {
+ const final: { role: 'user' | 'model'; parts: { text: string }[] }[] = [];
+ old.forEach((chat) => {
+ final.push({
+ role: 'user',
+ parts: [{ text: chat.question }],
+ });
+ final.push({
+ role: 'model',
+ parts: chat.answer.parts.map((part) => ({ text: part.text })),
+ });
+ });
+
+ return final;
+ }, []);
+
const getSearchResults = async () => {
setIsAiLoading(true);
const _value = value.trim();
- setValue("");
+ setValue('');
- // @dhravya, this is using temporary dummy data remove this before testing
- setChatHistory((prev) => [...prev, dummyChatHistory]);
- setTimeout(() => setIsAiLoading(false), 5000);
- return;
+ setChatHistory((prev) => [
+ ...prev,
+ {
+ question: _value,
+ answer: {
+ parts: [],
+ sources: [],
+ },
+ },
+ ]);
const sourcesResponse = await fetch(
`/api/chat?sourcesOnly=true&q=${_value}`,
{
- method: "POST",
+ method: 'POST',
body: JSON.stringify({
- chatHistory,
+ chatHistory: modifyChatHistory(chatHistory),
}),
},
);
@@ -194,15 +182,26 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
ids: string[];
};
- setSearchResults((prev) =>
- Array.from(new Set([...prev, ...(sourcesInJson.ids ?? [])])),
- );
+ setIsAiLoading(false)
+ setChatHistory((prev) => {
+ const lastMessage = prev[prev.length - 1];
+ return [
+ ...prev.slice(0, prev.length - 1),
+ {
+ ...lastMessage,
+ answer: {
+ parts: lastMessage.answer.parts,
+ sources: sourcesInJson.ids ?? [],
+ },
+ },
+ ];
+ });
// TODO: PASS THE `SPACE` TO THE API
const response = await fetch(`/api/chat?q=${_value}`, {
- method: "POST",
+ method: 'POST',
body: JSON.stringify({
- chatHistory,
+ chatHistory: modifyChatHistory(chatHistory),
}),
});
@@ -211,33 +210,21 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
return;
}
- setChatHistory((prev) => [
- ...prev,
- {
- question: _value,
- answer: {
- parts: [],
- sources: sourcesInJson.ids ?? [],
- },
- },
- ]);
-
if (response.body) {
let reader = response.body?.getReader();
- let decoder = new TextDecoder("utf-8");
- let result = "";
+ let decoder = new TextDecoder('utf-8');
+ let result = '';
// @ts-ignore
reader.read().then(function processText({ done, value }) {
if (done) {
setIsAiLoading(false);
- setToBeParsed("");
- setValue("");
+ setToBeParsed('');
+ setValue('');
return;
}
-
- handleStreamData(decoder.decode(value));
+ setToBeParsed((prev) => prev + decoder.decode(value));
return reader?.read().then(processText);
});
@@ -245,14 +232,14 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
};
const onSend = async () => {
- setLayout("chat");
+ setLayout('chat');
await getSearchResults();
};
return (
<>
<AnimatePresence mode="wait">
- {layout === "chat" ? (
+ {layout === 'chat' ? (
<Chat
key="chat"
isLoading={isAiLoading}
@@ -268,7 +255,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
ref={main}
className={cn(
"sidebar flex w-full flex-col items-end justify-center gap-5 px-5 pt-5 transition-[padding-left,padding-top,padding-right] delay-200 duration-200 md:items-center md:gap-10 md:px-72 [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 md:[&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)]",
- hide ? "" : "main-hidden",
+ hide ? '' : 'main-hidden',
)}
>
<h1 className="text-rgray-11 mt-auto w-full text-center text-3xl md:mt-0">
@@ -282,19 +269,19 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
y: 50,
}}
transition={{
- type: "tween",
+ type: 'tween',
duration: 0.2,
}}
textAreaProps={{
- placeholder: "Ask your SuperMemory...",
+ placeholder: 'Ask your SuperMemory...',
className:
- "h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 pt-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
+ 'h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 pt-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]',
value,
autoFocus: true,
onChange: (e) => setValue(e.target.value),
onKeyDown: (e) => {
console.log(e.key, e.ctrlKey, e.metaKey);
- if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
onSend();
}
},
@@ -303,7 +290,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
<div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
<FilterCombobox
onClose={() => {
- textArea.current?.querySelector("textarea")?.focus();
+ textArea.current?.querySelector('textarea')?.focus();
}}
className="hidden md:flex"
/>
@@ -363,7 +350,7 @@ export function Chat({
loading={i === chatHistory.length - 1 ? isLoading : false}
sources={msg.answer.sources}
>
- {msg.answer.parts.map((part) => part.text).join(" ")}
+ {msg.answer.parts.map((part) => part.text).join(' ')}
</ChatAnswer>
</ChatMessage>
))}
@@ -376,7 +363,7 @@ export function Chat({
<div className="animate-from-top fixed bottom-10 mt-auto flex w-[50%] flex-col items-start justify-center gap-2">
<FilterCombobox
onClose={() => {
- textArea.current?.querySelector("textarea")?.focus();
+ textArea.current?.querySelector('textarea')?.focus();
}}
side="top"
align="start"
@@ -386,15 +373,15 @@ export function Chat({
ref={textArea}
className="bg-rgray-2 h-auto w-full flex-row items-start justify-center overflow-auto px-3 md:items-center md:justify-center"
textAreaProps={{
- placeholder: "Ask your SuperMemory...",
+ placeholder: 'Ask your SuperMemory...',
className:
- "overflow-auto h-auto p-3 md:resize-none text-lg w-auto resize-y text-rgray-11 w-full",
+ 'overflow-auto h-auto p-3 md:resize-none text-lg w-auto resize-y text-rgray-11 w-full',
value,
rows: 1,
autoFocus: true,
onChange: onValueChange,
onKeyDown: (e) => {
- if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
askQuestion();
}
},