mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
ok
This commit is contained in:
parent
f9c34b7eeb
commit
bf99ee97f7
7 changed files with 9000 additions and 61 deletions
8718
apps/web/pnpm-lock.yaml
generated
Normal file
8718
apps/web/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
52
apps/web/src/actions/db.ts
Normal file
52
apps/web/src/actions/db.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"use server";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
contentToSpace,
|
||||
StoredContent,
|
||||
storedContent,
|
||||
} from "@/server/db/schema";
|
||||
import { like, eq, and } from "drizzle-orm";
|
||||
import { auth as authOptions } from "@/server/auth";
|
||||
import { getSession } from "next-auth/react";
|
||||
|
||||
export async function getMemory(title: string) {
|
||||
const session = await getSession();
|
||||
|
||||
console.log(session?.user?.name);
|
||||
|
||||
if (!session || !session.user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await db
|
||||
.select()
|
||||
.from(storedContent)
|
||||
.where(
|
||||
and(
|
||||
eq(storedContent.user, session.user.id!),
|
||||
like(storedContent.title, `%${title}%`),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function addMemory(
|
||||
content: typeof storedContent.$inferInsert,
|
||||
spaces: number[],
|
||||
) {
|
||||
const session = await getSession();
|
||||
|
||||
if (!session || !session.user) {
|
||||
return null;
|
||||
}
|
||||
content.user = session.user.id;
|
||||
|
||||
const _content = (
|
||||
await db.insert(storedContent).values(content).returning()
|
||||
)[0];
|
||||
await Promise.all(
|
||||
spaces.map((spaceId) =>
|
||||
db.insert(contentToSpace).values({ contentId: _content.id, spaceId }),
|
||||
),
|
||||
);
|
||||
return _content;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { FilterCombobox } from "./Sidebar/FilterCombobox";
|
||||
import { FilterSpaces } from "./Sidebar/FilterCombobox";
|
||||
import { Textarea2 } from "./ui/textarea";
|
||||
import { ArrowRight, ArrowUp } from "lucide-react";
|
||||
import { MemoryDrawer } from "./MemoryDrawer";
|
||||
|
|
@ -307,7 +307,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
|||
}}
|
||||
>
|
||||
<div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
|
||||
<FilterCombobox
|
||||
<FilterSpaces
|
||||
name={"Filter"}
|
||||
onClose={() => {
|
||||
textArea.current?.querySelector("textarea")?.focus();
|
||||
|
|
@ -383,7 +383,7 @@ export function Chat({
|
|||
className="absolute flex w-full items-center justify-center"
|
||||
>
|
||||
<div className="animate-from-top fixed bottom-10 mt-auto flex w-[50%] flex-col items-start justify-center gap-2">
|
||||
<FilterCombobox
|
||||
<FilterSpaces
|
||||
name={"Filter"}
|
||||
onClose={() => {
|
||||
textArea.current?.querySelector("textarea")?.focus();
|
||||
|
|
|
|||
|
|
@ -10,13 +10,17 @@ import { Input } from "../ui/input";
|
|||
import { Label } from "../ui/label";
|
||||
import { Markdown } from "tiptap-markdown";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { FilterCombobox } from "./FilterCombobox";
|
||||
import { FilterSpaces } from "./FilterCombobox";
|
||||
import { useMemory } from "@/contexts/MemoryContext";
|
||||
|
||||
export function AddMemoryPage() {
|
||||
const { addMemory } = useMemory();
|
||||
|
||||
const [url, setUrl] = useState("");
|
||||
const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>([]);
|
||||
|
||||
return (
|
||||
<div className="md:w-[40vw]">
|
||||
<form className="md:w-[40vw]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a web page to memory</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
|
@ -30,25 +34,41 @@ export function AddMemoryPage() {
|
|||
type="url"
|
||||
data-modal-autofocus
|
||||
className="bg-rgray-4 mt-2 w-full"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<FilterCombobox
|
||||
<FilterSpaces
|
||||
selectedSpaces={selectedSpacesId}
|
||||
setSelectedSpaces={setSelectedSpacesId}
|
||||
className="hover:bg-rgray-5 mr-auto bg-white/5"
|
||||
name={"Spaces"}
|
||||
/>
|
||||
<DialogClose
|
||||
type={undefined}
|
||||
<button
|
||||
type={"submit"}
|
||||
onClick={async () => {
|
||||
// @Dhravya this is adding a memory with insufficient information fix pls
|
||||
await addMemory(
|
||||
{
|
||||
title: url,
|
||||
content: "",
|
||||
type: "page",
|
||||
url: url,
|
||||
image: "/icons/logo_without_bg.png",
|
||||
savedAt: new Date(),
|
||||
},
|
||||
selectedSpacesId,
|
||||
);
|
||||
}}
|
||||
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"
|
||||
>
|
||||
Add
|
||||
</DialogClose>
|
||||
</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">
|
||||
Cancel
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -60,16 +80,15 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
const [content, setContent] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
function check() {
|
||||
function check(): boolean {
|
||||
const data = {
|
||||
name: name.trim(),
|
||||
content,
|
||||
};
|
||||
console.log(name);
|
||||
if (!data.name || data.name.length < 1) {
|
||||
if (!inputRef.current) {
|
||||
alert("Please enter a name for the note");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
inputRef.current.value = "";
|
||||
inputRef.current.placeholder = "Please enter a title for the note";
|
||||
|
|
@ -79,7 +98,9 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
inputRef.current!.dataset["error"] = "false";
|
||||
}, 500);
|
||||
inputRef.current.focus();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -87,7 +108,7 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
<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-200 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400"
|
||||
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"
|
||||
data-modal-autofocus
|
||||
value={name}
|
||||
|
|
@ -104,7 +125,7 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
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>
|
||||
<FilterCombobox
|
||||
<FilterSpaces
|
||||
selectedSpaces={selectedSpacesId}
|
||||
setSelectedSpaces={setSelectedSpacesId}
|
||||
className="hover:bg-rgray-5 mr-auto bg-white/5"
|
||||
|
|
@ -112,7 +133,9 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
check();
|
||||
if (check()) {
|
||||
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"
|
||||
>
|
||||
|
|
@ -137,7 +160,7 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
const [content, setContent] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
function check() {
|
||||
function check(): boolean {
|
||||
const data = {
|
||||
name: name.trim(),
|
||||
content,
|
||||
|
|
@ -146,7 +169,7 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
if (!data.name || data.name.length < 1) {
|
||||
if (!inputRef.current) {
|
||||
alert("Please enter a name for the note");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
inputRef.current.value = "";
|
||||
inputRef.current.placeholder = "Please enter a title for the note";
|
||||
|
|
@ -156,49 +179,32 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
inputRef.current!.dataset["error"] = "false";
|
||||
}, 500);
|
||||
inputRef.current.focus();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="md:w-[40vw]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a space</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Label className="mt-5 block">Name</Label>
|
||||
<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-200 focus-visible:ring-0 data-[error=true]:placeholder:text-red-400"
|
||||
placeholder="Title of the note"
|
||||
placeholder="Enter the name of the space"
|
||||
type="url"
|
||||
data-modal-autofocus
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<Editor
|
||||
disableLocalStorage
|
||||
defaultValue={""}
|
||||
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"
|
||||
className="bg-rgray-4 mt-2 w-full"
|
||||
/>
|
||||
<Label className="mt-5 block">Memories</Label>
|
||||
<DialogFooter>
|
||||
<FilterCombobox
|
||||
selectedSpaces={selectedSpacesId}
|
||||
setSelectedSpaces={setSelectedSpacesId}
|
||||
className="hover:bg-rgray-5 mr-auto bg-white/5"
|
||||
name={"Spaces"}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
check();
|
||||
}}
|
||||
<DialogClose
|
||||
type={undefined}
|
||||
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"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<DialogClose
|
||||
type={undefined}
|
||||
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>
|
||||
<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">
|
||||
Cancel
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,124 @@ export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export function FilterCombobox({
|
||||
export function FilterSpaces({
|
||||
className,
|
||||
side = "bottom",
|
||||
align = "center",
|
||||
onClose,
|
||||
selectedSpaces,
|
||||
setSelectedSpaces,
|
||||
name,
|
||||
...props
|
||||
}: Props) {
|
||||
const { spaces } = useMemory();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const sortedSpaces = spaces.sort(({ id: a }, { id: b }) =>
|
||||
selectedSpaces.includes(a) && !selectedSpaces.includes(b)
|
||||
? -1
|
||||
: selectedSpaces.includes(b) && !selectedSpaces.includes(a)
|
||||
? 1
|
||||
: 0,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!open) {
|
||||
onClose?.();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<AnimatePresence mode="popLayout">
|
||||
<LayoutGroup>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type={undefined}
|
||||
data-state-on={open}
|
||||
className={cn(
|
||||
"text-rgray-11/70 on:bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-3 relative flex items-center justify-center gap-1 rounded-md px-3 py-1.5 ring-2 ring-transparent focus-visible:outline-none",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SpaceIcon className="mr-1 h-5 w-5" />
|
||||
{name}
|
||||
<ChevronsUpDown className="h-4 w-4" />
|
||||
<div
|
||||
data-state-on={selectedSpaces.length > 0}
|
||||
className="on:flex text-rgray-11 border-rgray-6 bg-rgray-2 absolute left-0 top-0 hidden aspect-[1] h-4 w-4 -translate-x-1/3 -translate-y-1/3 items-center justify-center rounded-full border text-center text-[9px]"
|
||||
>
|
||||
{selectedSpaces.length}
|
||||
</div>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||
align={align}
|
||||
side={side}
|
||||
className="w-[200px] p-0"
|
||||
>
|
||||
<Command
|
||||
filter={(val, search) =>
|
||||
spaces
|
||||
.find((s) => s.id.toString() === val)
|
||||
?.title.toLowerCase()
|
||||
.includes(search.toLowerCase().trim())
|
||||
? 1
|
||||
: 0
|
||||
}
|
||||
>
|
||||
<CommandInput placeholder="Filter spaces..." />
|
||||
<CommandList asChild>
|
||||
<motion.div layoutScroll>
|
||||
<CommandEmpty>Nothing found</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{sortedSpaces.map((space) => (
|
||||
<CommandItem
|
||||
key={space.id}
|
||||
value={space.id.toString()}
|
||||
onSelect={(val) => {
|
||||
setSelectedSpaces((prev: number[]) =>
|
||||
prev.includes(parseInt(val))
|
||||
? prev.filter((v) => v !== parseInt(val))
|
||||
: [...prev, parseInt(val)],
|
||||
);
|
||||
}}
|
||||
asChild
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, transition: { delay: 0.05 } }}
|
||||
transition={{ duration: 0.15 }}
|
||||
layout
|
||||
layoutId={`space-combobox-${space.id}`}
|
||||
className="text-rgray-11"
|
||||
>
|
||||
<SpaceIcon className="mr-2 h-4 w-4" />
|
||||
{space.title}
|
||||
{selectedSpaces.includes(space.id)}
|
||||
<Check
|
||||
data-state-on={selectedSpaces.includes(space.id)}
|
||||
className={cn(
|
||||
"on:opacity-100 ml-auto h-4 w-4 opacity-0",
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</motion.div>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</LayoutGroup>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
||||
export function FilterMemories({
|
||||
className,
|
||||
side = "bottom",
|
||||
align = "center",
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ 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 } from "./AddMemoryDialog";
|
||||
import { AddMemoryPage, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog";
|
||||
|
||||
export function MemoriesBar() {
|
||||
const [parent, enableAnimations] = useAutoAnimate();
|
||||
|
|
@ -91,10 +91,16 @@ export function MemoriesBar() {
|
|||
Note
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DropdownMenuItem>
|
||||
<SpaceIcon className="mr-2 h-4 w-4" />
|
||||
Space
|
||||
</DropdownMenuItem>
|
||||
<DialogTrigger className="block w-full">
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setAddMemoryState("space");
|
||||
}}
|
||||
>
|
||||
<SpaceIcon className="mr-2 h-4 w-4" />
|
||||
Space
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</AddMemoryModal>
|
||||
|
|
@ -343,6 +349,8 @@ export function AddMemoryModal({
|
|||
<AddMemoryPage />
|
||||
) : type === "note" ? (
|
||||
<NoteAddPage closeDialog={() => setIsDialogOpen(false)} />
|
||||
) : type === "space" ? (
|
||||
<SpaceAddPage closeDialog={() => setIsDialogOpen(false)} />
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,37 @@
|
|||
"use client";
|
||||
import React, { useCallback } from "react";
|
||||
import { CollectedSpaces } from "../../types/memory";
|
||||
import { StoredContent, storedContent } from "@/server/db/schema";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { addMemory } from "@/actions/db";
|
||||
|
||||
// temperory (will change)
|
||||
export const MemoryContext = React.createContext<{
|
||||
spaces: CollectedSpaces[];
|
||||
deleteSpace: (id: number) => Promise<void>;
|
||||
freeMemories: StoredContent[];
|
||||
addSpace: (space: CollectedSpaces) => Promise<void>;
|
||||
addMemory: (
|
||||
memory: typeof storedContent.$inferInsert,
|
||||
spaces?: number[],
|
||||
) => Promise<void>;
|
||||
}>({
|
||||
spaces: [],
|
||||
addSpace: async (space) => {},
|
||||
deleteSpace: async (id) => {},
|
||||
freeMemories: [],
|
||||
addMemory: async () => {},
|
||||
addSpace: async () => {},
|
||||
deleteSpace: async () => {},
|
||||
});
|
||||
|
||||
export const MemoryProvider: React.FC<
|
||||
{ spaces: CollectedSpaces[] } & React.PropsWithChildren
|
||||
> = ({ children, spaces: initalSpaces }) => {
|
||||
{
|
||||
spaces: CollectedSpaces[];
|
||||
freeMemories: StoredContent[];
|
||||
} & React.PropsWithChildren
|
||||
> = ({ children, spaces: initalSpaces, freeMemories: initialFreeMemories }) => {
|
||||
const [spaces, setSpaces] = React.useState<CollectedSpaces[]>(initalSpaces);
|
||||
const [freeMemories, setFreeMemories] =
|
||||
React.useState<StoredContent[]>(initialFreeMemories);
|
||||
|
||||
const addSpace = useCallback(
|
||||
async (space: CollectedSpaces) => {
|
||||
|
|
@ -31,8 +46,31 @@ export const MemoryProvider: React.FC<
|
|||
[spaces],
|
||||
);
|
||||
|
||||
// const fetchMemories = useCallback(async (query: string) => {
|
||||
// const response = await fetch(`/api/memories?${query}`);
|
||||
// }, []);
|
||||
|
||||
const _addMemory = useCallback(
|
||||
async (
|
||||
memory: typeof storedContent.$inferInsert,
|
||||
spaces: number[] = [],
|
||||
) => {
|
||||
const content = await addMemory(memory, spaces);
|
||||
console.log(content);
|
||||
},
|
||||
[freeMemories, spaces],
|
||||
);
|
||||
|
||||
return (
|
||||
<MemoryContext.Provider value={{ spaces, addSpace, deleteSpace }}>
|
||||
<MemoryContext.Provider
|
||||
value={{
|
||||
spaces,
|
||||
addSpace,
|
||||
deleteSpace,
|
||||
freeMemories,
|
||||
addMemory: _addMemory,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MemoryContext.Provider>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue