diff options
| author | Dhravya <[email protected]> | 2024-05-21 22:47:33 -0500 |
|---|---|---|
| committer | Dhravya <[email protected]> | 2024-05-21 22:47:33 -0500 |
| commit | a4f566aaa085ce2a871f12070f932fa7da557316 (patch) | |
| tree | e8ff7e3eb0a27ef47300f261975afdefae093b32 /apps | |
| parent | footer fix: Arrow color should be consistent, no need for stroke (diff) | |
| download | supermemory-a4f566aaa085ce2a871f12070f932fa7da557316.tar.xz supermemory-a4f566aaa085ce2a871f12070f932fa7da557316.zip | |
format documents
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/web-v2/src/app/(landing)/EmailInput.tsx | 2 | ||||
| -rw-r--r-- | apps/web-v2/src/app/(landing)/FeatureContent.tsx | 8 | ||||
| -rw-r--r-- | apps/web-v2/src/app/(landing)/Features.tsx | 11 | ||||
| -rw-r--r-- | apps/web-v2/src/app/(landing)/Navbar.tsx | 2 | ||||
| -rw-r--r-- | apps/web-v2/src/app/(landing)/page.tsx | 10 | ||||
| -rw-r--r-- | apps/web-v2/src/app/globals.css | 2 | ||||
| -rw-r--r-- | apps/web-v2/src/utils/cn.ts | 2 | ||||
| -rw-r--r-- | apps/web/drizzle.config.ts | 14 | ||||
| -rw-r--r-- | apps/web/src/actions/db.ts | 530 | ||||
| -rw-r--r-- | apps/web/src/app/api/chat/route.ts | 74 | ||||
| -rw-r--r-- | apps/web/src/app/globals.css | 32 | ||||
| -rw-r--r-- | apps/web/src/components/ChatMessage.tsx | 40 | ||||
| -rw-r--r-- | apps/web/src/components/MemoryDrawer.tsx | 24 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/DeleteConfirmation.tsx | 48 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/ExpandedSpace.tsx | 325 | ||||
| -rw-r--r-- | apps/web/src/contexts/MemoryContext.tsx | 157 | ||||
| -rw-r--r-- | apps/web/src/lib/utils.ts | 26 | ||||
| -rw-r--r-- | apps/web/types/memory.tsx | 4 |
18 files changed, 707 insertions, 604 deletions
diff --git a/apps/web-v2/src/app/(landing)/EmailInput.tsx b/apps/web-v2/src/app/(landing)/EmailInput.tsx index 3e4f44a0..9fd175b7 100644 --- a/apps/web-v2/src/app/(landing)/EmailInput.tsx +++ b/apps/web-v2/src/app/(landing)/EmailInput.tsx @@ -39,7 +39,7 @@ function EmailInput() { <input type="email" name="email" - className={`transition-width flex w-full items-center rounded-2xl bg-[#37485E] px-4 py-2 duration-300 focus:outline-none outline-none`} + className={`transition-width flex w-full items-center rounded-2xl bg-[#37485E] px-4 py-2 outline-none duration-300 focus:outline-none`} placeholder="Enter your email" value={email} required diff --git a/apps/web-v2/src/app/(landing)/FeatureContent.tsx b/apps/web-v2/src/app/(landing)/FeatureContent.tsx index ff9f5f55..7de64d53 100644 --- a/apps/web-v2/src/app/(landing)/FeatureContent.tsx +++ b/apps/web-v2/src/app/(landing)/FeatureContent.tsx @@ -3,19 +3,19 @@ export const features = [ title: "For Researchers", description: "Add content to collections and use it as a knowledge base for your research, link multiple sources together to get a better understanding of the topic.", - svg: <ResearchSvg /> + svg: <ResearchSvg />, }, { title: "For Content writers", description: "Save time and use the writing assistant to generate content based on your own saved collections and sources.", - svg: <ContentSvg /> + svg: <ContentSvg />, }, { title: "For Developers", description: "Talk to documentation websites, code snippets, etc. so you never have to google the same thing a hundred times.", - svg: <DeveloperSvg /> + svg: <DeveloperSvg />, }, ]; @@ -56,4 +56,4 @@ function DeveloperSvg() { <path d="m11.293 5.293 1.414 1.414-8 8-1.414-1.414 8-8Zm7-1 1.414 1.414-8 8-1.414-1.414 8-8Zm0 6 1.414 1.414-8 8-1.414-1.414 8-8Z" /> </svg> ); -}
\ No newline at end of file +} diff --git a/apps/web-v2/src/app/(landing)/Features.tsx b/apps/web-v2/src/app/(landing)/Features.tsx index c79f282f..f52f7523 100644 --- a/apps/web-v2/src/app/(landing)/Features.tsx +++ b/apps/web-v2/src/app/(landing)/Features.tsx @@ -19,7 +19,7 @@ export default function Features() { tabs.current.parentElement.style.height = `${tabs.current.clientHeight}px`; }; - function handleClickIndex(tab:number){ + function handleClickIndex(tab: number) { setTab(tab); } @@ -33,7 +33,7 @@ export default function Features() { {/* Carousel */} <div id="use-cases" - className="mx-auto max-w-xl px-4 sm:px-6 lg:max-w-6xl md:pt-40" + className="mx-auto max-w-xl px-4 sm:px-6 md:pt-40 lg:max-w-6xl" > <div className="space-y-12 lg:flex lg:space-x-12 lg:space-y-0 xl:space-x-24"> {/* Content */} @@ -53,7 +53,11 @@ export default function Features() { </div> {/* Tabs buttons */} <div className="mb-8 space-y-2 md:mb-0"> - <CardClick tab={tab} items={features} handleClickIndex={handleClickIndex} /> + <CardClick + tab={tab} + items={features} + handleClickIndex={handleClickIndex} + /> </div> </div> @@ -252,7 +256,6 @@ export default function Features() { </div> </div> </div> - </div> </section> ); diff --git a/apps/web-v2/src/app/(landing)/Navbar.tsx b/apps/web-v2/src/app/(landing)/Navbar.tsx index 375ea544..c7bc80d4 100644 --- a/apps/web-v2/src/app/(landing)/Navbar.tsx +++ b/apps/web-v2/src/app/(landing)/Navbar.tsx @@ -11,7 +11,7 @@ function Navbar() { <Link href={"/"} className="flex items-center p-3 opacity-50"> <Image src={Logo} alt="Supermemory logo" width={40} height={40} /> </Link> - <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center gap-8 p-3"> + <div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center gap-8 p-3"> <Link href={"#use-cases"} className="text-soft-foreground-text"> Use cases </Link> diff --git a/apps/web-v2/src/app/(landing)/page.tsx b/apps/web-v2/src/app/(landing)/page.tsx index 5f0787b4..9b2e63b5 100644 --- a/apps/web-v2/src/app/(landing)/page.tsx +++ b/apps/web-v2/src/app/(landing)/page.tsx @@ -11,8 +11,7 @@ export const runtime = "edge"; export default function Home() { return ( <main className="dark flex min-h-screen flex-col items-center overflow-x-hidden px-2 md:px-0"> - - <Navbar /> + <Navbar /> {/* Background gradients */} <div className="absolute left-0 top-0 z-[-1] h-full w-full"> @@ -32,9 +31,7 @@ export default function Home() { </div> <div className="overflow-x-hidden"> - <div - className="absolute right-0 top-[145%] h-40 w-[17%] overflow-x-hidden bg-[#369DFD] bg-opacity-20 blur-[110px]" - /> + <div className="absolute right-0 top-[145%] h-40 w-[17%] overflow-x-hidden bg-[#369DFD] bg-opacity-20 blur-[110px]" /> </div> </div> @@ -44,7 +41,6 @@ export default function Home() { {/* Features section */} <Features /> - <RotatingIcons /> <Cta /> @@ -52,8 +48,6 @@ export default function Home() { <Toaster /> <Footer /> - - </main> ); } diff --git a/apps/web-v2/src/app/globals.css b/apps/web-v2/src/app/globals.css index 98d57f65..67115e30 100644 --- a/apps/web-v2/src/app/globals.css +++ b/apps/web-v2/src/app/globals.css @@ -98,7 +98,7 @@ html { /* Handle */ ::-webkit-scrollbar-thumb { - background: #131F2C; + background: #131f2c; } /* Handle on hover */ diff --git a/apps/web-v2/src/utils/cn.ts b/apps/web-v2/src/utils/cn.ts index df73a557..cec6ac9e 100644 --- a/apps/web-v2/src/utils/cn.ts +++ b/apps/web-v2/src/utils/cn.ts @@ -1,6 +1,6 @@ import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; - + export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } diff --git a/apps/web/drizzle.config.ts b/apps/web/drizzle.config.ts index d83fb3c9..c4f5d5fe 100644 --- a/apps/web/drizzle.config.ts +++ b/apps/web/drizzle.config.ts @@ -1,15 +1,17 @@ import { type Config } from "drizzle-kit"; const localDb = { - url: process.env.LOCAL_DB_URL! -} + url: process.env.LOCAL_DB_URL!, +}; export default { schema: "./src/server/db/schema.ts", driver: process.env.LOCAL_DB_URL ? "better-sqlite" : "d1", - dbCredentials: process.env.LOCAL_DB_URL ? localDb : { - wranglerConfigPath: "./wrangler.toml", - dbName: "dev-d1-anycontext", - }, + dbCredentials: process.env.LOCAL_DB_URL + ? localDb + : { + wranglerConfigPath: "./wrangler.toml", + dbName: "dev-d1-anycontext", + }, out: "drizzle", } satisfies Config; diff --git a/apps/web/src/actions/db.ts b/apps/web/src/actions/db.ts index 59899639..59eb8976 100644 --- a/apps/web/src/actions/db.ts +++ b/apps/web/src/actions/db.ts @@ -10,7 +10,17 @@ import { StoredContent, } from "@/server/db/schema"; import { SearchResult } from "@/contexts/MemoryContext"; -import { like, eq, and, sql, exists, asc, notExists, inArray, notInArray } from "drizzle-orm"; +import { + like, + eq, + and, + sql, + exists, + asc, + notExists, + inArray, + notInArray, +} from "drizzle-orm"; import { union } from "drizzle-orm/sqlite-core"; import { env } from "@/env"; @@ -20,10 +30,10 @@ export async function searchMemoriesAndSpaces( opts?: { filter?: { memories?: boolean; spaces?: boolean }; range?: { offset: number; limit: number }; - memoriesRelativeToSpace?: { - fromSpaces?: number[]; - notInSpaces?: number[]; - } + memoriesRelativeToSpace?: { + fromSpaces?: number[]; + notInSpaces?: number[]; + }; }, ): Promise<SearchResult[]> { const user = await getUser(); @@ -32,26 +42,50 @@ export async function searchMemoriesAndSpaces( return []; } - const defaultWhere = and( - eq(storedContent.user, user.id), - like(storedContent.title, `%${query}%`), - ) - const extraWheres = [] - - if (opts?.memoriesRelativeToSpace) { - if (opts.memoriesRelativeToSpace.fromSpaces) { - extraWheres.push(exists(db.select().from(contentToSpace).where(and( - eq(contentToSpace.contentId, storedContent.id), - inArray(contentToSpace.spaceId, opts.memoriesRelativeToSpace.fromSpaces) - )))) - } - if (opts.memoriesRelativeToSpace.notInSpaces) { - extraWheres.push(notExists(db.select().from(contentToSpace).where(and( - eq(contentToSpace.contentId, storedContent.id), - inArray(contentToSpace.spaceId, opts.memoriesRelativeToSpace.notInSpaces) - )))) - } - } + const defaultWhere = and( + eq(storedContent.user, user.id), + like(storedContent.title, `%${query}%`), + ); + const extraWheres = []; + + if (opts?.memoriesRelativeToSpace) { + if (opts.memoriesRelativeToSpace.fromSpaces) { + extraWheres.push( + exists( + db + .select() + .from(contentToSpace) + .where( + and( + eq(contentToSpace.contentId, storedContent.id), + inArray( + contentToSpace.spaceId, + opts.memoriesRelativeToSpace.fromSpaces, + ), + ), + ), + ), + ); + } + if (opts.memoriesRelativeToSpace.notInSpaces) { + extraWheres.push( + notExists( + db + .select() + .from(contentToSpace) + .where( + and( + eq(contentToSpace.contentId, storedContent.id), + inArray( + contentToSpace.spaceId, + opts.memoriesRelativeToSpace.notInSpaces, + ), + ), + ), + ), + ); + } + } try { let searchMemoriesQuery = db @@ -62,8 +96,11 @@ export async function searchMemoriesAndSpaces( }) .from(storedContent) .where( - extraWheres.length == 2 ? and(and(...extraWheres), defaultWhere) : - extraWheres.length == 1 ? and(...extraWheres, defaultWhere) : defaultWhere + extraWheres.length == 2 + ? and(and(...extraWheres), defaultWhere) + : extraWheres.length == 1 + ? and(...extraWheres, defaultWhere) + : defaultWhere, ) .orderBy(asc(storedContent.savedAt)); @@ -105,19 +142,24 @@ export async function searchMemoriesAndSpaces( } export async function getMemoriesFromUrl(urls: string[]) { - const user = await getUser(); if (!user) { return []; } - return urls.length > 0 ? await db.select() - .from(storedContent) - .where(and( - inArray(storedContent.url, urls), - eq(storedContent.user, user.id) - )).all() : [] + return urls.length > 0 + ? await db + .select() + .from(storedContent) + .where( + and( + inArray(storedContent.url, urls), + eq(storedContent.user, user.id), + ), + ) + .all() + : []; } async function getUser() { @@ -154,20 +196,18 @@ async function getUser() { } export async function getSpace(id: number) { - const user = await getUser(); if (!user) { return null; } - - return (await db.select() - .from(space) - .where(and( - eq(space.id, id), - eq(space.user, user.id) - )))[0] + return ( + await db + .select() + .from(space) + .where(and(eq(space.id, id), eq(space.user, user.id))) + )[0]; } export async function addSpace(name: string, memories: number[]) { @@ -205,35 +245,30 @@ export async function addSpace(name: string, memories: number[]) { } export async function fetchContent(id: number) { - - const user = await getUser(); if (!user) { return null; } - const fetchedMemory = await db.select() - .from(storedContent) - .where(and( - eq(storedContent.id, id), - eq(storedContent.user, user.id) - )); - - const memory = fetchedMemory.length > 0 ? fetchedMemory[0] : null - - const spaces = memory ? await db.select() - .from(contentToSpace) - .where( - eq(contentToSpace.contentId, memory.id) - ) : [] - - - return { - memory, - spaces: spaces.map(s => s.spaceId) - } + const fetchedMemory = await db + .select() + .from(storedContent) + .where(and(eq(storedContent.id, id), eq(storedContent.user, user.id))); + const memory = fetchedMemory.length > 0 ? fetchedMemory[0] : null; + + const spaces = memory + ? await db + .select() + .from(contentToSpace) + .where(eq(contentToSpace.contentId, memory.id)) + : []; + + return { + memory, + spaces: spaces.map((s) => s.spaceId), + }; } export async function fetchContentForSpace( @@ -243,7 +278,6 @@ export async function fetchContentForSpace( limit: number; }, ) { - const user = await getUser(); if (!user) { @@ -260,19 +294,22 @@ export async function fetchContentForSpace( .from(contentToSpace) .where( and( - and( - eq(contentToSpace.spaceId, spaceId), - eq(contentToSpace.contentId, storedContent.id), - ), - exists( - db.select() - .from(space) - .where(and( - eq(space.user, user.id), - eq(space.id, contentToSpace.spaceId) - )) - ) - ) + and( + eq(contentToSpace.spaceId, spaceId), + eq(contentToSpace.contentId, storedContent.id), + ), + exists( + db + .select() + .from(space) + .where( + and( + eq(space.user, user.id), + eq(space.id, contentToSpace.spaceId), + ), + ), + ), + ), ), ), ) @@ -293,46 +330,45 @@ export async function fetchFreeMemories(range?: { return []; } - try { - const query = db - .select() - .from(storedContent) - .where( - and( - notExists( - db - .select() - .from(contentToSpace) - .where(eq(contentToSpace.contentId, storedContent.id)), - ), - eq(storedContent.user, user.id), - ), - ) - .orderBy(asc(storedContent.savedAt)); - - return range - ? await query.limit(range.limit).offset(range.offset) - : await query.all(); - } catch { - return [] - } + try { + const query = db + .select() + .from(storedContent) + .where( + and( + notExists( + db + .select() + .from(contentToSpace) + .where(eq(contentToSpace.contentId, storedContent.id)), + ), + eq(storedContent.user, user.id), + ), + ) + .orderBy(asc(storedContent.savedAt)); + return range + ? await query.limit(range.limit).offset(range.offset) + : await query.all(); + } catch { + return []; + } } export async function updateSpaceTitle(id: number, title: string) { - const user = await getUser(); if (!user) { return null; } - return (await db.update(space).set({ name: title }).where( - and( - eq(space.id, id), - eq(space.user, user.id) - ) - ).returning())[0]; + return ( + await db + .update(space) + .set({ name: title }) + .where(and(eq(space.id, id), eq(space.user, user.id))) + .returning() + )[0]; } export async function addMemory( @@ -387,16 +423,19 @@ export async function addMemory( .returning() : []; - if (content.type === 'note') { - addedMemory = (await db.update(storedContent) - .set({ - url: addedMemory.url + addedMemory.id - }) - .where(eq(storedContent.id, addedMemory.id)) - .returning())[0] - } + if (content.type === "note") { + addedMemory = ( + await db + .update(storedContent) + .set({ + url: addedMemory.url + addedMemory.id, + }) + .where(eq(storedContent.id, addedMemory.id)) + .returning() + )[0]; + } - console.log("adding with:", `${addedMemory.url}-${user.email}`) + console.log("adding with:", `${addedMemory.url}-${user.email}`); // Add to vectorDB const res = (await Promise.race([ fetch("https://cf-ai-backend.dhravya.workers.dev/add", { @@ -423,30 +462,41 @@ export async function addMemory( } export async function addContentInSpaces(id: number, contents: number[]) { - const user = await getUser(); if (!user) { return null; } - - const data = contents.length > 0 ? await db.insert(contentToSpace).values(contents.map(i => ({ - spaceId: id, - contentId: i - }))).returning() : [] - return data + const data = + contents.length > 0 + ? await db + .insert(contentToSpace) + .values( + contents.map((i) => ({ + spaceId: id, + contentId: i, + })), + ) + .returning() + : []; + return data; } export async function updateMemory( - id: number, - { title, content, spaces, removedFromSpaces: removeSpaces }: { - title?: string; - content?: string; - spaces?: number[]; - removedFromSpaces?: number[]; - } + id: number, + { + title, + content, + spaces, + removedFromSpaces: removeSpaces, + }: { + title?: string; + content?: string; + spaces?: number[]; + removedFromSpaces?: number[]; + }, ) { const user = await getUser(); @@ -454,96 +504,95 @@ export async function updateMemory( return null; } - let updatedMemory: StoredContent | null = null; + let updatedMemory: StoredContent | null = null; - if (title && content) { + if (title && content) { + const [prev] = await db + .select() + .from(storedContent) + .where(and(eq(storedContent.user, user.id), eq(storedContent.id, id))); - const [prev] = await db.select() - .from(storedContent) - .where(and( - eq(storedContent.user, user.id), - eq(storedContent.id, id) - )); - - if (!prev) { - return null - } + if (!prev) { + return null; + } - const newContent = { - ...(title ? { title }: {}), - ...(content ? { content }: {}), - } + const newContent = { + ...(title ? { title } : {}), + ...(content ? { content } : {}), + }; + + const updated = { + ...newContent, + ...prev, + }; + + console.log("adding with:", `${updated.url}-${user.email}`); + // Add to vectorDB + const res = (await Promise.race([ + fetch("https://cf-ai-backend.dhravya.workers.dev/edit", { + method: "POST", + headers: { + "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, + }, + body: JSON.stringify({ + pageContent: updated.content, + title: updated.title, + url: updated.url, + user: user.email, + uniqueUrl: updated.url, + }), + }), + new Promise((_, reject) => + setTimeout(() => reject(new Error("Request timed out")), 40000), + ), + ])) as Response; - const updated = { - ...newContent, - ...prev - } + [updatedMemory] = await db + .update(storedContent) + .set(newContent) + .where(and(eq(storedContent.id, id), eq(storedContent.user, user.id))) + .returning(); - console.log("adding with:", `${updated.url}-${user.email}`) - // Add to vectorDB - const res = (await Promise.race([ - fetch("https://cf-ai-backend.dhravya.workers.dev/edit", { - method: "POST", - headers: { - "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, - }, - body: JSON.stringify({ - pageContent: updated.content, - title: updated.title, - url: updated.url, - user: user.email, - uniqueUrl: updated.url, - }), - }), - new Promise((_, reject) => - setTimeout(() => reject(new Error("Request timed out")), 40000), - ), - ])) as Response; + console.log(updatedMemory, newContent); + } - [updatedMemory] = await db - .update(storedContent) - .set(newContent) - .where(and( - eq(storedContent.id, id), - eq(storedContent.user, user.id) - )) - .returning(); - - console.log(updatedMemory, newContent) - } - - if (!updatedMemory) { - [updatedMemory] = await db - .select() - .from(storedContent) - .where(and( - eq(storedContent.id, id), - eq(storedContent.user, user.id) - )) - } - - const removedFromSpaces = removeSpaces ? - removeSpaces.length > 0 ? - await db.delete(contentToSpace) - .where(and( - inArray(contentToSpace.spaceId, removeSpaces), - eq(contentToSpace.contentId, id) - )).returning() : [] - : spaces ? - spaces.length > 0 ? - await db.delete(contentToSpace) - .where(and( - notInArray(contentToSpace.spaceId, spaces), - eq(contentToSpace.contentId, id) - )).returning() - : await db.delete(contentToSpace) - .where( - eq(contentToSpace.contentId, id) - ) - : []; + if (!updatedMemory) { + [updatedMemory] = await db + .select() + .from(storedContent) + .where(and(eq(storedContent.id, id), eq(storedContent.user, user.id))); + } + + const removedFromSpaces = removeSpaces + ? removeSpaces.length > 0 + ? await db + .delete(contentToSpace) + .where( + and( + inArray(contentToSpace.spaceId, removeSpaces), + eq(contentToSpace.contentId, id), + ), + ) + .returning() + : [] + : spaces + ? spaces.length > 0 + ? await db + .delete(contentToSpace) + .where( + and( + notInArray(contentToSpace.spaceId, spaces), + eq(contentToSpace.contentId, id), + ), + ) + .returning() + : await db + .delete(contentToSpace) + .where(eq(contentToSpace.contentId, id)) + : []; const addedToSpaces = - (spaces && spaces.length > 0) + spaces && spaces.length > 0 ? await db .insert(contentToSpace) .values( @@ -552,20 +601,24 @@ export async function updateMemory( spaceId: s, })), ) - .onConflictDoNothing() + .onConflictDoNothing() .returning() : []; - const resultedSpaces = (await db.select() - .from(contentToSpace) - .where(eq(contentToSpace.contentId, id)) - .all()).map(i => i.spaceId) ?? []; + const resultedSpaces = + ( + await db + .select() + .from(contentToSpace) + .where(eq(contentToSpace.contentId, id)) + .all() + ).map((i) => i.spaceId) ?? []; return { memory: updatedMemory, addedToSpaces, - removedFromSpaces, - resultedSpaces + removedFromSpaces, + resultedSpaces, }; } @@ -600,25 +653,24 @@ export async function deleteMemory(id: number) { .where(and(eq(storedContent.user, user.id), eq(storedContent.id, id))) .returning(); - if (deleted) { - - console.log("adding with:", `${deleted.url}-${user.email}`) - const res = (await Promise.race([ - fetch(`https://cf-ai-backend.dhravya.workers.dev/delete` , { - method: "DELETE", - headers: { - "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, - }, - body: JSON.stringify({ - websiteUrl: deleted.url, - user: user.email - }) - }), - new Promise((_, reject) => - setTimeout(() => reject(new Error("Request timed out")), 40000), - ), - ])) as Response; - } + if (deleted) { + console.log("adding with:", `${deleted.url}-${user.email}`); + const res = (await Promise.race([ + fetch(`https://cf-ai-backend.dhravya.workers.dev/delete`, { + method: "DELETE", + headers: { + "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, + }, + body: JSON.stringify({ + websiteUrl: deleted.url, + user: user.email, + }), + }), + new Promise((_, reject) => + setTimeout(() => reject(new Error("Request timed out")), 40000), + ), + ])) as Response; + } return deleted; } diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/chat/route.ts index 374f39cd..c815070b 100644 --- a/apps/web/src/app/api/chat/route.ts +++ b/apps/web/src/app/api/chat/route.ts @@ -69,43 +69,41 @@ export async function POST(req: NextRequest) { }); } - 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 { - } + 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 {} } diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 9a543be6..bed9278b 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -58,22 +58,22 @@ body { } .bottom-padding { - bottom: 20vh; - bottom: 20dvh; + bottom: 20vh; + bottom: 20dvh; } @media (min-width: 768px) { - .bottom-padding { - bottom: 0; - } + .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; +.chat-answer code { + @apply bg-rgray-3 border-rgray-5 text-rgray-11 text-wrap rounded-md border p-1 text-sm; } .novel-editor pre { - @apply bg-rgray-3 rounded-md border border-rgray-5 p-4 my-5 text-sm text-rgray-11; + @apply bg-rgray-3 border-rgray-5 text-rgray-11 my-5 rounded-md border p-4 text-sm; } .chat-answer h1 { @@ -81,19 +81,19 @@ body { } .chat-answer a { - @apply underline underline-offset-1 opacity-90 hover:opacity-100; + @apply underline underline-offset-1 opacity-90 hover:opacity-100; } .chat-answer img { - @apply rounded-md font-medium my-5; + @apply my-5 rounded-md font-medium; } .tippy-box { - @apply bg-rgray-3 text-rgray-11 border border-rgray-5 rounded-md py-0; + @apply bg-rgray-3 text-rgray-11 border-rgray-5 rounded-md border py-0; } .tippy-content #slash-command { - @apply text-rgray-11 bg-transparent border-none; + @apply text-rgray-11 border-none bg-transparent; } #slash-command button { @@ -101,18 +101,18 @@ body { } #slash-command button div:first-child { - @apply text-rgray-11 bg-rgray-4 border-rgray-5 ; + @apply text-rgray-11 bg-rgray-4 border-rgray-5; } #slash-command button.novel-bg-stone-100 { @apply bg-rgray-1; } -.novel-editor [data-type=taskList] > li { +.novel-editor [data-type="taskList"] > li { @apply my-0; } -.novel-editor input[type=checkbox] { +.novel-editor input[type="checkbox"] { @apply accent-rgray-4 rounded-md; background: var(--gray-4) !important; @@ -120,7 +120,7 @@ body { } .novel-editor .is-empty::before { - content: 'Press \'/\' for commands' !important; + content: "Press '/' for commands" !important; } .novel-editor h1 { diff --git a/apps/web/src/components/ChatMessage.tsx b/apps/web/src/components/ChatMessage.tsx index 0ab22271..58ef9870 100644 --- a/apps/web/src/components/ChatMessage.tsx +++ b/apps/web/src/components/ChatMessage.tsx @@ -12,7 +12,7 @@ export function ChatAnswer({ loading = false, }: { children: string; - sources?: ChatHistory['answer']['sources']; + sources?: ChatHistory["answer"]["sources"]; loading?: boolean; }) { return ( @@ -30,25 +30,25 @@ export function ChatAnswer({ <SpaceIcon className="h-6 w-6 -translate-y-[2px]" /> Related Memories </h1> - <div className="animate-fade-in gap-1 -mt-3 flex items-center justify-start opacity-0 [animation-duration:1s]"> - {sources?.map((source) => source.isNote ? ( - <button - className="bg-rgray-3 flex items-center justify-center gap-2 rounded-full py-1 pl-2 pr-3 text-sm" - > - <Text className="w-4 h-4" /> - {source.source} - </button> - ) : ( - <a - className="bg-rgray-3 flex items-center justify-center gap-2 rounded-full py-1 pl-2 pr-3 text-sm" - key={source.source} - href={source.source} - target="_blank" - > - <Globe className="h-4 w-4" /> - {cleanUrl(source.source)} - </a> - ))} + <div className="animate-fade-in -mt-3 flex items-center justify-start gap-1 opacity-0 [animation-duration:1s]"> + {sources?.map((source) => + source.isNote ? ( + <button className="bg-rgray-3 flex items-center justify-center gap-2 rounded-full py-1 pl-2 pr-3 text-sm"> + <Text className="h-4 w-4" /> + {source.source} + </button> + ) : ( + <a + className="bg-rgray-3 flex items-center justify-center gap-2 rounded-full py-1 pl-2 pr-3 text-sm" + key={source.source} + href={source.source} + target="_blank" + > + <Globe className="h-4 w-4" /> + {cleanUrl(source.source)} + </a> + ), + )} </div> </> )} diff --git a/apps/web/src/components/MemoryDrawer.tsx b/apps/web/src/components/MemoryDrawer.tsx index a71d3d19..14283281 100644 --- a/apps/web/src/components/MemoryDrawer.tsx +++ b/apps/web/src/components/MemoryDrawer.tsx @@ -32,18 +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> - <div className="w-full h-full overflow-y-auto"> - <MemoriesBar isOpen={true} /> - </div> + <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="h-full w-full overflow-y-auto"> + <MemoriesBar isOpen={true} /> + </div> </DrawerContent> <DrawerOverlay className="relative bg-transparent" /> </Drawer> diff --git a/apps/web/src/components/Sidebar/DeleteConfirmation.tsx b/apps/web/src/components/Sidebar/DeleteConfirmation.tsx index 9324b147..7955df0d 100644 --- a/apps/web/src/components/Sidebar/DeleteConfirmation.tsx +++ b/apps/web/src/components/Sidebar/DeleteConfirmation.tsx @@ -1,18 +1,30 @@ -import { Dialog, DialogContent, DialogTrigger, DialogTitle, DialogDescription, DialogClose, DialogFooter } from "../ui/dialog"; +import { + Dialog, + DialogContent, + DialogTrigger, + DialogTitle, + DialogDescription, + DialogClose, + DialogFooter, +} from "../ui/dialog"; -export default function DeleteConfirmation({ onDelete, trigger = true, children }: { trigger?: boolean, onDelete?: () => void; children: React.ReactNode }) { - return ( - <Dialog> - {trigger ? ( - <DialogTrigger asChild> - {children} - </DialogTrigger> - ) : ( - <> - {children} - </> - )} - <DialogContent> +export default function DeleteConfirmation({ + onDelete, + trigger = true, + children, +}: { + trigger?: boolean; + onDelete?: () => void; + children: React.ReactNode; +}) { + return ( + <Dialog> + {trigger ? ( + <DialogTrigger asChild>{children}</DialogTrigger> + ) : ( + <>{children}</> + )} + <DialogContent> <DialogTitle className="text-xl">Are you sure?</DialogTitle> <DialogDescription className="text-md"> You will not be able to recover this it. @@ -21,7 +33,7 @@ export default function DeleteConfirmation({ onDelete, trigger = true, children <DialogClose type={undefined} onClick={onDelete} - className="ml-auto flex items-center justify-center rounded-md text-red-400 bg-red-100/10 px-3 py-2 transition hover:bg-red-100/5 focus-visible:bg-red-100/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-100/30" + className="ml-auto flex items-center justify-center rounded-md bg-red-100/10 px-3 py-2 text-red-400 transition hover:bg-red-100/5 focus-visible:bg-red-100/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-100/30" > Delete </DialogClose> @@ -29,7 +41,7 @@ export default function DeleteConfirmation({ onDelete, trigger = true, children Cancel </DialogClose> </DialogFooter> - </DialogContent> - </Dialog> - ) + </DialogContent> + </Dialog> + ); } diff --git a/apps/web/src/components/Sidebar/ExpandedSpace.tsx b/apps/web/src/components/Sidebar/ExpandedSpace.tsx index 9e46f3fb..55d3f3f8 100644 --- a/apps/web/src/components/Sidebar/ExpandedSpace.tsx +++ b/apps/web/src/components/Sidebar/ExpandedSpace.tsx @@ -1,33 +1,52 @@ -import { fetchContentForSpace, getSpace } from "@/actions/db" -import { useMemory } from "@/contexts/MemoryContext" -import { StoredContent, StoredSpace } from '@/server/db/schema' -import { Edit3, Loader, Plus, Search, Sparkles, StickyNote, Text, Undo2 } from "lucide-react" -import { useEffect, useRef, useState } from "react" -import { Input, InputWithIcon } from "../ui/input" -import { useDebounce } from "@/hooks/useDebounce" -import { useAutoAnimate } from "@formkit/auto-animate/react" -import { AddMemoryModal, MemoryItem } from "./MemoriesBar" -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu" -import { DialogTrigger } from "../ui/dialog" - -export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => void; }) { - - const { updateMemory, updateSpace, search } = useMemory() +import { fetchContentForSpace, getSpace } from "@/actions/db"; +import { useMemory } from "@/contexts/MemoryContext"; +import { StoredContent, StoredSpace } from "@/server/db/schema"; +import { + Edit3, + Loader, + Plus, + Search, + Sparkles, + StickyNote, + Text, + Undo2, +} from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import { Input, InputWithIcon } from "../ui/input"; +import { useDebounce } from "@/hooks/useDebounce"; +import { useAutoAnimate } from "@formkit/auto-animate/react"; +import { AddMemoryModal, MemoryItem } from "./MemoriesBar"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; +import { DialogTrigger } from "../ui/dialog"; + +export function ExpandedSpace({ + spaceId, + back, +}: { + spaceId: number; + back: () => void; +}) { + const { updateMemory, updateSpace, search } = useMemory(); const [parent, enableAnimations] = useAutoAnimate(); - const inputRef = useRef<HTMLInputElement>(null); + const inputRef = useRef<HTMLInputElement>(null); - const [contentForSpace, setContentForSpace] = useState<StoredContent[]>([]) + const [contentForSpace, setContentForSpace] = useState<StoredContent[]>([]); - const [lastUpdatedTitle, setLastUpdatedTitle] = useState<string | null>(null); + const [lastUpdatedTitle, setLastUpdatedTitle] = useState<string | null>(null); - const [title, setTitle] = useState<string>(''); - const debouncedTitle = useDebounce(title, 500) + const [title, setTitle] = useState<string>(""); + const debouncedTitle = useDebounce(title, 500); - const [loading, setLoading] = useState(true) + const [loading, setLoading] = useState(true); - const [saveLoading, setSaveLoading] = useState(false); + const [saveLoading, setSaveLoading] = useState(false); const [searchQuery, setSearcyQuery] = useState(""); const [searchLoading, setSearchLoading] = useState(false); @@ -35,33 +54,34 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => const [searchResults, setSearchResults] = useState<StoredContent[]>([]); - const [addMemoryState, setAddMemoryState] = useState< "page" | "note" | "existing-memory" | "space" | null >(null); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - - - useEffect(() => { - (async () => { - const title = (await getSpace(spaceId))?.name ?? ""; - setTitle(title) - setLastUpdatedTitle(title) - setContentForSpace((await fetchContentForSpace(spaceId)) ?? []) - setLoading(false) - })(); - }, []) - - useEffect(() => { - if (debouncedTitle.trim().length < 1 || debouncedTitle.trim() === lastUpdatedTitle?.trim()) return - (async () => { - setSaveLoading(true) - await updateSpace(spaceId, debouncedTitle.trim()) - setLastUpdatedTitle(debouncedTitle) - setSaveLoading(false) - })() - }, [debouncedTitle]) + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + useEffect(() => { + (async () => { + const title = (await getSpace(spaceId))?.name ?? ""; + setTitle(title); + setLastUpdatedTitle(title); + setContentForSpace((await fetchContentForSpace(spaceId)) ?? []); + setLoading(false); + })(); + }, []); + useEffect(() => { + if ( + debouncedTitle.trim().length < 1 || + debouncedTitle.trim() === lastUpdatedTitle?.trim() + ) + return; + (async () => { + setSaveLoading(true); + await updateSpace(spaceId, debouncedTitle.trim()); + setLastUpdatedTitle(debouncedTitle); + setSaveLoading(false); + })(); + }, [debouncedTitle]); useEffect(() => { const q = query.trim(); @@ -73,56 +93,68 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => setSearchLoading(true); (async () => { - setSearchResults((await search(q, { - filter: { spaces: false }, - memoriesRelativeToSpace: { - fromSpaces: [spaceId] - } - })).map(i => i.memory!)); + setSearchResults( + ( + await search(q, { + filter: { spaces: false }, + memoriesRelativeToSpace: { + fromSpaces: [spaceId], + }, + }) + ).map((i) => i.memory!), + ); setSearchLoading(false); })(); }, [query]); - - if (loading) { - return ( - <div className="h-full w-full flex justify-center items-center"> - <Loader className="w-5 h-5 animate-spin" /> - </div> - ) - } + if (loading) { + return ( + <div className="flex h-full w-full items-center justify-center"> + <Loader className="h-5 w-5 animate-spin" /> + </div> + ); + } return ( <div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left"> - <div className="px-8 flex justify-start items-center w-full gap-2"> - <button onClick={back} className="transition rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7"> - <Undo2 className="w-5 h-5" /> - </button> - <Input - ref={inputRef} - data-error="false" - className="w-full border-none p-0 text-xl ring-0 placeholder:text-white/30 placeholder:transition placeholder:duration-500 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400" - placeholder="Title of the space" - data-modal-autofocus - value={title} - onChange={(e) => setTitle(e.target.value)} - /> - <button onClick={() => { - inputRef.current?.focus() - inputRef.current?.animate({ - opacity: [1, 0.2, 1] - }, { - duration: 100 - }) - }} className="transition rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7"> - {saveLoading ? ( - <Loader className="w-5 h-5 opacity-70 animate-spin" /> - ) : ( - <Edit3 className="w-5 h-5 opacity-70" /> - )} - </button> - </div> - <div className="px-8 w-full"> + <div className="flex w-full items-center justify-start gap-2 px-8"> + <button + onClick={back} + className="focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7 rounded-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2" + > + <Undo2 className="h-5 w-5" /> + </button> + <Input + ref={inputRef} + data-error="false" + className="w-full border-none p-0 text-xl ring-0 placeholder:text-white/30 placeholder:transition placeholder:duration-500 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400" + placeholder="Title of the space" + data-modal-autofocus + value={title} + onChange={(e) => setTitle(e.target.value)} + /> + <button + onClick={() => { + inputRef.current?.focus(); + inputRef.current?.animate( + { + opacity: [1, 0.2, 1], + }, + { + duration: 100, + }, + ); + }} + className="focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7 rounded-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2" + > + {saveLoading ? ( + <Loader className="h-5 w-5 animate-spin opacity-70" /> + ) : ( + <Edit3 className="h-5 w-5 opacity-70" /> + )} + </button> + </div> + <div className="w-full px-8"> <InputWithIcon placeholder="Search" icon={ @@ -136,23 +168,28 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => value={searchQuery} onChange={(e) => setSearcyQuery(e.target.value)} /> - </div> - <div className="w-full px-8 mt-2"> - <AddMemoryModal onAdd={(data) => { - if (!data) { - setLoading(true); - (async () => { - const title = (await getSpace(spaceId))?.name ?? ""; - setTitle(title) - setLastUpdatedTitle(title) - setContentForSpace((await fetchContentForSpace(spaceId)) ?? []) - setLoading(false) - })(); - } else if (Object.hasOwn(data, "url")) { - const _data = data as StoredContent; - setContentForSpace(prev => [...prev, _data]) - } - }} data={{ space: { title, id: spaceId }, notInSpaces: [spaceId] }} defaultSpaces={[spaceId]} type={addMemoryState}> + </div> + <div className="mt-2 w-full px-8"> + <AddMemoryModal + onAdd={(data) => { + if (!data) { + setLoading(true); + (async () => { + const title = (await getSpace(spaceId))?.name ?? ""; + setTitle(title); + setLastUpdatedTitle(title); + setContentForSpace((await fetchContentForSpace(spaceId)) ?? []); + setLoading(false); + })(); + } else if (Object.hasOwn(data, "url")) { + const _data = data as StoredContent; + setContentForSpace((prev) => [...prev, _data]); + } + }} + data={{ space: { title, id: spaceId }, notInSpaces: [spaceId] }} + defaultSpaces={[spaceId]} + type={addMemoryState} + > <DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}> <DropdownMenuTrigger asChild> <button className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2"> @@ -194,47 +231,57 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => </DropdownMenuContent> </DropdownMenu> </AddMemoryModal> - </div> + </div> <div ref={parent} className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5" > - {query.trim().length > 0 ? ( + {query.trim().length > 0 ? ( <> {searchResults.map((memory, i) => ( - <MemoryItem - removeFromSpace={async () => { - await updateMemory(memory.id, { - removedFromSpaces: [spaceId] - }) - setContentForSpace(prev => prev.filter(s => s.id !== memory.id)) - setSearchResults(prev => prev.filter(i => i.id !== memory.id)) - }} - {...memory!} - key={i} - onDelete={() => { - setContentForSpace(prev => prev.filter(s => s.id !== memory.id)) - setSearchResults(prev => prev.filter(i => i.id !== memory.id)) - }} - /> + <MemoryItem + removeFromSpace={async () => { + await updateMemory(memory.id, { + removedFromSpaces: [spaceId], + }); + setContentForSpace((prev) => + prev.filter((s) => s.id !== memory.id), + ); + setSearchResults((prev) => + prev.filter((i) => i.id !== memory.id), + ); + }} + {...memory!} + key={i} + onDelete={() => { + setContentForSpace((prev) => + prev.filter((s) => s.id !== memory.id), + ); + setSearchResults((prev) => + prev.filter((i) => i.id !== memory.id), + ); + }} + /> ))} </> - ) : - contentForSpace.map(m => ( - <MemoryItem - key={m.id} - {...m} - onDelete={() => setContentForSpace(prev => prev.filter(s => s.id !== m.id))} - removeFromSpace={async () => { - await updateMemory(m.id, { - removedFromSpaces: [spaceId] - }) - setContentForSpace(prev => prev.filter(s => s.id !== m.id)) - }} - /> - )) - } - </div> + ) : ( + contentForSpace.map((m) => ( + <MemoryItem + key={m.id} + {...m} + onDelete={() => + setContentForSpace((prev) => prev.filter((s) => s.id !== m.id)) + } + removeFromSpace={async () => { + await updateMemory(m.id, { + removedFromSpaces: [spaceId], + }); + setContentForSpace((prev) => prev.filter((s) => s.id !== m.id)); + }} + /> + )) + )} + </div> </div> - ) + ); } diff --git a/apps/web/src/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx index 5e58b6a6..09412465 100644 --- a/apps/web/src/contexts/MemoryContext.tsx +++ b/apps/web/src/contexts/MemoryContext.tsx @@ -36,9 +36,9 @@ export const MemoryContext = React.createContext<{ search: typeof searchMemoriesAndSpaces; deleteSpace: typeof deleteSpace; deleteMemory: typeof deleteMemory; - updateMemory: typeof updateMemory; - updateSpace: typeof updateSpaceTitle; - addMemoriesToSpace: typeof addContentInSpaces; + updateMemory: typeof updateMemory; + updateSpace: typeof updateSpaceTitle; + addMemoriesToSpace: typeof addContentInSpaces; }>({ spaces: [], freeMemories: [], @@ -50,7 +50,7 @@ export const MemoryContext = React.createContext<{ deleteSpace: (() => {}) as unknown as typeof deleteSpace, updateMemory: (() => {}) as unknown as typeof updateMemory, updateSpace: (() => {}) as unknown as typeof updateSpaceTitle, - addMemoriesToSpace: (() => {}) as unknown as typeof addContentInSpaces, + addMemoriesToSpace: (() => {}) as unknown as typeof addContentInSpaces, }); export const MemoryProvider: React.FC< @@ -104,10 +104,10 @@ export const MemoryProvider: React.FC< setSpaces((prev) => [...prev, addedSpace]); const cachedMemories = ( - await fetchContentForSpace(addedSpace.id, { + (await fetchContentForSpace(addedSpace.id, { offset: 0, limit: 3, - }) ?? [] + })) ?? [] ).map((m) => ({ ...m, space: addedSpace.id })); setCachedMemories((prev) => [...prev, ...cachedMemories]); @@ -141,81 +141,80 @@ export const MemoryProvider: React.FC< }; }; - const _updateMemory: typeof updateMemory = async (id, _data) => { - const data = await updateMemory(id, _data); - - let contents: ChachedSpaceContent[] = []; - - await Promise.all([ - spaces.forEach(async (space) => { - console.log("fetching "); - const data = ( - await fetchContentForSpace(space.id, { - offset: 0, - limit: 3, - }) ?? [] - ).map((data) => ({ - ...data, - space: space.id, - })); - contents = [...contents, ...data]; - }), - ]); - - const freeMemories = await fetchFreeMemories(); - - setCachedMemories(contents) - setFreeMemories(freeMemories) - - - return data - } - - const _updateSpace: typeof updateSpaceTitle = async (...params) => { - const updatedSpace = await updateSpaceTitle(...params); - - if (updatedSpace) { - setSpaces(prev => prev.map( - i => i.id === updatedSpace.id ? updatedSpace : i - )) - } - - return updatedSpace - } - - const addMemoriesToSpace: typeof addContentInSpaces = async (...params) => { - const data = await addContentInSpaces(...params); - - let contents: ChachedSpaceContent[] = []; - - await Promise.all([ - spaces.forEach(async (space) => { - console.log("fetching "); - const data = ( - await fetchContentForSpace(space.id, { - offset: 0, - limit: 3, - }) ?? [] - ).map((data) => ({ - ...data, - space: space.id, - })); - contents = [...contents, ...data]; - }), - ]); - - const freeMemories = await fetchFreeMemories(); - - setCachedMemories(contents) - setFreeMemories(freeMemories) - - return data - } + const _updateMemory: typeof updateMemory = async (id, _data) => { + const data = await updateMemory(id, _data); + + let contents: ChachedSpaceContent[] = []; + + await Promise.all([ + spaces.forEach(async (space) => { + console.log("fetching "); + const data = ( + (await fetchContentForSpace(space.id, { + offset: 0, + limit: 3, + })) ?? [] + ).map((data) => ({ + ...data, + space: space.id, + })); + contents = [...contents, ...data]; + }), + ]); + + const freeMemories = await fetchFreeMemories(); + + setCachedMemories(contents); + setFreeMemories(freeMemories); + + return data; + }; + + const _updateSpace: typeof updateSpaceTitle = async (...params) => { + const updatedSpace = await updateSpaceTitle(...params); + + if (updatedSpace) { + setSpaces((prev) => + prev.map((i) => (i.id === updatedSpace.id ? updatedSpace : i)), + ); + } + + return updatedSpace; + }; + + const addMemoriesToSpace: typeof addContentInSpaces = async (...params) => { + const data = await addContentInSpaces(...params); + + let contents: ChachedSpaceContent[] = []; + + await Promise.all([ + spaces.forEach(async (space) => { + console.log("fetching "); + const data = ( + (await fetchContentForSpace(space.id, { + offset: 0, + limit: 3, + })) ?? [] + ).map((data) => ({ + ...data, + space: space.id, + })); + contents = [...contents, ...data]; + }), + ]); + + const freeMemories = await fetchFreeMemories(); + + setCachedMemories(contents); + setFreeMemories(freeMemories); + + return data; + }; return ( <MemoryContext.Provider value={{ - updateSpace: _updateSpace, + updateSpace: _updateSpace, search: searchMemoriesAndSpaces, spaces, addSpace: _addSpace, @@ -224,8 +223,8 @@ export const MemoryProvider: React.FC< cachedMemories, deleteMemory: _deleteMemory, addMemory: _addMemory, - updateMemory: _updateMemory, - addMemoriesToSpace, + updateMemory: _updateMemory, + addMemoriesToSpace, }} > {children} diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts index 0fe5bdfd..81fa8549 100644 --- a/apps/web/src/lib/utils.ts +++ b/apps/web/src/lib/utils.ts @@ -89,25 +89,23 @@ export function convertRemToPixels(rem: number) { } export function isArraysEqual(a: any[], b: any[]) { - if (a === b) return true; + if (a === b) return true; if (a == null || b == null) return false; if (a.length !== b.length) return false; - let isEqual = true; - - a.forEach(i => { - if (!isEqual) return - isEqual = b.includes(i) - }) + let isEqual = true; - if (!isEqual) - return isEqual + a.forEach((i) => { + if (!isEqual) return; + isEqual = b.includes(i); + }); - b.forEach(i => { - if (!isEqual) return - isEqual = a.includes(i) - }) + if (!isEqual) return isEqual; - return isEqual + b.forEach((i) => { + if (!isEqual) return; + isEqual = a.includes(i); + }); + return isEqual; } diff --git a/apps/web/types/memory.tsx b/apps/web/types/memory.tsx index 988c9336..80ed5755 100644 --- a/apps/web/types/memory.tsx +++ b/apps/web/types/memory.tsx @@ -1,7 +1,5 @@ import { db } from "@/server/db"; -import { - StoredContent, -} from "@/server/db/schema"; +import { StoredContent } from "@/server/db/schema"; export type CollectedSpaces = { id: number; |