aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
authoryxshv <[email protected]>2024-04-13 15:01:51 +0530
committeryxshv <[email protected]>2024-04-13 15:01:51 +0530
commit070ead455120d6da3d0a6843cb0d842a65359c43 (patch)
tree67196c3aaec0a07e0b0151f21582fe22ad068ae7 /apps/web/src
parentmemory item (diff)
downloadsupermemory-070ead455120d6da3d0a6843cb0d842a65359c43.tar.xz
supermemory-070ead455120d6da3d0a6843cb0d842a65359c43.zip
search results
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/actions/db.ts61
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx70
-rw-r--r--apps/web/src/contexts/MemoryContext.tsx2
-rw-r--r--apps/web/src/hooks/useDebounce.ts27
4 files changed, 122 insertions, 38 deletions
diff --git a/apps/web/src/actions/db.ts b/apps/web/src/actions/db.ts
index 46e3ddf6..db301e01 100644
--- a/apps/web/src/actions/db.ts
+++ b/apps/web/src/actions/db.ts
@@ -6,36 +6,51 @@ import {
sessions,
StoredContent,
storedContent,
+ StoredSpace,
users,
space
} from "@/server/db/schema";
+import { SearchResult } from "@/contexts/MemoryContext";
import { like, eq, and, sql } from "drizzle-orm";
import { union } from "drizzle-orm/sqlite-core"
-import { auth as authOptions } from "@/server/auth";
// @todo: (future) pagination not yet needed
-export async function searchMemoriesAndSpaces(userId: string, query: string) {
- const searchMemoriesQuery = db.select({
- type: sql<string>`'memory'`,
- space: sql`NULL`,
- memory: storedContent as any
- }).from(storedContent).where(and(
- eq(storedContent.user, userId),
- like(storedContent.title, `%${query}%`)
- ))
-
- const searchSpacesQuery = db.select({
- type: sql<string>`'space'`,
- space: space as any,
- memory: sql`NULL`,
- }).from(space).where(
- and(
- eq(space.user, userId),
- like(space.name, `%${query}%`)
- )
- )
-
- return await union(searchMemoriesQuery, searchSpacesQuery)
+export async function searchMemoriesAndSpaces(query: string): Promise<SearchResult[]> {
+
+ const user = await getUser()
+
+ if (!user) {
+ return []
+ }
+
+ try {
+ const searchMemoriesQuery = db.select({
+ type: sql<string>`'memory'`,
+ space: sql`NULL`,
+ memory: storedContent as any
+ }).from(storedContent).where(and(
+ eq(storedContent.user, user.id),
+ like(storedContent.title, `%${query}%`)
+ )).all()
+
+ const searchSpacesQuery = db.select({
+ type: sql<string>`'space'`,
+ space: space as any,
+ memory: sql`NULL`,
+ }).from(space).where(
+ and(
+ eq(space.user, user.id),
+ like(space.name, `%${query}%`)
+ )
+ ).all()
+
+ const data = await Promise.all([searchSpacesQuery, searchMemoriesQuery])
+
+ return data.reduce((acc, i) => [...acc, ...i]) as SearchResult[]
+ } catch {
+ return []
+ }
+
}
async function getUser() {
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx
index 1b3c718e..213667c8 100644
--- a/apps/web/src/components/Sidebar/MemoriesBar.tsx
+++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx
@@ -10,6 +10,7 @@ import { Input, InputWithIcon } from "../ui/input";
import {
ArrowUpRight,
Edit3,
+ Loader,
MoreHorizontal,
Plus,
Search,
@@ -25,7 +26,7 @@ import {
} from "../ui/dropdown-menu";
import { useEffect, useMemo, useRef, useState } from "react";
import { Variant, useAnimate, motion } from "framer-motion";
-import { useMemory } from "@/contexts/MemoryContext";
+import { SearchResult, useMemory } from "@/contexts/MemoryContext";
import { SpaceIcon } from "@/assets/Memories";
import {
Dialog,
@@ -44,10 +45,12 @@ 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 { searchMemoriesAndSpaces } from "@/actions/db";
export function MemoriesBar() {
const [parent, enableAnimations] = useAutoAnimate();
- const { spaces, deleteSpace, freeMemories } = useMemory();
+ const { spaces, deleteSpace, freeMemories, search } = useMemory();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [addMemoryState, setAddMemoryState] = useState<
@@ -55,6 +58,11 @@ export function MemoriesBar() {
>(null);
const [expandedSpace, setExpandedSpace] = useState<number | null>(null);
+ const [searchQuery, setSearcyQuery] = useState("");
+ const [searchLoading, setSearchLoading] = useState(false)
+ const query = useDebounce(searchQuery, 1000)
+
+ const [searchResults, setSearchResults] = useState<SearchResult[]>([])
if (expandedSpace) {
return (
@@ -65,14 +73,31 @@ export function MemoriesBar() {
);
}
+ useEffect(() => {
+ const q = query.trim()
+ if (q.length < 1) {
+ setSearchResults([])
+ return
+ }
+
+ setSearchLoading(true);
+
+ (async () => {
+ setSearchResults(await search(q))
+ setSearchLoading(false)
+ })();
+ }, [query])
+
return (
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
<div className="w-full px-8">
<h1 className="w-full text-2xl">Your Memories</h1>
<InputWithIcon
placeholder="Search"
- icon={<Search className="text-rgray-11 h-5 w-5 opacity-50" />}
+ icon={searchLoading ? <Loader className="text-rgray-11 h-5 w-5 opacity-50 animate-spin" /> : <Search className="text-rgray-11 h-5 w-5 opacity-50" />}
className="bg-rgray-4 mt-2 w-full"
+ value={searchQuery}
+ onChange={(e) => setSearcyQuery(e.target.value)}
/>
</div>
<div className="mt-2 flex w-full px-8">
@@ -123,17 +148,34 @@ export function MemoriesBar() {
ref={parent}
className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5"
>
- {spaces.map((space) => (
- <SpaceItem
- onDelete={() => {}}
- key={space.id}
- //onClick={() => setExpandedSpace(space.id)}
- {...space}
- />
- ))}
- {freeMemories.map(m => (
- <MemoryItem {...m} key={m.id} />
- ))}
+ {searchQuery.trim().length > 0 ? (
+ <>
+ {searchResults.map(({ type, space, memory }, i) => (
+ <>
+ {type === "memory" && (
+ <MemoryItem {...memory!} key={i} />
+ )}
+ {type === "space" && (
+ <SpaceItem {...space!} key={i} onDelete={() => {}} />
+ )}
+ </>
+ ))}
+ </>
+ ): (
+ <>
+ {spaces.map((space) => (
+ <SpaceItem
+ onDelete={() => {}}
+ key={space.id}
+ //onClick={() => setExpandedSpace(space.id)}
+ {...space}
+ />
+ ))}
+ {freeMemories.map(m => (
+ <MemoryItem {...m} key={m.id} />
+ ))}
+ </>
+ )}
</div>
</div>
);
diff --git a/apps/web/src/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx
index a6050332..b805d41e 100644
--- a/apps/web/src/contexts/MemoryContext.tsx
+++ b/apps/web/src/contexts/MemoryContext.tsx
@@ -62,7 +62,7 @@ export const MemoryProvider: React.FC<
if (!user.id) {
throw new Error('user id is not define')
}
- const data = await searchMemoriesAndSpaces(user.id, query)
+ const data = await searchMemoriesAndSpaces(query)
return data as SearchResult[]
}
diff --git a/apps/web/src/hooks/useDebounce.ts b/apps/web/src/hooks/useDebounce.ts
new file mode 100644
index 00000000..4a47de72
--- /dev/null
+++ b/apps/web/src/hooks/useDebounce.ts
@@ -0,0 +1,27 @@
+import { useEffect, useState } from 'react';
+
+/**
+ * Use this hook when you need to debounce a value.
+ * @param value
+ * @param delay in milliseconds
+ */
+export const useDebounce = <T>(value: T, delay: number) => {
+ // State and setters for debounced value
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
+
+ useEffect(() => {
+ // Update debounced value after delay
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ // Cancel the timeout if value changes (also on delay change or unmount)
+ // This is how we prevent debounced value from updating if the value is changed
+ // within the delay period. Timeout gets cleared and restarted.
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+};