aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authoryxshv <[email protected]>2024-04-14 14:29:23 +0530
committeryxshv <[email protected]>2024-04-14 14:29:23 +0530
commitfa39265142a7aa452a273e4290d58757af2786bb (patch)
tree52e2e07d2a20009d650ed0b3ebe60aaab87d81ff /apps
parentfixed notes vectorize (diff)
downloadsupermemory-fa39265142a7aa452a273e4290d58757af2786bb.tar.xz
supermemory-fa39265142a7aa452a273e4290d58757af2786bb.zip
new modals
Diffstat (limited to 'apps')
-rw-r--r--apps/web/src/actions/db.ts268
-rw-r--r--apps/web/src/app/globals.css12
-rw-r--r--apps/web/src/app/page.tsx4
-rw-r--r--apps/web/src/components/ChatMessage.tsx22
-rw-r--r--apps/web/src/components/Main.tsx25
-rw-r--r--apps/web/src/components/Sidebar/AddMemoryDialog.tsx3
-rw-r--r--apps/web/src/components/Sidebar/DeleteConfirmation.tsx35
-rw-r--r--apps/web/src/components/Sidebar/EditNoteDialog.tsx152
-rw-r--r--apps/web/src/components/Sidebar/FilterCombobox.tsx5
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx98
-rw-r--r--apps/web/src/contexts/MemoryContext.tsx40
-rw-r--r--apps/web/src/lib/utils.ts24
-rw-r--r--apps/web/types/memory.tsx2
13 files changed, 579 insertions, 111 deletions
diff --git a/apps/web/src/actions/db.ts b/apps/web/src/actions/db.ts
index cd1a0f1d..20aa4de6 100644
--- a/apps/web/src/actions/db.ts
+++ b/apps/web/src/actions/db.ts
@@ -4,14 +4,12 @@ import { db } from "@/server/db";
import {
contentToSpace,
sessions,
- StoredContent,
storedContent,
- StoredSpace,
users,
space,
} from "@/server/db/schema";
import { SearchResult } from "@/contexts/MemoryContext";
-import { like, eq, and, sql, exists, asc, notExists } 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";
@@ -82,6 +80,22 @@ 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() : []
+}
+
async function getUser() {
const token =
cookies().get("next-auth.session-token")?.value ??
@@ -167,6 +181,38 @@ export async function addSpace(name: string, memories: number[]) {
};
}
+export async function fetchContent(id: number) {
+
+
+ const user = await getUser();
+
+ if (!user) {
+ return null;
+ }
+
+ const fetchedMemory = await db.select()
+ .from(storedContent)
+ .where(and(
+ eq(storedContent.id, id),
+ eq(storedContent.user, user.id)
+ ));
+
+ const memory = fetchedMemory.length > 0 ? fetchedMemory[0] : null
+
+ const spaces = memory ? await db.select()
+ .from(contentToSpace)
+ .where(
+ eq(contentToSpace.contentId, memory.id)
+ ) : []
+
+
+ return {
+ memory,
+ spaces: spaces.map(s => s.spaceId)
+ }
+
+}
+
export async function fetchContentForSpace(
spaceId: number,
range?: {
@@ -174,6 +220,13 @@ export async function fetchContentForSpace(
limit: number;
},
) {
+
+ const user = await getUser();
+
+ if (!user) {
+ return null;
+ }
+
const query = db
.select()
.from(storedContent)
@@ -184,9 +237,19 @@ export async function fetchContentForSpace(
.from(contentToSpace)
.where(
and(
- eq(contentToSpace.spaceId, spaceId),
- eq(contentToSpace.contentId, storedContent.id),
- ),
+ 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)
+ ))
+ )
+ )
),
),
)
@@ -207,25 +270,30 @@ export async function fetchFreeMemories(range?: {
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.savedAt));
+ 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 []
+ }
- return range
- ? await query.limit(range.limit).offset(range.offset)
- : await query.all();
}
export async function addMemory(
@@ -238,7 +306,7 @@ export async function addMemory(
return null;
}
- if (!content.content || content.content == "") {
+ if (!content.content || content.content.trim() === "") {
const resp = await fetch(
`https://cf-ai-backend.dhravya.workers.dev/getPageContent?url=${content.url}`,
{
@@ -259,9 +327,36 @@ export async function addMemory(
return null;
}
- console.log(content);
+ let [addedMemory] = await db
+ .insert(storedContent)
+ .values({
+ user: user.id,
+ ...content,
+ })
+ .returning();
+
+ const addedToSpaces =
+ spaces.length > 0
+ ? await db
+ .insert(contentToSpace)
+ .values(
+ spaces.map((s) => ({
+ contentId: addedMemory.id,
+ spaceId: s,
+ })),
+ )
+ .returning()
+ : [];
+
+ 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({ ...content, user: user.email });
// Add to vectorDB
const res = (await Promise.race([
@@ -271,9 +366,9 @@ export async function addMemory(
"X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY,
},
body: JSON.stringify({
- pageContent: content.content,
- title: content.title,
- url: content.url,
+ pageContent: addedMemory.content,
+ title: addedMemory.title,
+ url: addedMemory.url,
user: user.email,
}),
}),
@@ -282,30 +377,110 @@ export async function addMemory(
),
])) as Response;
- const [addedMemory] = await db
- .insert(storedContent)
- .values({
- user: user.id,
- ...content,
- })
+ return {
+ memory: addedMemory,
+ addedToSpaces,
+ };
+}
+
+
+export async function updateMemory(
+ id: number,
+ { title, content, spaces }: {
+ title?: string;
+ content?: string;
+ spaces?: number[]
+ }
+) {
+ const user = await getUser();
+
+ if (!user) {
+ return null;
+ }
+
+ console.log("updating")
+
+ const [prev] = await db.select()
+ .from(storedContent)
+ .where(and(
+ eq(storedContent.user, user.id),
+ eq(storedContent.id, id)
+ ));
+
+ if (!prev) {
+ return null
+ }
+
+ const newContent = {
+ ...(title ? { title }: {}),
+ ...(content ? { content }: {}),
+ }
+
+ const updated = {
+ ...newContent,
+ ...prev
+ }
+
+ // Add to vectorDB
+ const res = (await Promise.race([
+ fetch("https://cf-ai-backend.dhravya.workers.dev/edit?uniqueUrl="+updated.url , {
+ 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,
+ }),
+ }),
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error("Request timed out")), 40000),
+ ),
+ ])) as Response;
+
+ const [updatedMemory] = await db
+ .update(storedContent)
+ .set(newContent)
+ .where(
+ eq(storedContent.id, id)
+ )
.returning();
+
+ console.log(updatedMemory, newContent)
+
+ const removedFromSpaces = 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.length > 0
+ (spaces && spaces.length > 0)
? await db
.insert(contentToSpace)
.values(
spaces.map((s) => ({
- contentId: addedMemory.id,
+ contentId: id,
spaceId: s,
})),
)
+ .onConflictDoNothing()
.returning()
: [];
return {
- memory: addedMemory,
+ memory: updatedMemory,
addedToSpaces,
+ removedFromSpaces
};
}
@@ -340,5 +515,20 @@ export async function deleteMemory(id: number) {
.where(and(eq(storedContent.user, user.id), eq(storedContent.id, id)))
.returning();
+ if (deleted) {
+
+ const res = (await Promise.race([
+ fetch(`https://cf-ai-backend.dhravya.workers.dev/delete?websiteUrl=${deleted.url}&user=${user.email}` , {
+ method: "DELETE",
+ headers: {
+ "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY,
+ }
+ }),
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error("Request timed out")), 40000),
+ ),
+ ])) as Response;
+ }
+
return deleted;
}
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index b09627ba..895b87e4 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -57,18 +57,22 @@ body {
padding-bottom: 15dvh;
}
-.chat-answer pre {
- @apply bg-rgray-3 rounded-md border border-rgray-5 p-3 text-sm my-5;
+.chat-answer code {
+ @apply bg-rgray-3 rounded-md border border-rgray-5 p-1 text-sm text-rgray-11;
}
.novel-editor pre {
- @apply bg-rgray-3 rounded-md border border-rgray-5 p-4 text-sm text-rgray-11;
+ @apply bg-rgray-3 rounded-md border border-rgray-5 p-4 my-5 text-sm text-rgray-11;
}
.chat-answer h1 {
@apply text-rgray-11 my-5 text-xl font-medium;
}
+.chat-answer a {
+ @apply underline underline-offset-1 opacity-90 hover:opacity-100;
+}
+
.chat-answer img {
@apply rounded-md font-medium my-5;
}
@@ -122,4 +126,4 @@ body {
.novel-editor .drag-handle {
@apply hidden;
-} \ No newline at end of file
+}
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index 1cc21adf..8df5dad3 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -64,8 +64,6 @@ export default async function Home() {
// Fetch only first 3 content of each spaces
let contents: ChachedSpaceContent[] = [];
- //console.log(await db.select().from(storedContent).)
-
await Promise.all([
collectedSpaces.forEach(async (space) => {
console.log("fetching ");
@@ -82,7 +80,7 @@ export default async function Home() {
}),
]);
- console.log(contents);
+ console.log('contents', contents);
// freeMemories
const freeMemories = await fetchFreeMemories(userData.id);
diff --git a/apps/web/src/components/ChatMessage.tsx b/apps/web/src/components/ChatMessage.tsx
index db0778c4..1567a9ac 100644
--- a/apps/web/src/components/ChatMessage.tsx
+++ b/apps/web/src/components/ChatMessage.tsx
@@ -1,9 +1,10 @@
import React, { useEffect } from "react";
import { motion } from "framer-motion";
-import { ArrowUpRight, Globe } from "lucide-react";
+import { ArrowUpRight, Globe, Text } from "lucide-react";
import { convertRemToPixels } from "@/lib/utils";
import { SpaceIcon } from "@/assets/Memories";
import Markdown from "react-markdown";
+import { ChatHistory } from "../../types/memory";
export function ChatAnswer({
children: message,
@@ -11,7 +12,7 @@ export function ChatAnswer({
loading = false,
}: {
children: string;
- sources?: string[];
+ sources?: ChatHistory['answer']['sources'];
loading?: boolean;
}) {
return (
@@ -29,15 +30,22 @@ export function ChatAnswer({
<SpaceIcon className="h-6 w-6 -translate-y-[2px]" />
Related Memories
</h1>
- <div className="animate-fade-in -mt-3 flex items-center justify-start opacity-0 [animation-duration:1s]">
- {sources?.map((source) => (
+ <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}
- href={source}
+ key={source.source}
+ href={source.source}
>
<Globe className="h-4 w-4" />
- {cleanUrl(source)}
+ {cleanUrl(source.source)}
</a>
))}
</div>
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx
index bbdb630d..df6a08bf 100644
--- a/apps/web/src/components/Main.tsx
+++ b/apps/web/src/components/Main.tsx
@@ -13,6 +13,7 @@ import { useRouter, useSearchParams } from "next/navigation";
import { useMemory } from "@/contexts/MemoryContext";
import Image from "next/image";
+import { getMemoriesFromUrl } from "@/actions/db";
function supportsDVH() {
try {
@@ -185,11 +186,25 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
},
);
- const sourcesInJson = (await sourcesResponse.json()) as {
- ids: string[];
- };
- console.log(sourcesInJson);
+ const sourcesInJson = getIdsFromSource(((await sourcesResponse.json()) as {
+ ids: string[]
+ }).ids) ?? [];
+
+
+ const notesInSources = sourcesInJson.filter(
+ (urls) => urls.startsWith("https://notes.supermemory.dhr.wtf/")
+ )
+ const nonNotes = sourcesInJson.filter(
+ i => !notesInSources.includes(i)
+ )
+
+ const fetchedTitles = await getMemoriesFromUrl(notesInSources);
+
+ const sources = [
+ ...nonNotes.map(n => ({ isNote: false, source: n ?? "<unnamed>" })),
+ ...fetchedTitles.map(n => ({ isNote: true, source: n.title ?? "<unnamed>" }))
+ ]
setIsAiLoading(false);
setChatHistory((prev) => {
@@ -200,7 +215,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
...lastMessage,
answer: {
parts: lastMessage.answer.parts,
- sources: getIdsFromSource(sourcesInJson.ids) ?? [],
+ sources
},
},
];
diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
index 95bb3d22..f6a7224f 100644
--- a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
+++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx
@@ -165,13 +165,12 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
onClick={() => {
if (check()) {
setLoading(true);
- const randomId = Math.floor(Math.random() * 1000000);
addMemory(
{
content,
title: name,
type: "note",
- url: `https://notes.supermemory.dhr.wtf/${randomId}`,
+ url: `https://notes.supermemory.dhr.wtf/`,
image: "",
savedAt: new Date(),
},
diff --git a/apps/web/src/components/Sidebar/DeleteConfirmation.tsx b/apps/web/src/components/Sidebar/DeleteConfirmation.tsx
new file mode 100644
index 00000000..9324b147
--- /dev/null
+++ b/apps/web/src/components/Sidebar/DeleteConfirmation.tsx
@@ -0,0 +1,35 @@
+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>
+ <DialogTitle className="text-xl">Are you sure?</DialogTitle>
+ <DialogDescription className="text-md">
+ You will not be able to recover this it.
+ </DialogDescription>
+ <DialogFooter>
+ <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"
+ >
+ Delete
+ </DialogClose>
+ <DialogClose 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">
+ Cancel
+ </DialogClose>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}
diff --git a/apps/web/src/components/Sidebar/EditNoteDialog.tsx b/apps/web/src/components/Sidebar/EditNoteDialog.tsx
new file mode 100644
index 00000000..b7760656
--- /dev/null
+++ b/apps/web/src/components/Sidebar/EditNoteDialog.tsx
@@ -0,0 +1,152 @@
+
+import { Editor } from "novel";
+import {
+ DialogClose,
+ DialogFooter,
+} from "../ui/dialog";
+import { Input } from "../ui/input";
+import { Markdown } from "tiptap-markdown";
+import { useEffect, useRef, useState } from "react";
+import { FilterSpaces } from "./FilterCombobox";
+import { useMemory } from "@/contexts/MemoryContext";
+import { Loader, Plus, Trash, X } from "lucide-react";
+import { motion } from "framer-motion";
+import { StoredContent } from "@/server/db/schema";
+import { fetchContent } from "@/actions/db";
+import { isArraysEqual } from "@/lib/utils";
+import DeleteConfirmation from "./DeleteConfirmation";
+
+
+export function NoteEdit({ memory, closeDialog }: { memory: StoredContent, closeDialog: () => any }) {
+ const { updateMemory, deleteMemory } = useMemory();
+
+ const [initialSpaces, setInitialSpaces] = useState<number[]>([])
+ const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>([]);
+
+ const inputRef = useRef<HTMLInputElement>(null);
+ const [name, setName] = useState(memory.title ?? "");
+ const [content, setContent] = useState(memory.content);
+ const [loading, setLoading] = useState(false);
+
+ function check(): boolean {
+ const data = {
+ name: name.trim(),
+ content,
+ };
+ if (!data.name || data.name.length < 1) {
+ if (!inputRef.current) {
+ alert("Please enter a name for the note");
+ return false;
+ }
+ inputRef.current.value = "";
+ inputRef.current.placeholder = "Please enter a title for the note";
+ inputRef.current.dataset["error"] = "true";
+ setTimeout(() => {
+ inputRef.current!.placeholder = "Title of the note";
+ inputRef.current!.dataset["error"] = "false";
+ }, 500);
+ inputRef.current.focus();
+ return false;
+ }
+ return true;
+ }
+
+ useEffect(() => {
+ fetchContent(memory.id).then((data) => {
+ if (data?.spaces) {
+ setInitialSpaces(data.spaces)
+ setSelectedSpacesId(data.spaces)
+ }
+ })
+ }, [])
+
+ return (
+ <div>
+ <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 note"
+ value={name}
+ disabled={loading}
+ onChange={(e) => setName(e.target.value)}
+ />
+ <Editor
+ disableLocalStorage
+ defaultValue={memory.content}
+ onUpdate={(editor) => {
+ if (!editor) return;
+ setContent(editor.storage.markdown.getMarkdown());
+ }}
+ extensions={[Markdown]}
+ className="novel-editor bg-rgray-4 border-rgray-7 dark mt-5 max-h-[60vh] min-h-[40vh] w-[50vw] overflow-y-auto rounded-lg border [&>div>div]:p-5"
+ />
+ <DialogFooter>
+ <FilterSpaces
+ selectedSpaces={selectedSpacesId}
+ setSelectedSpaces={setSelectedSpacesId}
+ className="hover:bg-rgray-5 mr-auto bg-white/5"
+ name={"Spaces"}
+ />
+ <DeleteConfirmation onDelete={() => {
+ deleteMemory(memory.id)
+ }}>
+ <button
+ type={undefined}
+ disabled={loading}
+ className="focus-visible:bg-red-100 focus-visible:text-red-400 dark:focus-visible:bg-red-100/10 hover:bg-red-100 dark:hover:bg-red-100/10 hover:text-red-400 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
+ >
+ <Trash className="w-5 h-5" />
+ </button>
+ </DeleteConfirmation>
+ <button
+ onClick={() => {
+ if (check()) {
+ setLoading(true);
+ console.log(
+
+ {
+ title: name === memory.title ? undefined : name,
+ content: content === memory.content ? undefined : content,
+ spaces: isArraysEqual(initialSpaces, selectedSpacesId) ? undefined : selectedSpacesId,
+ },
+ )
+ updateMemory(
+ memory.id,
+ {
+ title: name === memory.title ? undefined : name,
+ content: content === memory.content ? undefined : content,
+ spaces: isArraysEqual(initialSpaces, selectedSpacesId) ? undefined : selectedSpacesId,
+ },
+ ).then(closeDialog);
+ }
+ }}
+ disabled={loading}
+ className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
+ >
+ <motion.div
+ initial={{ x: "-50%", y: "-100%" }}
+ animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
+ className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
+ >
+ <Loader className="text-rgray-11 h-5 w-5 animate-spin" />
+ </motion.div>
+ <motion.div
+ initial={{ y: "0%" }}
+ animate={loading && { opacity: 0, y: "30%" }}
+ >
+ Save
+ </motion.div>
+ </button>
+ <DialogClose
+ type={undefined}
+ disabled={loading}
+ 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 disabled:cursor-not-allowed disabled:opacity-70"
+ >
+ Cancel
+ </DialogClose>
+ </DialogFooter>
+ </div>
+ );
+}
+
diff --git a/apps/web/src/components/Sidebar/FilterCombobox.tsx b/apps/web/src/components/Sidebar/FilterCombobox.tsx
index f93ae710..7625e2b6 100644
--- a/apps/web/src/components/Sidebar/FilterCombobox.tsx
+++ b/apps/web/src/components/Sidebar/FilterCombobox.tsx
@@ -90,6 +90,7 @@ export function FilterSpaces({
align={align}
side={side}
className="w-[200px] p-0"
+ onCloseAutoFocus={e => e.preventDefault()}
>
<Command
filter={(val, search) =>
@@ -128,7 +129,7 @@ export function FilterSpaces({
className="text-rgray-11"
>
<SpaceIcon className="mr-2 h-4 w-4" />
- {space.name}
+ {space.name.length > 10 ? space.name.slice(0, 10) + "..." : space.name}
{selectedSpaces.includes(space.id)}
<Check
data-state-on={selectedSpaces.includes(space.id)}
@@ -267,7 +268,7 @@ export function FilterMemories({
}
className="mr-2 h-4 w-4"
/>
- {m.title}
+ {(m.title && m.title?.length > 14) ? m.title?.slice(0, 14) + "..." : m.title}
<Check
data-state-on={
selected.find((i) => i.id === m.id) !== undefined
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx
index 6c640e26..1218407b 100644
--- a/apps/web/src/components/Sidebar/MemoriesBar.tsx
+++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx
@@ -37,15 +37,15 @@ import {
DialogFooter,
DialogClose,
} from "../ui/dialog";
-import { Label } from "../ui/label";
import useViewport from "@/hooks/useViewport";
import useTouchHold from "@/hooks/useTouchHold";
import { DialogTrigger } from "@radix-ui/react-dialog";
import { AddMemoryPage, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog";
import { ExpandedSpace } from "./ExpandedSpace";
import { StoredContent, StoredSpace } from "@/server/db/schema";
-import Image from "next/image";
import { useDebounce } from "@/hooks/useDebounce";
+import { NoteEdit } from "./EditNoteDialog";
+import DeleteConfirmation from "./DeleteConfirmation";
export function MemoriesBar() {
const [parent, enableAnimations] = useAutoAnimate();
@@ -194,39 +194,58 @@ const SpaceExitVariant: Variant = {
},
};
-export function MemoryItem({ id, title, image, type }: StoredContent) {
+export function MemoryItem(props: StoredContent) {
+
+ const { id, title, image, type } = props
+
const name = title
? title.length > 10
? title.slice(0, 10) + "..."
: title
: "<no title>";
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+
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">
- {name}
- </button>
+ <Dialog open={type === "note" ? isDialogOpen : false} onOpenChange={setIsDialogOpen}>
+ <div onClick={() => setIsDialogOpen(true)} className="cursor-pointer 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">
+ {
+ type === "note" ?
+ (
+ <DialogTrigger asChild>
+ <button data-space-text className="focus-visible:outline-none">
+ {name}
+ </button>
+ </DialogTrigger>
+ ) : (
+ <button data-space-text className="focus-visible:outline-none">
+ {name}
+ </button>
+ )
+ }
- <div className="flex h-24 w-24 items-center justify-center">
- {type === "page" ? (
- <img
- className="h-16 w-16"
- id={id.toString()}
- src={image!}
- onError={(e) => {
- (e.target as HTMLImageElement).src =
- "/icons/white_without_bg.png";
- }}
- />
- ) : type === "note" ? (
- <div className="bg-rgray-4 flex items-center justify-center rounded-md p-2 shadow-md">
- <Text className="h-10 w-10" />
- </div>
- ) : (
- <></>
- )}
- </div>
- </div>
+ <div className="flex h-24 w-24 items-center justify-center">
+ {type === "page" ? (
+ <img
+ className="h-16 w-16"
+ id={id.toString()}
+ src={image!}
+ onError={(e) => {
+ (e.target as HTMLImageElement).src =
+ "/icons/white_without_bg.png";
+ }}
+ />
+ ) : type === "note" ? (
+ <Text className="h-16 w-16" />
+ ) : (
+ <></>
+ )}
+ </div>
+ </div>
+ <DialogContent className="w-max max-w-[auto]">
+ <NoteEdit closeDialog={() => setIsDialogOpen(false)} memory={props} />
+ </DialogContent>
+ </Dialog>
);
}
@@ -254,6 +273,9 @@ export function SpaceItem({
}, [cachedMemories]);
const _name = name.length > 10 ? name.slice(0, 10) + "..." : name;
+
+ console.log(spaceMemories)
+
return (
<motion.div
ref={itemRef}
@@ -396,7 +418,7 @@ export function SpaceMoreButton({
setIsOpen?: (open: boolean) => void;
}) {
return (
- <Dialog>
+ <DeleteConfirmation onDelete={onDelete} trigger={false}>
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuTrigger asChild>
<button
@@ -426,25 +448,7 @@ export function SpaceMoreButton({
</DialogTrigger>
</DropdownMenuContent>
</DropdownMenu>
- <DialogContent>
- <DialogTitle className="text-xl">Are you sure?</DialogTitle>
- <DialogDescription className="text-md">
- You will not be able to recover this space
- </DialogDescription>
- <DialogFooter>
- <DialogClose
- type={undefined}
- onClick={onDelete}
- className="ml-auto flex items-center justify-center rounded-md bg-red-500/40 px-3 py-2 transition hover:bg-red-500/60 focus-visible:bg-red-500/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500"
- >
- Delete
- </DialogClose>
- <DialogClose 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">
- Cancel
- </DialogClose>
- </DialogFooter>
- </DialogContent>
- </Dialog>
+ </DeleteConfirmation>
);
}
diff --git a/apps/web/src/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx
index e10984bb..d8a147a5 100644
--- a/apps/web/src/contexts/MemoryContext.tsx
+++ b/apps/web/src/contexts/MemoryContext.tsx
@@ -14,6 +14,7 @@ import {
deleteSpace,
deleteMemory,
fetchFreeMemories,
+ updateMemory,
} from "@/actions/db";
import { User } from "next-auth";
@@ -33,6 +34,7 @@ export const MemoryContext = React.createContext<{
search: typeof searchMemoriesAndSpaces;
deleteSpace: typeof deleteSpace;
deleteMemory: typeof deleteMemory;
+ updateMemory: typeof updateMemory;
}>({
spaces: [],
freeMemories: [],
@@ -42,6 +44,7 @@ export const MemoryContext = React.createContext<{
search: async () => [],
deleteMemory: (() => {}) as unknown as typeof deleteMemory,
deleteSpace: (() => {}) as unknown as typeof deleteSpace,
+ updateMemory: (() => {}) as unknown as typeof updateMemory,
});
export const MemoryProvider: React.FC<
@@ -98,7 +101,7 @@ export const MemoryProvider: React.FC<
await fetchContentForSpace(addedSpace.id, {
offset: 0,
limit: 3,
- })
+ }) ?? []
).map((m) => ({ ...m, space: addedSpace.id }));
setCachedMemories((prev) => [...prev, ...cachedMemories]);
@@ -132,6 +135,40 @@ export const MemoryProvider: React.FC<
};
};
+ const _updateMemory: typeof updateMemory = async (id, _data) => {
+ const data = await updateMemory(id, _data);
+
+ console.log(data)
+
+ if (data) {
+ if (!_data.spaces) {
+ console.log("non spaces", freeMemories.map(i => i.id === data.memory.id ? data.memory : i ))
+ setCachedMemories(prev => prev.map(i => i.id === data.memory.id ? { ...data.memory, space: i.space } : i ))
+ setFreeMemories(prev => prev.map(i => i.id === data.memory.id ? data.memory : i ))
+ return data
+ }
+ setCachedMemories(prev => prev.filter(i => i.id !== data.memory.id))
+ setFreeMemories(prev => prev.filter(i => i.id !== data.memory.id))
+ if (_data.spaces.length > 0) {
+ console.log('has space')
+ setCachedMemories(
+ prev => [
+ ...prev,
+ ..._data.spaces!.map(s => ({
+ ...data.memory,
+ space: s
+ }))
+ ]
+ )
+ } else {
+ console.log('does nto have space')
+ setFreeMemories(prev => [...prev, data.memory])
+ }
+ }
+
+ return data
+ }
+
return (
<MemoryContext.Provider
value={{
@@ -143,6 +180,7 @@ export const MemoryProvider: React.FC<
cachedMemories,
deleteMemory: _deleteMemory,
addMemory: _addMemory,
+ updateMemory: _updateMemory
}}
>
{children}
diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts
index f50b526d..0fe5bdfd 100644
--- a/apps/web/src/lib/utils.ts
+++ b/apps/web/src/lib/utils.ts
@@ -87,3 +87,27 @@ export function countLines(textarea: HTMLTextAreaElement): number {
export function convertRemToPixels(rem: number) {
return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
}
+
+export function isArraysEqual(a: any[], b: any[]) {
+ 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)
+ })
+
+ if (!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 6bfda971..f5939224 100644
--- a/apps/web/types/memory.tsx
+++ b/apps/web/types/memory.tsx
@@ -125,6 +125,6 @@ export type ChatHistory = {
question: string;
answer: {
parts: { text: string }[];
- sources: string[];
+ sources: { isNote: boolean; source: string }[];
};
};