aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components')
-rw-r--r--apps/web/src/components/ChatMessage.tsx40
-rw-r--r--apps/web/src/components/MemoryDrawer.tsx24
-rw-r--r--apps/web/src/components/Sidebar/DeleteConfirmation.tsx48
-rw-r--r--apps/web/src/components/Sidebar/ExpandedSpace.tsx325
4 files changed, 248 insertions, 189 deletions
diff --git a/apps/web/src/components/ChatMessage.tsx b/apps/web/src/components/ChatMessage.tsx
index 0ab22271..58ef9870 100644
--- a/apps/web/src/components/ChatMessage.tsx
+++ b/apps/web/src/components/ChatMessage.tsx
@@ -12,7 +12,7 @@ export function ChatAnswer({
loading = false,
}: {
children: string;
- sources?: ChatHistory['answer']['sources'];
+ sources?: ChatHistory["answer"]["sources"];
loading?: boolean;
}) {
return (
@@ -30,25 +30,25 @@ export function ChatAnswer({
<SpaceIcon className="h-6 w-6 -translate-y-[2px]" />
Related Memories
</h1>
- <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.source}
- href={source.source}
- target="_blank"
- >
- <Globe className="h-4 w-4" />
- {cleanUrl(source.source)}
- </a>
- ))}
+ <div className="animate-fade-in -mt-3 flex items-center justify-start gap-1 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="h-4 w-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.source}
+ href={source.source}
+ target="_blank"
+ >
+ <Globe className="h-4 w-4" />
+ {cleanUrl(source.source)}
+ </a>
+ ),
+ )}
</div>
</>
)}
diff --git a/apps/web/src/components/MemoryDrawer.tsx b/apps/web/src/components/MemoryDrawer.tsx
index a71d3d19..14283281 100644
--- a/apps/web/src/components/MemoryDrawer.tsx
+++ b/apps/web/src/components/MemoryDrawer.tsx
@@ -32,18 +32,18 @@ export function MemoryDrawer({ className, hide = false, ...props }: Props) {
)}
handle={false}
>
- <button
- onClick={() =>
- setActiveSnapPoint((prev) => (prev === 0.9 ? 0.1 : 0.9))
- }
- className="bg-rgray-4 border-rgray-6 text-rgray-11 absolute left-1/2 top-0 flex w-fit -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-2 rounded-md border px-3 py-2"
- >
- <MemoryIcon className="h-7 w-7" />
- Memories
- </button>
- <div className="w-full h-full overflow-y-auto">
- <MemoriesBar isOpen={true} />
- </div>
+ <button
+ onClick={() =>
+ setActiveSnapPoint((prev) => (prev === 0.9 ? 0.1 : 0.9))
+ }
+ className="bg-rgray-4 border-rgray-6 text-rgray-11 absolute left-1/2 top-0 flex w-fit -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-2 rounded-md border px-3 py-2"
+ >
+ <MemoryIcon className="h-7 w-7" />
+ Memories
+ </button>
+ <div className="h-full w-full overflow-y-auto">
+ <MemoriesBar isOpen={true} />
+ </div>
</DrawerContent>
<DrawerOverlay className="relative bg-transparent" />
</Drawer>
diff --git a/apps/web/src/components/Sidebar/DeleteConfirmation.tsx b/apps/web/src/components/Sidebar/DeleteConfirmation.tsx
index 9324b147..7955df0d 100644
--- a/apps/web/src/components/Sidebar/DeleteConfirmation.tsx
+++ b/apps/web/src/components/Sidebar/DeleteConfirmation.tsx
@@ -1,18 +1,30 @@
-import { Dialog, DialogContent, DialogTrigger, DialogTitle, DialogDescription, DialogClose, DialogFooter } from "../ui/dialog";
+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>
+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.
@@ -21,7 +33,7 @@ export default function DeleteConfirmation({ onDelete, trigger = true, children
<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"
+ className="ml-auto flex items-center justify-center rounded-md bg-red-100/10 px-3 py-2 text-red-400 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>
@@ -29,7 +41,7 @@ export default function DeleteConfirmation({ onDelete, trigger = true, children
Cancel
</DialogClose>
</DialogFooter>
- </DialogContent>
- </Dialog>
- )
+ </DialogContent>
+ </Dialog>
+ );
}
diff --git a/apps/web/src/components/Sidebar/ExpandedSpace.tsx b/apps/web/src/components/Sidebar/ExpandedSpace.tsx
index 9e46f3fb..55d3f3f8 100644
--- a/apps/web/src/components/Sidebar/ExpandedSpace.tsx
+++ b/apps/web/src/components/Sidebar/ExpandedSpace.tsx
@@ -1,33 +1,52 @@
-import { fetchContentForSpace, getSpace } from "@/actions/db"
-import { useMemory } from "@/contexts/MemoryContext"
-import { StoredContent, StoredSpace } from '@/server/db/schema'
-import { Edit3, Loader, Plus, Search, Sparkles, StickyNote, Text, Undo2 } from "lucide-react"
-import { useEffect, useRef, useState } from "react"
-import { Input, InputWithIcon } from "../ui/input"
-import { useDebounce } from "@/hooks/useDebounce"
-import { useAutoAnimate } from "@formkit/auto-animate/react"
-import { AddMemoryModal, MemoryItem } from "./MemoriesBar"
-import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu"
-import { DialogTrigger } from "../ui/dialog"
-
-export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () => void; }) {
-
- const { updateMemory, updateSpace, search } = useMemory()
+import { fetchContentForSpace, getSpace } from "@/actions/db";
+import { useMemory } from "@/contexts/MemoryContext";
+import { StoredContent, StoredSpace } from "@/server/db/schema";
+import {
+ Edit3,
+ Loader,
+ Plus,
+ Search,
+ Sparkles,
+ StickyNote,
+ Text,
+ Undo2,
+} from "lucide-react";
+import { useEffect, useRef, useState } from "react";
+import { Input, InputWithIcon } from "../ui/input";
+import { useDebounce } from "@/hooks/useDebounce";
+import { useAutoAnimate } from "@formkit/auto-animate/react";
+import { AddMemoryModal, MemoryItem } from "./MemoriesBar";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "../ui/dropdown-menu";
+import { DialogTrigger } from "../ui/dialog";
+
+export function ExpandedSpace({
+ spaceId,
+ back,
+}: {
+ spaceId: number;
+ back: () => void;
+}) {
+ const { updateMemory, updateSpace, search } = useMemory();
const [parent, enableAnimations] = useAutoAnimate();
- const inputRef = useRef<HTMLInputElement>(null);
+ const inputRef = useRef<HTMLInputElement>(null);
- const [contentForSpace, setContentForSpace] = useState<StoredContent[]>([])
+ const [contentForSpace, setContentForSpace] = useState<StoredContent[]>([]);
- const [lastUpdatedTitle, setLastUpdatedTitle] = useState<string | null>(null);
+ const [lastUpdatedTitle, setLastUpdatedTitle] = useState<string | null>(null);
- const [title, setTitle] = useState<string>('');
- const debouncedTitle = useDebounce(title, 500)
+ const [title, setTitle] = useState<string>("");
+ const debouncedTitle = useDebounce(title, 500);
- const [loading, setLoading] = useState(true)
+ const [loading, setLoading] = useState(true);
- const [saveLoading, setSaveLoading] = useState(false);
+ const [saveLoading, setSaveLoading] = useState(false);
const [searchQuery, setSearcyQuery] = useState("");
const [searchLoading, setSearchLoading] = useState(false);
@@ -35,33 +54,34 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () =>
const [searchResults, setSearchResults] = useState<StoredContent[]>([]);
-
const [addMemoryState, setAddMemoryState] = useState<
"page" | "note" | "existing-memory" | "space" | null
>(null);
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
-
-
- useEffect(() => {
- (async () => {
- const title = (await getSpace(spaceId))?.name ?? "";
- setTitle(title)
- setLastUpdatedTitle(title)
- setContentForSpace((await fetchContentForSpace(spaceId)) ?? [])
- setLoading(false)
- })();
- }, [])
-
- useEffect(() => {
- if (debouncedTitle.trim().length < 1 || debouncedTitle.trim() === lastUpdatedTitle?.trim()) return
- (async () => {
- setSaveLoading(true)
- await updateSpace(spaceId, debouncedTitle.trim())
- setLastUpdatedTitle(debouncedTitle)
- setSaveLoading(false)
- })()
- }, [debouncedTitle])
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+
+ useEffect(() => {
+ (async () => {
+ const title = (await getSpace(spaceId))?.name ?? "";
+ setTitle(title);
+ setLastUpdatedTitle(title);
+ setContentForSpace((await fetchContentForSpace(spaceId)) ?? []);
+ setLoading(false);
+ })();
+ }, []);
+ useEffect(() => {
+ if (
+ debouncedTitle.trim().length < 1 ||
+ debouncedTitle.trim() === lastUpdatedTitle?.trim()
+ )
+ return;
+ (async () => {
+ setSaveLoading(true);
+ await updateSpace(spaceId, debouncedTitle.trim());
+ setLastUpdatedTitle(debouncedTitle);
+ setSaveLoading(false);
+ })();
+ }, [debouncedTitle]);
useEffect(() => {
const q = query.trim();
@@ -73,56 +93,68 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () =>
setSearchLoading(true);
(async () => {
- setSearchResults((await search(q, {
- filter: { spaces: false },
- memoriesRelativeToSpace: {
- fromSpaces: [spaceId]
- }
- })).map(i => i.memory!));
+ setSearchResults(
+ (
+ await search(q, {
+ filter: { spaces: false },
+ memoriesRelativeToSpace: {
+ fromSpaces: [spaceId],
+ },
+ })
+ ).map((i) => i.memory!),
+ );
setSearchLoading(false);
})();
}, [query]);
-
- if (loading) {
- return (
- <div className="h-full w-full flex justify-center items-center">
- <Loader className="w-5 h-5 animate-spin" />
- </div>
- )
- }
+ if (loading) {
+ return (
+ <div className="flex h-full w-full items-center justify-center">
+ <Loader className="h-5 w-5 animate-spin" />
+ </div>
+ );
+ }
return (
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
- <div className="px-8 flex justify-start items-center w-full gap-2">
- <button onClick={back} className="transition rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7">
- <Undo2 className="w-5 h-5" />
- </button>
- <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 space"
- data-modal-autofocus
- value={title}
- onChange={(e) => setTitle(e.target.value)}
- />
- <button onClick={() => {
- inputRef.current?.focus()
- inputRef.current?.animate({
- opacity: [1, 0.2, 1]
- }, {
- duration: 100
- })
- }} className="transition rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7">
- {saveLoading ? (
- <Loader className="w-5 h-5 opacity-70 animate-spin" />
- ) : (
- <Edit3 className="w-5 h-5 opacity-70" />
- )}
- </button>
- </div>
- <div className="px-8 w-full">
+ <div className="flex w-full items-center justify-start gap-2 px-8">
+ <button
+ onClick={back}
+ className="focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7 rounded-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
+ >
+ <Undo2 className="h-5 w-5" />
+ </button>
+ <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 space"
+ data-modal-autofocus
+ value={title}
+ onChange={(e) => setTitle(e.target.value)}
+ />
+ <button
+ onClick={() => {
+ inputRef.current?.focus();
+ inputRef.current?.animate(
+ {
+ opacity: [1, 0.2, 1],
+ },
+ {
+ duration: 100,
+ },
+ );
+ }}
+ className="focus-visible:ring-offset-rgray-3 focus-visible:ring-rgray-7 rounded-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
+ >
+ {saveLoading ? (
+ <Loader className="h-5 w-5 animate-spin opacity-70" />
+ ) : (
+ <Edit3 className="h-5 w-5 opacity-70" />
+ )}
+ </button>
+ </div>
+ <div className="w-full px-8">
<InputWithIcon
placeholder="Search"
icon={
@@ -136,23 +168,28 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () =>
value={searchQuery}
onChange={(e) => setSearcyQuery(e.target.value)}
/>
- </div>
- <div className="w-full px-8 mt-2">
- <AddMemoryModal onAdd={(data) => {
- if (!data) {
- setLoading(true);
- (async () => {
- const title = (await getSpace(spaceId))?.name ?? "";
- setTitle(title)
- setLastUpdatedTitle(title)
- setContentForSpace((await fetchContentForSpace(spaceId)) ?? [])
- setLoading(false)
- })();
- } else if (Object.hasOwn(data, "url")) {
- const _data = data as StoredContent;
- setContentForSpace(prev => [...prev, _data])
- }
- }} data={{ space: { title, id: spaceId }, notInSpaces: [spaceId] }} defaultSpaces={[spaceId]} type={addMemoryState}>
+ </div>
+ <div className="mt-2 w-full px-8">
+ <AddMemoryModal
+ onAdd={(data) => {
+ if (!data) {
+ setLoading(true);
+ (async () => {
+ const title = (await getSpace(spaceId))?.name ?? "";
+ setTitle(title);
+ setLastUpdatedTitle(title);
+ setContentForSpace((await fetchContentForSpace(spaceId)) ?? []);
+ setLoading(false);
+ })();
+ } else if (Object.hasOwn(data, "url")) {
+ const _data = data as StoredContent;
+ setContentForSpace((prev) => [...prev, _data]);
+ }
+ }}
+ data={{ space: { title, id: spaceId }, notInSpaces: [spaceId] }}
+ defaultSpaces={[spaceId]}
+ type={addMemoryState}
+ >
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
<DropdownMenuTrigger asChild>
<button 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">
@@ -194,47 +231,57 @@ export function ExpandedSpace({ spaceId, back }: { spaceId: number, back: () =>
</DropdownMenuContent>
</DropdownMenu>
</AddMemoryModal>
- </div>
+ </div>
<div
ref={parent}
className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5"
>
- {query.trim().length > 0 ? (
+ {query.trim().length > 0 ? (
<>
{searchResults.map((memory, i) => (
- <MemoryItem
- removeFromSpace={async () => {
- await updateMemory(memory.id, {
- removedFromSpaces: [spaceId]
- })
- setContentForSpace(prev => prev.filter(s => s.id !== memory.id))
- setSearchResults(prev => prev.filter(i => i.id !== memory.id))
- }}
- {...memory!}
- key={i}
- onDelete={() => {
- setContentForSpace(prev => prev.filter(s => s.id !== memory.id))
- setSearchResults(prev => prev.filter(i => i.id !== memory.id))
- }}
- />
+ <MemoryItem
+ removeFromSpace={async () => {
+ await updateMemory(memory.id, {
+ removedFromSpaces: [spaceId],
+ });
+ setContentForSpace((prev) =>
+ prev.filter((s) => s.id !== memory.id),
+ );
+ setSearchResults((prev) =>
+ prev.filter((i) => i.id !== memory.id),
+ );
+ }}
+ {...memory!}
+ key={i}
+ onDelete={() => {
+ setContentForSpace((prev) =>
+ prev.filter((s) => s.id !== memory.id),
+ );
+ setSearchResults((prev) =>
+ prev.filter((i) => i.id !== memory.id),
+ );
+ }}
+ />
))}
</>
- ) :
- contentForSpace.map(m => (
- <MemoryItem
- key={m.id}
- {...m}
- onDelete={() => setContentForSpace(prev => prev.filter(s => s.id !== m.id))}
- removeFromSpace={async () => {
- await updateMemory(m.id, {
- removedFromSpaces: [spaceId]
- })
- setContentForSpace(prev => prev.filter(s => s.id !== m.id))
- }}
- />
- ))
- }
- </div>
+ ) : (
+ contentForSpace.map((m) => (
+ <MemoryItem
+ key={m.id}
+ {...m}
+ onDelete={() =>
+ setContentForSpace((prev) => prev.filter((s) => s.id !== m.id))
+ }
+ removeFromSpace={async () => {
+ await updateMemory(m.id, {
+ removedFromSpaces: [spaceId],
+ });
+ setContentForSpace((prev) => prev.filter((s) => s.id !== m.id));
+ }}
+ />
+ ))
+ )}
+ </div>
</div>
- )
+ );
}