This commit is contained in:
Yash 2024-04-11 11:02:24 +00:00
parent f9c34b7eeb
commit bf99ee97f7
7 changed files with 9000 additions and 61 deletions

8718
apps/web/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

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

View file

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

View file

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

View file

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

View file

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

View file

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