aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
authoryxshv <[email protected]>2024-04-13 20:04:20 +0530
committeryxshv <[email protected]>2024-04-13 20:04:20 +0530
commit0d4e91068f8b16ddb956e9c302ef62b9e38b1788 (patch)
treed078ea44ccd8b1b5c6a93d25c7d7ca8468455a1b /apps/web/src
parentspaces dialog (diff)
downloadsupermemory-0d4e91068f8b16ddb956e9c302ef62b9e38b1788.tar.xz
supermemory-0d4e91068f8b16ddb956e9c302ef62b9e38b1788.zip
space add
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/actions/db.ts82
-rw-r--r--apps/web/src/components/Sidebar/AddMemoryDialog.tsx32
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx27
-rw-r--r--apps/web/src/contexts/MemoryContext.tsx29
4 files changed, 144 insertions, 26 deletions
diff --git a/apps/web/src/actions/db.ts b/apps/web/src/actions/db.ts
index b12ed13b..c5481392 100644
--- a/apps/web/src/actions/db.ts
+++ b/apps/web/src/actions/db.ts
@@ -8,10 +8,10 @@ import {
storedContent,
StoredSpace,
users,
- space
+ space,
} from "@/server/db/schema";
import { SearchResult } from "@/contexts/MemoryContext";
-import { like, eq, and, sql } from "drizzle-orm";
+import { like, eq, and, sql, exists, asc, notExists } from "drizzle-orm";
import { union } from "drizzle-orm/sqlite-core"
// @todo: (future) pagination not yet needed
@@ -137,3 +137,81 @@ export async function addMemory(
);
return _content;
}
+
+export async function addSpace(name: string, memories: number[]) {
+
+ const user = await getUser();
+
+ if (!user) {
+ return null
+ }
+
+ const [addedSpace] = await db
+ .insert(space)
+ .values({
+ name: name,
+ user: user.id
+ }).returning();
+
+ const addedMemories = memories.length > 0 ? await db.insert(contentToSpace)
+ .values(memories.map(m => ({
+ contentId: m,
+ spaceId: addedSpace.id
+ }))).returning() : []
+
+ return {
+ space: addedSpace,
+ addedMemories
+ }
+}
+
+
+export async function fetchContentForSpace(
+ spaceId: number,
+ range?: {
+ offset: number;
+ limit: number;
+ },
+) {
+
+ const query = db
+ .select()
+ .from(storedContent)
+ .where(
+ exists(
+ db.select().from(contentToSpace).where(and(eq(contentToSpace.spaceId, spaceId), eq(contentToSpace.contentId, storedContent.id))),
+ ),
+ ).orderBy(asc(storedContent.title))
+
+ return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
+}
+
+export async function fetchFreeMemories(
+ range?: {
+ offset: number;
+ limit: number;
+ }
+) {
+
+ const user = await getUser()
+
+ if (!user) {
+ return []
+ }
+
+ 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.title))
+
+ return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
+
+}
diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
index 4f8ef734..1482774e 100644
--- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
+++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
@@ -12,9 +12,10 @@ import { Markdown } from "tiptap-markdown";
import { useEffect, useRef, useState } from "react";
import { FilterMemories, FilterSpaces } from "./FilterCombobox";
import { useMemory } from "@/contexts/MemoryContext";
-import { Command, Plus, X } from "lucide-react";
+import { Loader, Plus, X } from "lucide-react";
import { StoredContent } from "@/server/db/schema";
import { cleanUrl } from "@/lib/utils";
+import { motion } from "framer-motion"
export function AddMemoryPage() {
const { addMemory } = useMemory();
@@ -157,8 +158,11 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
+ const { addSpace } = useMemory()
+
const inputRef = useRef<HTMLInputElement>(null);
const [name, setName] = useState("");
+
const [loading, setLoading] = useState(false);
const [selected, setSelected] = useState<StoredContent[]>([]);
@@ -198,6 +202,7 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
type="url"
data-modal-autofocus
value={name}
+ disabled={loading}
onChange={e => setName(e.target.value)}
className="bg-rgray-4 mt-2 w-full focus-visible:data-[error=true]:ring-red-500/10 data-[error=true]:placeholder:text-red-400 placeholder:transition placeholder:duration-500"
/>
@@ -219,7 +224,8 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
<FilterMemories
selected={selected}
setSelected={setSelected}
- className="mr-auto bg-white/5 hover:bg-rgray-4 focus-visible:bg-rgray-4"
+ disabled={loading}
+ className="mr-auto bg-white/5 hover:bg-rgray-4 focus-visible:bg-rgray-4 disabled:opacity-70 disabled:cursor-not-allowed"
>
<Plus className="w-5 h-5" />
Memory
@@ -228,14 +234,28 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
type={undefined}
onClick={() => {
if (check()) {
-
+ setLoading(true)
+ addSpace(name, selected.map(s => s.id)).then(() => closeDialog())
}
}}
- className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
+ disabled={loading}
+ className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
>
- Add
+ <motion.div
+ initial={{ x: '-50%', y: '-100%' }}
+ animate={loading && { y: '-50%', x: '-50%', opacity: 1 }}
+ className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2"
+ >
+ <Loader className="w-5 h-5 animate-spin text-rgray-11" />
+ </motion.div>
+ <motion.div
+ initial={{ y: '0%' }}
+ animate={loading && { opacity: 0, y: '30%' }}
+ >
+ Add
+ </motion.div>
</button>
- <DialogClose className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2">
+ <DialogClose disabled={loading} className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2">
Cancel
</DialogClose>
</DialogFooter>
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx
index 1c9e7143..88262f4e 100644
--- a/apps/web/src/components/Sidebar/MemoriesBar.tsx
+++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx
@@ -196,13 +196,16 @@ export function MemoryItem({
title,
image
}: StoredContent) {
+
+ const name = title ? title.length > 10 ? title.slice(0, 10) + "..." : title : '<no title>';
+
return (
<div
className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100"
>
<button data-space-text className="focus-visible:outline-none">
- {title}
+ {name}
</button>
<div className="w-24 h-24 flex justify-center items-center">
@@ -240,6 +243,8 @@ export function SpaceItem({
return cachedMemories.filter(m => m.space === id)
}, [cachedMemories])
+ const _name = name.length > 10 ? name.slice(0, 10) + "..." : name
+
return (
<motion.div
ref={itemRef}
@@ -248,7 +253,7 @@ export function SpaceItem({
className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100"
>
<button data-space-text className="focus-visible:outline-none">
- {name}
+ {_name}
</button>
<SpaceMoreButton
isOpen={moreDropdownOpen}
@@ -341,19 +346,23 @@ export function SpaceItem({
id={id.toString()}
images={spaceMemories.map((c) => c.image).reverse() as string[]}
/>
- ) : spaceMemories.length === 1 ? (
- <MemoryWithImage
+ ) : spaceMemories.length > 1 ? (
+ <MemoryWithImages2
className="h-24 w-24"
id={id.toString()}
- image={spaceMemories[0].image!}
+ images={spaceMemories.map((c) => c.image).reverse() as string[]}
/>
- ) : (
- <MemoryWithImages2
+ ) : spaceMemories.length === 1 ? (
+ <MemoryWithImage
className="h-24 w-24"
id={id.toString()}
- images={spaceMemories.map((c) => c.image).reverse() as string[]}
+ image={spaceMemories[0].image!}
/>
- )}
+ ) : (
+ <div className="bg-rgray-4 opacity-30 rounded-full w-24 h-24 scale-50 shadow-">
+
+ </div>
+ )}
</motion.div>
);
}
diff --git a/apps/web/src/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx
index f2f09e80..08d166a6 100644
--- a/apps/web/src/contexts/MemoryContext.tsx
+++ b/apps/web/src/contexts/MemoryContext.tsx
@@ -1,8 +1,7 @@
"use client";
import React, { useCallback } from "react";
-import { CollectedSpaces } from "../../types/memory";
import { ChachedSpaceContent, StoredContent, storedContent, StoredSpace } from "@/server/db/schema";
-import { addMemory, searchMemoriesAndSpaces } from "@/actions/db";
+import { addMemory, searchMemoriesAndSpaces, addSpace, fetchContentForSpace } from "@/actions/db";
import { User } from "next-auth";
export type SearchResult = {
@@ -16,7 +15,7 @@ export const MemoryContext = React.createContext<{
spaces: StoredSpace[];
deleteSpace: (id: number) => Promise<void>;
freeMemories: StoredContent[];
- addSpace: (space: StoredSpace) => Promise<void>;
+ addSpace: typeof addSpace;
addMemory: (
memory: typeof storedContent.$inferInsert,
spaces?: number[],
@@ -27,7 +26,7 @@ export const MemoryContext = React.createContext<{
spaces: [],
freeMemories: [],
addMemory: async () => {},
- addSpace: async () => {},
+ addSpace: (async () => {}) as unknown as (typeof addSpace),
deleteSpace: async () => {},
cachedMemories: [],
search: async () => []
@@ -49,10 +48,6 @@ export const MemoryProvider: React.FC<
const [cachedMemories, setCachedMemories] = React.useState<ChachedSpaceContent[]>(
initialCachedMemories
);
-
- const addSpace = async (space: StoredSpace) => {
- setSpaces((prev) => [...prev, space]);
- }
const deleteSpace = async (id: number) => {
setSpaces((prev) => prev.filter((s) => s.id !== id));
@@ -68,13 +63,29 @@ export const MemoryProvider: React.FC<
) => {
const content = await addMemory(memory, spaces);
}
+
+ const _addSpace: typeof addSpace = async (...params) => {
+ const { space: addedSpace, addedMemories } = (await addSpace(...params))!;
+
+ setSpaces(prev => [...prev, addedSpace])
+ const cachedMemories = (await fetchContentForSpace(addedSpace.id, {
+ offset: 0,
+ limit: 3
+ })).map(m => ({ ...m, space: addedSpace.id }))
+
+ setCachedMemories(prev => [...prev, ...cachedMemories])
+
+ return {
+ space: addedSpace, addedMemories
+ }
+ }
return (
<MemoryContext.Provider
value={{
search: searchMemoriesAndSpaces,
spaces,
- addSpace,
+ addSpace: _addSpace,
deleteSpace,
freeMemories,
cachedMemories,