aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
authorYash <[email protected]>2024-04-09 06:21:58 +0000
committerYash <[email protected]>2024-04-09 06:21:58 +0000
commitf4917ef8f6cb90235d142b7ace1328ead4449061 (patch)
tree1bd5c18b4719d58f2d6b9bfbaabbdffaff1fc5ae /apps/web/src
parenttest (diff)
parentremoved unnecessary files (diff)
downloadsupermemory-f4917ef8f6cb90235d142b7ace1328ead4449061.tar.xz
supermemory-f4917ef8f6cb90235d142b7ace1328ead4449061.zip
Merge branch 'new-ui' of https://github.com/Dhravya/supermemory into new-ui
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/app/api/chat/route.ts5
-rw-r--r--apps/web/src/components/ChatMessage.tsx2
-rw-r--r--apps/web/src/components/Main.tsx137
-rw-r--r--apps/web/src/components/QueryAI.tsx139
-rw-r--r--apps/web/src/components/SearchResults.tsx38
-rw-r--r--apps/web/src/contexts/MemoryContext.tsx2
6 files changed, 104 insertions, 219 deletions
diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/chat/route.ts
index 2cb03186..ef59fd43 100644
--- a/apps/web/src/app/api/chat/route.ts
+++ b/apps/web/src/app/api/chat/route.ts
@@ -31,22 +31,25 @@ export async function POST(req: NextRequest) {
chatHistory: ChatHistory[]
};
+ console.log("CHathistory", chatHistory)
if (!query) {
return new Response(JSON.stringify({ message: "Invalid query" }), { status: 400 });
}
+
const resp = await fetch(`https://cf-ai-backend.dhravya.workers.dev/chat?q=${query}&user=${session.user.email ?? session.user.name}&sourcesOnly=${sourcesOnly}`, {
headers: {
"X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY,
},
method: "POST",
body: JSON.stringify({
- chatHistory
+ chatHistory: chatHistory.chatHistory ?? []
})
})
console.log(resp.status)
+ console.log(resp.statusText)
if (resp.status !== 200 || !resp.ok) {
const errorData = await resp.json();
diff --git a/apps/web/src/components/ChatMessage.tsx b/apps/web/src/components/ChatMessage.tsx
index a8199758..114d0a48 100644
--- a/apps/web/src/components/ChatMessage.tsx
+++ b/apps/web/src/components/ChatMessage.tsx
@@ -7,9 +7,11 @@ import Image from 'next/image';
function ChatMessage({
message,
user,
+ sources,
}: {
message: string;
user: User | 'ai';
+ sources?: string[];
}) {
return (
<div className="flex flex-col gap-4">
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx
index 813c0d62..3f021906 100644
--- a/apps/web/src/components/Main.tsx
+++ b/apps/web/src/components/Main.tsx
@@ -7,10 +7,10 @@ import { MemoryDrawer } from "./MemoryDrawer";
import useViewport from "@/hooks/useViewport";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
-import SearchResults from "./SearchResults";
import { ChatHistory } from "../../types/memory";
import { ChatMessage } from "./ChatMessage";
import { useSession } from "next-auth/react";
+import { Card, CardContent } from "./ui/card";
function supportsDVH() {
try {
@@ -21,8 +21,6 @@ function supportsDVH() {
}
export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
- return <Chat sidebarOpen={sidebarOpen} />;
-
const [hide, setHide] = useState(false);
const [value, setValue] = useState("");
const { width } = useViewport();
@@ -37,7 +35,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
// 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>
+ Record<string, string[]>
>({});
// helper function to append a new msg
@@ -107,7 +105,36 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
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);
+ // 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 &&
+ chatHistory[chatHistory.length - 1].role === "model"
+ ) {
+ setChatHistory((prev: any) => {
+ const lastMessage = prev[prev.length - 1];
+ const newParts = [
+ ...lastMessage.parts,
+ { text: parsedPart.response },
+ ];
+ return [
+ ...prev.slice(0, prev.length - 1),
+ { ...lastMessage, parts: newParts },
+ ];
+ });
+ } else {
+ setChatHistory((prev) => [
+ ...prev,
+ {
+ role: "model",
+ parts: [{ text: parsedPart.response }],
+ },
+ ]);
+ }
}
} catch (error) {
// If parsing fails and it's not the last part, it's a malformed JSON
@@ -139,17 +166,35 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
e.preventDefault();
setIsAiLoading(true);
+ console.log(value);
+
+ appendToChatHistory("user", value);
+
const sourcesResponse = await fetch(
- `/api/query?sourcesOnly=true&q=${value}`,
+ `/api/chat?sourcesOnly=true&q=${value}`,
+ {
+ method: "POST",
+ body: JSON.stringify({
+ chatHistory,
+ }),
+ },
);
const sourcesInJson = (await sourcesResponse.json()) as {
ids: string[];
};
- setSearchResults(sourcesInJson.ids);
+ setSearchResults((prev) =>
+ Array.from(new Set([...prev, ...sourcesInJson.ids])),
+ );
- const response = await fetch(`/api/query?q=${value}`);
+ // TODO: PASS THE `SPACE` TO THE API
+ const response = await fetch(`/api/chat?q=${value}`, {
+ method: "POST",
+ body: JSON.stringify({
+ chatHistory,
+ }),
+ });
if (response.status !== 200) {
setIsAiLoading(false);
@@ -164,8 +209,10 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
// @ts-ignore
reader.read().then(function processText({ done, value }) {
if (done) {
- // setSearchResults(JSON.parse(result.replace('data: ', '')));
- // setIsAiLoading(false);
+ setIsAiLoading(false);
+ setToBeParsed("");
+ setValue("");
+
return;
}
@@ -177,7 +224,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
};
return (
- <main
+ <motion.main
data-sidebar-open={sidebarOpen}
ref={main}
className={cn(
@@ -189,43 +236,55 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
{chatHistory.map((chat, index) => (
<ChatMessage
key={index}
- message={chat.parts[0].text}
+ message={chat.parts.map((part) => part.text).join("")}
user={chat.role === "model" ? "ai" : session?.user!}
/>
))}
+ {searchResults.length > 0 && (
+ <div className="mt-4">
+ <h1>Related memories</h1>
+ <div className="grid gap-6">
+ {searchResults.map((value, index) => (
+ <Card key={index}>
+ <CardContent className="space-y-2">{value}</CardContent>
+ </Card>
+ ))}
+ </div>
+ </div>
+ )}
</div>
<h1 className="text-rgray-11 mt-auto w-full text-center text-3xl md:mt-0">
Ask your Second brain
</h1>
-
- <Textarea2
- ref={textArea}
- className="mt-auto h-max max-h-[30em] min-h-[3em] resize-y flex-row items-start justify-center overflow-auto py-5 md:mt-0 md:h-[20vh] md:resize-none md:flex-col md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
- textAreaProps={{
- placeholder: "Ask your SuperMemory...",
- className:
- "h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-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),
- }}
+ <form
+ className="overflow-none mt-auto h-max min-h-[3em] w-full resize-y flex-row items-start justify-center py-5 md:mt-0 md:h-[20vh] md:resize-none md:flex-col md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
+ onSubmit={async (e) => await getSearchResults(e)}
>
- <div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
- <FilterCombobox className="hidden md:flex" />
- <button
- type="submit"
- disabled={value.trim().length < 1}
- className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:ml-auto md:mt-0"
- >
- <ArrowRight className="h-5 w-5" />
- </button>
- </div>
- </Textarea2>
- {/* {searchResults && (
- <SearchResults aiResponse={aiResponse} sources={searchResults} />
- )} */}
+ <Textarea2
+ ref={textArea}
+ textAreaProps={{
+ 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]",
+ value,
+ autoFocus: true,
+ onChange: (e) => setValue(e.target.value),
+ }}
+ >
+ <div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
+ <FilterCombobox className="hidden md:flex" />
+ <button
+ type="submit"
+ disabled={value.trim().length < 1}
+ className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:ml-auto md:mt-0"
+ >
+ <ArrowRight className="h-5 w-5" />
+ </button>
+ </div>
+ </Textarea2>
+ </form>
{width <= 768 && <MemoryDrawer hide={hide} />}
- </main>
+ </motion.main>
);
}
diff --git a/apps/web/src/components/QueryAI.tsx b/apps/web/src/components/QueryAI.tsx
deleted file mode 100644
index 3cb14178..00000000
--- a/apps/web/src/components/QueryAI.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-'use client';
-
-import { Label } from './ui/label';
-import React, { useEffect, useState } from 'react';
-import { Input } from './ui/input';
-import { Button } from './ui/button';
-import SearchResults from './SearchResults';
-
-function QueryAI() {
- const [searchResults, setSearchResults] = useState<string[]>([]);
- const [isAiLoading, setIsAiLoading] = useState(false);
-
- const [aiResponse, setAIResponse] = useState('');
- const [input, setInput] = useState('');
- const [toBeParsed, setToBeParsed] = useState('');
-
- 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 = '';
-
- // 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
-
- // 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);
- }
- } 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);
- } else {
- // If it's the last part, it may be incomplete, so keep it
- remainingData = part;
- }
- }
- });
-
- // Update the toBeParsed state to only contain the unparsed remainder
- if (remainingData !== toBeParsed) {
- setToBeParsed(remainingData);
- }
- } catch (error) {
- console.error('Error parsing accumulated data: ', error);
- }
- };
-
- // Call the parsing function if there's data to be parsed
- if (toBeParsed) {
- tryParseAccumulatedData();
- }
- }, [toBeParsed]);
-
- const getSearchResults = async (e: React.FormEvent<HTMLFormElement>) => {
- e.preventDefault();
- setIsAiLoading(true);
-
- const sourcesResponse = await fetch(
- `/api/query?sourcesOnly=true&q=${input}`,
- );
-
- const sourcesInJson = (await sourcesResponse.json()) as {
- ids: string[];
- };
-
- setSearchResults(sourcesInJson.ids);
-
- const response = await fetch(`/api/query?q=${input}`);
-
- if (response.status !== 200) {
- setIsAiLoading(false);
- return;
- }
-
- if (response.body) {
- let reader = response.body.getReader();
- let decoder = new TextDecoder('utf-8');
- let result = '';
-
- // @ts-ignore
- reader.read().then(function processText({ done, value }) {
- if (done) {
- // setSearchResults(JSON.parse(result.replace('data: ', '')));
- // setIsAiLoading(false);
- return;
- }
-
- handleStreamData(decoder.decode(value));
-
- return reader.read().then(processText);
- });
- }
- };
-
- return (
- <div className="w-full max-w-2xl mx-auto">
- <form onSubmit={async (e) => await getSearchResults(e)} className="mt-8">
- <Label htmlFor="searchInput">Ask your SuperMemory</Label>
- <div className="flex flex-col md:flex-row md:w-full md:items-center space-y-2 md:space-y-0 md:space-x-2">
- <Input
- value={input}
- onChange={(e) => setInput(e.target.value)}
- placeholder="Search using AI... ✨"
- id="searchInput"
- />
- <Button
- disabled={isAiLoading}
- className="max-w-min md:w-full"
- type="submit"
- variant="default"
- >
- Ask AI
- </Button>
- </div>
- </form>
-
- {searchResults && (
- <SearchResults aiResponse={aiResponse} sources={searchResults} />
- )}
- </div>
- );
-}
-
-export default QueryAI;
diff --git a/apps/web/src/components/SearchResults.tsx b/apps/web/src/components/SearchResults.tsx
deleted file mode 100644
index 0445d0b4..00000000
--- a/apps/web/src/components/SearchResults.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-'use client'
-
-import React from 'react';
-import { Card, CardContent } from './ui/card';
-import Markdown from 'react-markdown';
-import remarkGfm from 'remark-gfm'
-
-function SearchResults({
- aiResponse,
- sources,
-}: {
- aiResponse: string;
- sources: string[];
-}) {
- return (
- <div
- style={{
- backgroundImage: `linear-gradient(to right, #E5D9F2, #CDC1FF)`,
- }}
- className="w-full max-w-2xl mx-auto px-4 py-6 space-y-6 border mt-4 rounded-xl"
- >
- <div className="text-start">
- <div className="text-xl text-black">
- <Markdown remarkPlugins={[remarkGfm]}>{aiResponse.replace('</s>', '')}</Markdown>
- </div>
- </div>
- <div className="grid gap-6">
- {sources.map((value, index) => (
- <Card key={index}>
- <CardContent className="space-y-2">{value}</CardContent>
- </Card>
- ))}
- </div>
- </div>
- );
-}
-
-export default SearchResults;
diff --git a/apps/web/src/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx
index 820736ff..3727c464 100644
--- a/apps/web/src/contexts/MemoryContext.tsx
+++ b/apps/web/src/contexts/MemoryContext.tsx
@@ -31,8 +31,6 @@ export const MemoryProvider: React.FC<
[spaces],
);
- console.log(spaces);
-
return (
<MemoryContext.Provider value={{ spaces, addSpace, deleteSpace }}>
{children}