diff --git a/SETUP-GUIDE.md b/SETUP-GUIDE.md index b7f47999..7d69b545 100644 --- a/SETUP-GUIDE.md +++ b/SETUP-GUIDE.md @@ -59,7 +59,7 @@ OPENAI_API_KEY="sk-" ``` 6. Run this command to initialise vector database -> Note: You need to use the workers paid plan to use vectorize for now. + > Note: You need to use the workers paid plan to use vectorize for now. ``` wrangler vectorize create --dimensions=1536 supermem-vector-1 --metric=cosine 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() { + svg: , }, { title: "For Content writers", description: "Save time and use the writing assistant to generate content based on your own saved collections and sources.", - svg: + svg: , }, { title: "For Developers", description: "Talk to documentation websites, code snippets, etc. so you never have to google the same thing a hundred times.", - svg: + svg: , }, ]; @@ -56,4 +56,4 @@ function DeveloperSvg() { ); -} \ 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 */}
{/* Content */} @@ -53,7 +53,11 @@ export default function Features() {
{/* Tabs buttons */}
- +
@@ -252,7 +256,6 @@ export default function Features() { - ); 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() { Supermemory logo -
+
Use cases 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 (
- - + {/* Background gradients */}
@@ -32,9 +31,7 @@ export default function Home() {
-
+
@@ -44,7 +41,6 @@ export default function Home() { {/* Features section */} - @@ -52,8 +48,6 @@ export default function Home() {
- -
); } 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 { 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 = [] + 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) - )))) - } - } + 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) - ) : [] - + const fetchedMemory = await db + .select() + .from(storedContent) + .where(and(eq(storedContent.id, id), eq(storedContent.user, user.id))); - return { - memory, - spaces: spaces.map(s => s.spaceId) - } + 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 - } + 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, + 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; + new Promise((_, reject) => + setTimeout(() => reject(new Error("Request timed out")), 40000), + ), + ])) as Response; - [updatedMemory] = await db - .update(storedContent) - .set(newContent) - .where(and( - eq(storedContent.id, id), - eq(storedContent.user, user.id) - )) - .returning(); - - console.log(updatedMemory, newContent) - } + [updatedMemory] = await db + .update(storedContent) + .set(newContent) + .where(and(eq(storedContent.id, id), eq(storedContent.user, user.id))) + .returning(); - if (!updatedMemory) { - [updatedMemory] = await db - .select() - .from(storedContent) - .where(and( - eq(storedContent.id, id), - eq(storedContent.user, user.id) - )) - } + console.log(updatedMemory, newContent); + } - 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 ?? [], - }), - }, - ); + 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); + 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 (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 }, - ); - } + 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 { - } + // 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({ Related Memories -
- {sources?.map((source) => source.isNote ? ( - - ) : ( - - - {cleanUrl(source.source)} - - ))} +
+ {sources?.map((source) => + source.isNote ? ( + + ) : ( + + + {cleanUrl(source.source)} + + ), + )}
)} 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} > - -
- -
+ +
+ +
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 ( - - {trigger ? ( - - {children} - - ) : ( - <> - {children} - - )} - +export default function DeleteConfirmation({ + onDelete, + trigger = true, + children, +}: { + trigger?: boolean; + onDelete?: () => void; + children: React.ReactNode; +}) { + return ( + + {trigger ? ( + {children} + ) : ( + <>{children} + )} + Are you sure? You will not be able to recover this it. @@ -21,7 +33,7 @@ export default function DeleteConfirmation({ onDelete, trigger = true, children Delete @@ -29,7 +41,7 @@ export default function DeleteConfirmation({ onDelete, trigger = true, children Cancel - - - ) + + + ); } 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" +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() +export function ExpandedSpace({ + spaceId, + back, +}: { + spaceId: number; + back: () => void; +}) { + const { updateMemory, updateSpace, search } = useMemory(); const [parent, enableAnimations] = useAutoAnimate(); - const inputRef = useRef(null); + const inputRef = useRef(null); - const [contentForSpace, setContentForSpace] = useState([]) + const [contentForSpace, setContentForSpace] = useState([]); - const [lastUpdatedTitle, setLastUpdatedTitle] = useState(null); + const [lastUpdatedTitle, setLastUpdatedTitle] = useState(null); - const [title, setTitle] = useState(''); - const debouncedTitle = useDebounce(title, 500) + const [title, setTitle] = useState(""); + 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([]); - const [addMemoryState, setAddMemoryState] = useState< "page" | "note" | "existing-memory" | "space" | null >(null); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - + 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(() => { + (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 ( -
- -
- ) - } + if (loading) { + return ( +
+ +
+ ); + } return (
-
- - setTitle(e.target.value)} - /> - -
-
+
+ + setTitle(e.target.value)} + /> + +
+
value={searchQuery} onChange={(e) => setSearcyQuery(e.target.value)} /> -
-
- { - 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}> +
+
+ { + 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} + >
+
- {query.trim().length > 0 ? ( + {query.trim().length > 0 ? ( <> {searchResults.map((memory, i) => ( - { - 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)) - }} - /> + { + 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 => ( - 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)) - }} - /> - )) - } -
+ ) : ( + contentForSpace.map((m) => ( + + 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)); + }} + /> + )) + )} +
- ) + ); } 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); + const _updateMemory: typeof updateMemory = async (id, _data) => { + const data = await updateMemory(id, _data); - let contents: ChachedSpaceContent[] = []; + 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]; - }), - ]); + 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(); + const freeMemories = await fetchFreeMemories(); - setCachedMemories(contents) - setFreeMemories(freeMemories) + setCachedMemories(contents); + setFreeMemories(freeMemories); - - return data - } + return data; + }; - const _updateSpace: typeof updateSpaceTitle = async (...params) => { - const updatedSpace = await updateSpaceTitle(...params); + const _updateSpace: typeof updateSpaceTitle = async (...params) => { + const updatedSpace = await updateSpaceTitle(...params); - if (updatedSpace) { - setSpaces(prev => prev.map( - i => i.id === updatedSpace.id ? updatedSpace : i - )) - } + if (updatedSpace) { + setSpaces((prev) => + prev.map((i) => (i.id === updatedSpace.id ? updatedSpace : i)), + ); + } - return updatedSpace - } + return updatedSpace; + }; - const addMemoriesToSpace: typeof addContentInSpaces = async (...params) => { - const data = await addContentInSpaces(...params); + const addMemoriesToSpace: typeof addContentInSpaces = async (...params) => { + const data = await addContentInSpaces(...params); - let contents: ChachedSpaceContent[] = []; + 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]; - }), - ]); + 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(); + const freeMemories = await fetchFreeMemories(); - setCachedMemories(contents) - setFreeMemories(freeMemories) + setCachedMemories(contents); + setFreeMemories(freeMemories); - return data - } + return data; + }; return ( {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;