aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
authorDhravya <[email protected]>2024-04-08 18:12:16 -0700
committerDhravya <[email protected]>2024-04-08 18:12:16 -0700
commitf04fa3faf75c1b2c63f094632c15a528a98932c5 (patch)
treef2831ca93153a0b7698cb0517a35c0601f1b5ed3 /apps/web/src
parentmade it functional (diff)
downloadsupermemory-f04fa3faf75c1b2c63f094632c15a528a98932c5.tar.xz
supermemory-f04fa3faf75c1b2c63f094632c15a528a98932c5.zip
setup for multi chat
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/app/api/chat/route.ts62
-rw-r--r--apps/web/src/components/ChatMessage.tsx50
-rw-r--r--apps/web/src/components/Main.tsx52
3 files changed, 157 insertions, 7 deletions
diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/chat/route.ts
new file mode 100644
index 00000000..2cb03186
--- /dev/null
+++ b/apps/web/src/app/api/chat/route.ts
@@ -0,0 +1,62 @@
+import { db } from "@/server/db";
+import { eq } from "drizzle-orm";
+import { sessions, users } from "@/server/db/schema";
+import { type NextRequest, NextResponse } from "next/server";
+import { env } from "@/env";
+import { ChatHistory } from "../../../../types/memory";
+
+export const runtime = "edge";
+
+export async function POST(req: NextRequest) {
+ const token = req.cookies.get("next-auth.session-token")?.value ?? req.cookies.get("__Secure-authjs.session-token")?.value ?? req.cookies.get("authjs.session-token")?.value ?? req.headers.get("Authorization")?.replace("Bearer ", "");
+
+ const sessionData = await db.select().from(sessions).where(eq(sessions.sessionToken, token!))
+
+ if (!sessionData || sessionData.length === 0) {
+ return new Response(JSON.stringify({ message: "Invalid Key, session not found." }), { status: 404 });
+ }
+
+ const user = await db.select().from(users).where(eq(users.id, sessionData[0].userId)).limit(1)
+
+ if (!user || user.length === 0) {
+ return NextResponse.json({ message: "Invalid Key, session not found." }, { status: 404 });
+ }
+
+ const session = { session: sessionData[0], user: user[0] }
+
+ const query = new URL(req.url).searchParams.get("q");
+ const sourcesOnly = new URL(req.url).searchParams.get("sourcesOnly") ?? "false";
+
+ const chatHistory = await req.json() as {
+ 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
+ })
+ })
+
+ console.log(resp.status)
+
+ if (resp.status !== 200 || !resp.ok) {
+ const errorData = await resp.json();
+ console.log(errorData)
+ return new Response(JSON.stringify({ message: "Error in CF function", error: errorData }), { status: resp.status });
+ }
+
+ // Stream the response back to the client
+ const { readable, writable } = new TransformStream();
+ resp && resp.body!.pipeTo(writable);
+
+ return new Response(readable, { status: 200 });
+} \ No newline at end of file
diff --git a/apps/web/src/components/ChatMessage.tsx b/apps/web/src/components/ChatMessage.tsx
new file mode 100644
index 00000000..a8199758
--- /dev/null
+++ b/apps/web/src/components/ChatMessage.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
+import { User } from 'next-auth';
+import { User2 } from 'lucide-react';
+import Image from 'next/image';
+
+function ChatMessage({
+ message,
+ user,
+}: {
+ message: string;
+ user: User | 'ai';
+}) {
+ return (
+ <div className="flex flex-col gap-4">
+ <div
+ className={`font-bold ${!(user === 'ai') && 'text-xl '} flex flex-col md:flex-row items-center gap-4`}
+ >
+ <Avatar>
+ {user === 'ai' ? (
+ <Image
+ src="/logo.png"
+ width={48}
+ height={48}
+ alt="AI"
+ className="rounded-md w-12 h-12"
+ />
+ ) : user?.image ? (
+ <>
+ <AvatarImage
+ className="h-6 w-6 rounded-lg"
+ src={user?.image}
+ alt="user pfp"
+ />
+ <AvatarFallback>
+ {user?.name?.split(' ').map((n) => n[0])}{' '}
+ </AvatarFallback>
+ </>
+ ) : (
+ <User2 strokeWidth={1.3} className="h-6 w-6" />
+ )}
+ </Avatar>
+ <div className="ml-4">{message}</div>
+ </div>
+ <div className="w-full h-0.5 bg-gray-700 my-2 md:my-0 md:mx-4 mt-8"></div>
+ </div>
+ );
+}
+
+export { ChatMessage };
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx
index 86679dcf..a9111494 100644
--- a/apps/web/src/components/Main.tsx
+++ b/apps/web/src/components/Main.tsx
@@ -1,5 +1,5 @@
'use client';
-import { useEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import { FilterCombobox } from './Sidebar/FilterCombobox';
import { Textarea2 } from './ui/textarea';
import { ArrowRight } from 'lucide-react';
@@ -8,6 +8,9 @@ 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';
function supportsDVH() {
try {
@@ -24,7 +27,34 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
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>
+ >({});
+
+ // helper function to append a new msg
+ const appendToChatHistory = useCallback(
+ (role: 'user' | 'model', content: string) => {
+ setChatHistory((prev) => [
+ ...prev,
+ {
+ role,
+ parts: [{ text: content }],
+ },
+ ]);
+ },
+ [],
+ );
+
+ // This is the streamed AI response we get from the server.
const [aiResponse, setAIResponse] = useState('');
+
const [toBeParsed, setToBeParsed] = useState('');
const textArea = useRef<HTMLTextAreaElement>(null);
@@ -153,17 +183,28 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
hide ? '' : 'main-hidden',
)}
>
+ <div className="flex flex-col w-full">
+ {chatHistory.map((chat, index) => (
+ <ChatMessage
+ key={index}
+ message={chat.parts[0].text}
+ user={chat.role === 'model' ? 'ai' : session?.user!}
+ />
+ ))}
+ </div>
<h1 className="text-rgray-11 mt-auto w-full text-center text-3xl md:mt-0">
Ask your Second brain
</h1>
- <form onSubmit={async (e) => await getSearchResults(e)}>
+ <form
+ className="mt-auto h-max min-h-[3em] w-full resize-y flex-row items-start justify-center overflow-none 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)}
+ >
<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]',
+ '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),
@@ -181,9 +222,6 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
</div>
</Textarea2>
</form>
- {searchResults && (
- <SearchResults aiResponse={aiResponse} sources={searchResults} />
- )}
{width <= 768 && <MemoryDrawer hide={hide} />}
</motion.main>
);