mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
format documents
This commit is contained in:
parent
51018c8568
commit
a4f566aaa0
19 changed files with 683 additions and 580 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ html {
|
|||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #131F2C;
|
||||
background: #131f2c;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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);
|
||||
|
||||
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 (
|
||||
<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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue