new modals

This commit is contained in:
yxshv 2024-04-14 14:29:23 +05:30
parent 3ae0c13de9
commit fa39265142
13 changed files with 590 additions and 122 deletions

View file

@ -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,30 +327,7 @@ export async function addMemory(
return null;
}
console.log(content);
console.log({ ...content, user: user.email });
// Add to vectorDB
const res = (await Promise.race([
fetch("https://cf-ai-backend.dhravya.workers.dev/add", {
method: "POST",
headers: {
"X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY,
},
body: JSON.stringify({
pageContent: content.content,
title: content.title,
url: content.url,
user: user.email,
}),
}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Request timed out")), 40000),
),
])) as Response;
const [addedMemory] = await db
let [addedMemory] = await db
.insert(storedContent)
.values({
user: user.id,
@ -303,12 +348,142 @@ 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]
}
// Add to vectorDB
const res = (await Promise.race([
fetch("https://cf-ai-backend.dhravya.workers.dev/add", {
method: "POST",
headers: {
"X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY,
},
body: JSON.stringify({
pageContent: addedMemory.content,
title: addedMemory.title,
url: addedMemory.url,
user: user.email,
}),
}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Request timed out")), 40000),
),
])) as Response;
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 && spaces.length > 0)
? await db
.insert(contentToSpace)
.values(
spaces.map((s) => ({
contentId: id,
spaceId: s,
})),
)
.onConflictDoNothing()
.returning()
: [];
return {
memory: updatedMemory,
addedToSpaces,
removedFromSpaces
};
}
export async function deleteSpace(id: number) {
const user = await getUser();
@ -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;
}

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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>

View file

@ -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
},
},
];

View file

@ -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(),
},

View file

@ -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>
)
}

View file

@ -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>
);
}

View file

@ -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

View file

@ -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>";
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>
const [isDialogOpen, setIsDialogOpen] = useState(false);
<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>
return (
<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" ? (
<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>
);
}

View file

@ -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}

View file

@ -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
}

View file

@ -125,6 +125,6 @@ export type ChatHistory = {
question: string;
answer: {
parts: { text: string }[];
sources: string[];
sources: { isNote: boolean; source: string }[];
};
};