diff options
| author | yxshv <[email protected]> | 2024-04-15 01:57:39 +0530 |
|---|---|---|
| committer | yxshv <[email protected]> | 2024-04-15 01:57:39 +0530 |
| commit | 0ce7c916ffcbf6515c3da521775c72861bddd53c (patch) | |
| tree | a23c4b9b8bce9b7124c8574ee6f60709b522c6ae /apps/web/src | |
| parent | space expand layout (diff) | |
| download | supermemory-0ce7c916ffcbf6515c3da521775c72861bddd53c.tar.xz supermemory-0ce7c916ffcbf6515c3da521775c72861bddd53c.zip | |
add profile and fix drawer scroll
Diffstat (limited to 'apps/web/src')
| -rw-r--r-- | apps/web/src/app/api/chat/route.ts | 72 | ||||
| -rw-r--r-- | apps/web/src/app/api/getCount/route.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/app/globals.css | 11 | ||||
| -rw-r--r-- | apps/web/src/app/layout.tsx | 1 | ||||
| -rw-r--r-- | apps/web/src/components/Main.tsx | 84 | ||||
| -rw-r--r-- | apps/web/src/components/MemoryDrawer.tsx | 22 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/AddMemoryDialog.tsx | 10 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/index.tsx | 102 | ||||
| -rw-r--r-- | apps/web/src/components/ui/dialog.tsx | 6 |
9 files changed, 245 insertions, 67 deletions
diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/chat/route.ts index c78c9484..374f39cd 100644 --- a/apps/web/src/app/api/chat/route.ts +++ b/apps/web/src/app/api/chat/route.ts @@ -69,39 +69,43 @@ export async function POST(req: NextRequest) { }); } - const resp = await fetch( - `https://cf-ai-backend.dhravya.workers.dev/chat?q=${query}&user=${session.user.email ?? session.user.name}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, - { - headers: { - "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, - }, - method: "POST", - body: JSON.stringify({ - chatHistory: chatHistory.chatHistory ?? [], - }), - }, - ); - - console.log("sourcesOnly", sourcesOnly); - - if (sourcesOnly == "true") { - const data = await resp.json(); - console.log("data", data); - return new Response(JSON.stringify(data), { status: 200 }); - } - - 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); + try { + const resp = await fetch( + `https://cf-ai-backend.dhravya.workers.dev/chat?q=${query}&user=${session.user.email ?? session.user.name}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, + { + headers: { + "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, + }, + method: "POST", + body: JSON.stringify({ + chatHistory: chatHistory.chatHistory ?? [], + }), + }, + ); + + console.log("sourcesOnly", sourcesOnly); + + if (sourcesOnly == "true") { + const data = await resp.json(); + console.log("data", data); + return new Response(JSON.stringify(data), { status: 200 }); + } + + 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 }); + } catch { + } - return new Response(readable, { status: 200 }); } diff --git a/apps/web/src/app/api/getCount/route.ts b/apps/web/src/app/api/getCount/route.ts index 3238e58a..9fe54f78 100644 --- a/apps/web/src/app/api/getCount/route.ts +++ b/apps/web/src/app/api/getCount/route.ts @@ -1,5 +1,5 @@ import { db } from "@/server/db"; -import { and, eq, sql } from "drizzle-orm"; +import { and, eq, ne, sql } from "drizzle-orm"; import { sessions, storedContent, users } from "@/server/db/schema"; import { type NextRequest, NextResponse } from "next/server"; @@ -66,7 +66,7 @@ export async function GET(req: NextRequest) { .where( and( eq(storedContent.user, session.user.id), - eq(storedContent.type, "page"), + ne(storedContent.type, "twitter-bookmark"), ), ); diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index e4523452..9a543be6 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -57,6 +57,17 @@ body { padding-bottom: 15dvh; } +.bottom-padding { + bottom: 20vh; + bottom: 20dvh; +} + +@media (min-width: 768px) { + .bottom-padding { + bottom: 0; + } +} + .chat-answer code { @apply bg-rgray-3 text-wrap rounded-md border border-rgray-5 p-1 text-sm text-rgray-11; } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index e5a447dc..b57c3d9c 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -2,7 +2,6 @@ import type { Metadata } from "next"; import { Roboto, Inter } from "next/font/google"; import "./globals.css"; -const roboto = Roboto({ weight: ["300", "400", "500"], subsets: ["latin"] }); const inter = Inter({ weight: ["300", "400", "500"], subsets: ["latin"] }); export const metadata: Metadata = { diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx index 68ee8d9f..79225eb5 100644 --- a/apps/web/src/components/Main.tsx +++ b/apps/web/src/components/Main.tsx @@ -45,6 +45,8 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]); + const [isStreaming, setIsStreaming] = useState(false) + useEffect(() => { const search = searchParams.get("q"); if (search && search.trim().length > 0) { @@ -241,6 +243,8 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { return; } + setIsStreaming(true) + if (response.body) { let reader = response.body?.getReader(); let decoder = new TextDecoder("utf-8"); @@ -259,14 +263,24 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { return reader?.read().then(processText); }); + } }; - const onSend = async () => { + const onSend = () => { + if (value.trim().length < 1) return setLayout("chat"); - await getSearchResults(); + getSearchResults(); }; + function onValueChange(e: React.ChangeEvent<HTMLTextAreaElement>) { + const value = e.target.value; + setValue(value); + const lines = countLines(e.target); + e.target.rows = Math.min(5, lines); + } + + return ( <> <AnimatePresence mode="wait"> @@ -284,6 +298,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { /> ) : ( <main + key='intial' data-sidebar-open={sidebarOpen} ref={main} className={cn( @@ -302,7 +317,57 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { Ask your second brain </h1> - <Textarea2 + <FilterSpaces + name={"Filter"} + onClose={() => { + textArea.current?.querySelector("textarea")?.focus(); + }} + side="top" + align="start" + className="bg-[#252525] mr-auto md:hidden" + selectedSpaces={selectedSpaces} + setSelectedSpaces={setSelectedSpaces} + /> + <Textarea2 + ref={textArea} + className="bg-rgray-2 md:hidden 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...", + className: + "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)) { + onSend(); + } + }, + }} + > + <div className="md:hidden text-rgray-11/70 ml-auto mt-auto flex h-full w-min items-center justify-center pb-3 pr-2"> + + <FilterSpaces + name={"Filter"} + onClose={() => { + textArea.current?.querySelector("textarea")?.focus(); + }} + className="hidden md:flex" + selectedSpaces={selectedSpaces} + setSelectedSpaces={setSelectedSpaces} + /> + <button + onClick={onSend} + 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 transition-[filter] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" + > + <ArrowUp className="h-5 w-5" /> + </button> + </div> + </Textarea2> + + <Textarea2 ref={textArea} exit={{ opacity: 0, @@ -325,6 +390,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { } }, }} + className="hidden md:flex" > <div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2"> <FilterSpaces @@ -344,10 +410,10 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { <ArrowRight className="h-5 w-5" /> </button> </div> - </Textarea2> - {width <= 768 && <MemoryDrawer hide={hide} />} - </main> + </Textarea2> + </main> )} + {width <= 768 && <MemoryDrawer hide={hide} />} </AnimatePresence> </> ); @@ -388,7 +454,7 @@ export function Chat({ "sidebar relative flex w-full flex-col items-end 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)]", )} > - <div className="scrollbar-none h-screen w-full overflow-y-auto px-2 md:px-5"> + <div className="scrollbar-none h-[70vh] md:h-screen w-full overflow-y-auto px-2 md:px-5"> {chatHistory.map((msg, i) => ( <ChatMessage index={i} key={i} isLast={i === chatHistory.length - 1}> <ChatQuestion>{msg.question}</ChatQuestion> @@ -404,12 +470,12 @@ export function Chat({ </ChatMessage> ))} </div> - <div className="from-rgray-2 via-rgray-2 to-rgray-2/0 absolute bottom-0 left-0 h-[30%] w-full bg-gradient-to-t" /> + <div className="from-rgray-2 via-rgray-2 to-rgray-2/0 absolute bottom-0 left-0 w-full bg-gradient-to-t" /> <div data-sidebar-open={sidebarOpen} className="absolute flex w-full items-center justify-center" > - <div className="animate-from-top fixed bottom-10 left-1/2 md:left-[auto] md:translate-x-0 -translate-x-1/2 mt-auto flex w-[90%] md:w-[50%] flex-col items-center justify-center gap-2"> + <div className="animate-from-top fixed bottom-padding md:bottom-10 left-1/2 md:left-[auto] md:translate-x-0 -translate-x-1/2 mt-auto flex w-[90%] md:w-[50%] flex-col items-center justify-center gap-2"> <FilterSpaces name={"Filter"} onClose={() => { diff --git a/apps/web/src/components/MemoryDrawer.tsx b/apps/web/src/components/MemoryDrawer.tsx index f1ca5d47..a71d3d19 100644 --- a/apps/web/src/components/MemoryDrawer.tsx +++ b/apps/web/src/components/MemoryDrawer.tsx @@ -32,16 +32,18 @@ export function MemoryDrawer({ className, hide = false, ...props }: Props) { )} handle={false} > - <button - onClick={() => - setActiveSnapPoint((prev) => (prev === 0.9 ? 0.1 : 0.9)) - } - className="bg-rgray-4 border-rgray-6 text-rgray-11 absolute left-1/2 top-0 flex w-fit -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-2 rounded-md border px-3 py-2" - > - <MemoryIcon className="h-7 w-7" /> - Memories - </button> - <MemoriesBar /> + <button + onClick={() => + setActiveSnapPoint((prev) => (prev === 0.9 ? 0.1 : 0.9)) + } + className="bg-rgray-4 border-rgray-6 text-rgray-11 absolute left-1/2 top-0 flex w-fit -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-2 rounded-md border px-3 py-2" + > + <MemoryIcon className="h-7 w-7" /> + Memories + </button> + <div className="w-full h-full overflow-y-auto"> + <MemoriesBar isOpen={true} /> + </div> </DrawerContent> <DrawerOverlay className="relative bg-transparent" /> </Drawer> diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx index 1406925c..63f0d122 100644 --- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx +++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx @@ -26,7 +26,7 @@ export function AddMemoryPage({ closeDialog, defaultSpaces, onAdd }: { closeDial const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>(defaultSpaces ?? []); return ( - <div className="md:w-[40vw]"> + <div className="max-w-[80vw] w-[80vw] md:w-[40vw]"> <DialogHeader> <DialogTitle>Add a web page to memory</DialogTitle> <DialogDescription> @@ -133,7 +133,7 @@ export function NoteAddPage({ closeDialog, defaultSpaces, onAdd }: { closeDialog } return ( - <div> + <div className="w-[80vw] md:w-auto"> <Input ref={inputRef} data-error="false" @@ -152,7 +152,7 @@ export function NoteAddPage({ closeDialog, defaultSpaces, onAdd }: { closeDialog setContent(editor.storage.markdown.getMarkdown()); }} extensions={[Markdown]} - className="novel-editor bg-rgray-4 border-rgray-7 dark mt-5 max-h-[60vh] min-h-[40vh] w-[50vw] overflow-y-auto rounded-lg border [&>div>div]:p-5" + className="novel-editor w-full bg-rgray-4 border-rgray-7 dark mt-5 max-h-[60vh] min-h-[40vh] md:w-[50vw] overflow-y-auto rounded-lg border [&>div>div]:p-5" /> <DialogFooter> <FilterSpaces @@ -243,7 +243,7 @@ export function SpaceAddPage({ closeDialog, onAdd }: { closeDialog: () => void, } return ( - <div className="md:w-[40vw]"> + <div className="w-[80vw] md:w-[40vw]"> <DialogHeader> <DialogTitle>Add a space</DialogTitle> </DialogHeader> @@ -376,7 +376,7 @@ export function AddExistingMemoryToSpace({ const [selected, setSelected] = useState<StoredContent[]>([]); return ( - <div className="md:w-[40vw]"> + <div className="w-[80vw] md:w-[40vw]"> <DialogHeader> <DialogTitle>Add an existing memory to {space.title}</DialogTitle> <DialogDescription> diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx index f87d516b..f1a25a85 100644 --- a/apps/web/src/components/Sidebar/index.tsx +++ b/apps/web/src/components/Sidebar/index.tsx @@ -1,12 +1,12 @@ "use client"; import { MemoryIcon } from "../../assets/Memories"; -import { Trash2, User2 } from "lucide-react"; +import { Box, LogOut, Trash2, User2 } from "lucide-react"; import React, { useEffect, useState } from "react"; import { MemoriesBar } from "./MemoriesBar"; import { AnimatePresence, motion } from "framer-motion"; import { Bin } from "@/assets/Bin"; import { Avatar, AvatarFallback, AvatarImage } from "@radix-ui/react-avatar"; -import { useSession } from "next-auth/react"; +import { signOut, useSession } from "next-auth/react"; import MessagePoster from "@/app/MessagePoster"; export type MenuItem = { @@ -61,6 +61,7 @@ export default function Sidebar({ </div> ), label: "Profile", + content: <ProfileTab open={selectedItem !== null} /> }, ]; @@ -88,6 +89,7 @@ export default function Sidebar({ setSelectedItem={setSelectedItem} /> <div className="mt-auto" /> + {/* <MenuItem item={{ label: "Trash", @@ -97,6 +99,7 @@ export default function Sidebar({ id="trash-button" setSelectedItem={setSelectedItem} /> + */} <MenuItem item={{ label: "Profile", @@ -118,11 +121,12 @@ export default function Sidebar({ </Avatar> </div> ), + content: <ProfileTab open={selectedItem !== null} /> }} selectedItem={selectedItem} setSelectedItem={setSelectedItem} /> - <MessagePoster jwt={jwt} /> + {/* <MessagePoster jwt={jwt} /> */} </div> <AnimatePresence> {selectedItem && <SubSidebar>{Subbar}</SubSidebar>} @@ -184,3 +188,95 @@ export function SubSidebar({ children }: { children?: React.ReactNode }) { </motion.div> ); } + +export function ProfileTab({ open }: { open: boolean }) { + + const { data: session } = useSession() + + const [tweetStat, setTweetStat] = useState<[number, number] | null>(); + const [memoryStat, setMemoryStat] = useState<[number, number] | null>(); + + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetch("/api/getCount").then(async resp => { + const data = await resp.json() as any; + setTweetStat([data.tweetsCount, data.tweetsLimit]) + setMemoryStat([data.pageCount, data.pageLimit]) + setLoading(false) + }) + }, [open]) + + return ( + <div className="text-rgray-11 h-full flex w-full font-normal flex-col items-start py-8 text-left"> + <div className="w-full px-8"> + <h1 className="w-full text-2xl font-medium">Profile</h1> + <div className="w-full mt-5 grid grid-cols-3"> + <img + className="rounded-full" + src={session?.user?.image ?? "/icons/white_without_bg.png"} + onError={(e) => { + (e.target as HTMLImageElement).src = "/icons/white_without_bg.png" + }} + /> + <div className="col-span-2 flex flex-col justify-center items-start"> + <h1 className="text-xl font-medium">{session?.user?.name}</h1> + <span> + {session?.user?.email} + </span> + <button + onClick={() => signOut()} + className="flex justify-center mt-auto gap-2 items-center bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70" + > + <LogOut className="w-4 h-4" /> + Logout + </button> + </div> + </div> + </div> + <div className="w-full mt-auto pt-8 px-8 border-t border-rgray-5"> + <h1 className="w-full text-xl flex items-center gap-2"> + <Box className="w-6 h-6" /> + Storage + </h1> + {loading ? ( + <div className="flex w-full my-5 gap-5 flex-col justify-center items-center"> + <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div> + <div className="bg-rgray-5 h-6 w-full animate-pulse rounded-md text-lg"></div> + </div> + ) : ( + <> + <div className="my-5"> + <h2 className="w-full text-md flex justify-between items-center"> + Memories + <div className="flex rounded-md bg-rgray-4 px-2 py-2 text-white/50 text-xs"> + {memoryStat?.join("/")} + </div> + </h2> + <div className="rounded-full overflow-hidden w-full h-5 bg-rgray-2 mt-2"> + <div style={{ + width: `${((memoryStat?.[0] ?? 0) / (memoryStat?.[1] ?? 100))*100}%`, + minWidth: memoryStat?.[0] ?? 0 > 0 ? '5%' : '0%' + }} className="rounded-full h-full bg-rgray-5" /> + </div> + </div> + <div className="my-5"> + <h2 className="w-full text-md flex justify-between items-center"> + Tweets + <div className="flex rounded-md bg-rgray-4 px-2 py-2 text-white/50 text-xs"> + {tweetStat?.join("/")} + </div> + </h2> + <div className="rounded-full overflow-hidden w-full h-5 bg-rgray-2 mt-2"> + <div style={{ + width: `${((tweetStat?.[0] ?? 0) / (tweetStat?.[1] ?? 100))*100}%` , + minWidth: tweetStat?.[0] ?? 0 > 0 ? '5%' : '0%' + }} className="rounded-full h-full bg-rgray-5" /> + </div> + </div> + </> + )} + </div> + </div> + ) +} diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx index bc36e749..12ccd5ea 100644 --- a/apps/web/src/components/ui/dialog.tsx +++ b/apps/web/src/components/ui/dialog.tsx @@ -38,7 +38,7 @@ const DialogContent = React.forwardRef< <DialogPrimitive.Content ref={ref} className={cn( - "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] border-rgray-6 bg-rgray-3 fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] border p-6 shadow-lg duration-200 sm:rounded-lg", + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] border-rgray-6 bg-rgray-3 fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] border p-6 shadow-lg duration-200 rounded-lg", className, )} {...props} @@ -59,7 +59,7 @@ const DialogHeader = ({ }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( - "flex flex-col space-y-1.5 text-center sm:text-left", + "flex flex-col space-y-1.5 text-left", className, )} {...props} @@ -73,7 +73,7 @@ const DialogFooter = ({ }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn( - "mt-5 flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", + "mt-5 flex flex-row sm:flex-row sm:justify-end sm:space-x-2", className, )} {...props} |