From 52d89fd1a6036c00bdc79bf1e0ec0df87760890f Mon Sep 17 00:00:00 2001 From: Dhravya Shah Date: Tue, 18 Feb 2025 15:28:01 -0700 Subject: [PATCH] better space selector --- apps/backend/src/routes/actions.ts | 18 --- .../app/components/memories/MemoriesPage.tsx | 136 ++++++++++++------ .../app/components/memories/SharedCard.tsx | 29 ++-- apps/web/app/routes/signin.tsx | 8 +- 4 files changed, 115 insertions(+), 76 deletions(-) diff --git a/apps/backend/src/routes/actions.ts b/apps/backend/src/routes/actions.ts index deba952b..c0801ada 100644 --- a/apps/backend/src/routes/actions.ts +++ b/apps/backend/src/routes/actions.ts @@ -50,9 +50,6 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() }) ), async (c) => { - const startTime = performance.now(); - console.log("[chat] Starting request"); - const user = c.get("user"); if (!user) { return c.json({ error: "Unauthorized" }, 401); @@ -60,7 +57,6 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() const { messages, threadId } = await c.req.valid("json"); - console.log("[chat] Converting messages"); const unfilteredCoreMessages = convertToCoreMessages( (messages as Message[]) .filter((m) => m.content.length > 0) @@ -83,7 +79,6 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() (message) => message.content.length > 0 ); - console.log("[chat] Setting up DB and logger"); const db = database(c.env.HYPERDRIVE.connectionString); const { initLogger, wrapAISDKModel } = await import("braintrust"); @@ -106,8 +101,6 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() return c.json({ error: "Empty query" }, 400); } - console.log("[chat] Generating embeddings and creating thread"); - const embedStart = performance.now(); // Run embedding generation and thread creation in parallel const [{ data: embedding }, thread] = await Promise.all([ c.env.AI.run("@cf/baai/bge-base-en-v1.5", { text: queryText }), @@ -123,7 +116,6 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() .returning() : null, ]); - console.log(`[chat] Embedding generation took ${performance.now() - embedStart}ms`); const threadUuid = threadId || thread?.[0].uuid; @@ -131,8 +123,6 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() return c.json({ error: "Failed to generate embedding" }, 500); } - console.log("[chat] Performing semantic search"); - const searchStart = performance.now(); // Perform semantic search const similarity = sql`1 - (${cosineDistance(chunk.embeddings, embedding[0])})`; @@ -154,7 +144,6 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() .where(and(eq(documents.userId, user.id), sql`${similarity} > 0.4`)) .orderBy(desc(similarity)) .limit(5); - console.log(`[chat] Semantic search took ${performance.now() - searchStart}ms`); const cleanDocumentsForContext = finalResults.map((d) => ({ title: d.title, @@ -180,8 +169,6 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() } try { - console.log("[chat] Starting stream generation"); - const streamStart = performance.now(); const data = new StreamData(); // De-duplicate chunks by URL to avoid showing duplicate content const uniqueResults = finalResults.reduce((acc, current) => { @@ -237,8 +224,6 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() ], async onFinish(completion) { try { - console.log("[chat] Stream finished, updating thread"); - const updateStart = performance.now(); if (lastUserMessage) { lastUserMessage.content = typeof lastUserMessage.content === "string" @@ -272,15 +257,12 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() .set({ messages: newMessages }) .where(eq(chatThreads.uuid, threadUuid)); } - console.log(`[chat] Thread update took ${performance.now() - updateStart}ms`); } catch (error) { console.error("Failed to update thread:", error); } }, }); - console.log(`[chat] Stream generation took ${performance.now() - streamStart}ms`); - console.log(`[chat] Total request time: ${performance.now() - startTime}ms`); return result.toDataStreamResponse({ headers: { "Supermemory-Thread-Uuid": threadUuid ?? "", diff --git a/apps/web/app/components/memories/MemoriesPage.tsx b/apps/web/app/components/memories/MemoriesPage.tsx index 8b0dbdc9..da12427e 100644 --- a/apps/web/app/components/memories/MemoriesPage.tsx +++ b/apps/web/app/components/memories/MemoriesPage.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from "react"; +import { createContext, memo, useCallback, useContext, useMemo, useState } from "react"; import { useParams } from "react-router-dom"; import { Button } from "../ui/button"; @@ -22,6 +22,14 @@ interface MemoriesPageProps { isSpace?: boolean; } +interface SelectionContextType { + isSelectionMode: boolean; + selectedItems: Set; + toggleSelection: (uuid: string) => void; +} + +const SelectionContext = createContext(null); + function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPageProps) { const isHydrated = useHydrated(); const { spaceId } = useParams(); @@ -106,21 +114,23 @@ function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPagePr // Memoize space items transformation const spaceItems = useMemo(() => { if (spaceId) return []; - return spaces.map((space) => ({ - id: space.uuid, - type: "space", - content: space.name, - createdAt: new Date(space.createdAt), - description: null, - ogImage: null, - title: space.name, - url: `/space/${space.uuid}`, - uuid: space.uuid, - updatedAt: null, - raw: null, - userId: space.ownerId, - isSuccessfullyProcessed: true, - })); + return spaces + .filter((space) => space.uuid !== "") + .map((space) => ({ + id: space.uuid, + type: "space", + content: space.name, + createdAt: new Date(space.createdAt), + description: null, + ogImage: null, + title: space.name, + url: `/space/${space.uuid}`, + uuid: space.uuid, + updatedAt: null, + raw: null, + userId: space.ownerId, + isSuccessfullyProcessed: true, + })); }, [spaces, spaceId]); // Memoize filtered memories @@ -180,8 +190,29 @@ function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPagePr }; }, [addButtonItem, spaceItems, filteredMemories, selectedVariant, spaceId, isSpace]); - const renderCard = useCallback( - ({ data, index }: { data: Memory; index: number }) => { + const selectionContextValue = useMemo( + () => ({ + isSelectionMode, + selectedItems, + toggleSelection: handleToggleSelection, + }), + [isSelectionMode, selectedItems, handleToggleSelection], + ); + + const MemoizedSharedCard = memo( + ({ + data, + index, + showAddButtons, + isSpace, + }: { + data: Memory; + index: number; + showAddButtons: boolean; + isSpace: boolean; + }) => { + const selection = useContext(SelectionContext); + if (index === 0 && showAddButtons) { return ; } @@ -191,13 +222,30 @@ function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPagePr return ( handleToggleSelection(data.uuid)} + isSelectionMode={selection?.isSelectionMode ?? false} + isSelected={selection?.selectedItems.has(data.uuid) ?? false} + onToggleSelect={() => selection?.toggleSelection(data.uuid)} /> ); }, - [showAddButtons, isSelectionMode, selectedItems, handleToggleSelection], + (prevProps, nextProps) => { + // Custom comparison function for memo + return prevProps.data.uuid === nextProps.data.uuid; + }, + ); + + MemoizedSharedCard.displayName = "MemoizedSharedCard"; + + const renderCard = useCallback( + ({ data, index }: { data: Memory; index: number }) => ( + + ), + [showAddButtons, isSpace], ); const handleVariantClick = useCallback((variant: Variant) => { @@ -345,28 +393,30 @@ function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPagePr if (!isHydrated) return null; return ( -
-
- {MobileVariantButton} - {MobileVariantMenu} - {DesktopVariantMenu} + +
+
+ {MobileVariantButton} + {MobileVariantMenu} + {DesktopVariantMenu} +
+ + {SelectionControls} + + + + {isLoading &&
Loading more...
}
- - {SelectionControls} - - - - {isLoading &&
Loading more...
} -
+ ); } diff --git a/apps/web/app/components/memories/SharedCard.tsx b/apps/web/app/components/memories/SharedCard.tsx index cebdc796..69283324 100644 --- a/apps/web/app/components/memories/SharedCard.tsx +++ b/apps/web/app/components/memories/SharedCard.tsx @@ -3,7 +3,7 @@ import { memo, useCallback, useEffect, useMemo, useState } from "react"; import { useInView } from "react-intersection-observer"; import { TweetSkeleton } from "react-tweet"; -import { useNavigate } from "@remix-run/react"; +import { useNavigate, useParams } from "@remix-run/react"; import { NotionIcon } from "../icons/IntegrationIcons"; import { CustomTwitterComp } from "../twitter/render-tweet"; @@ -111,6 +111,7 @@ const renderContent = { page: ({ data }: { data: Memory }) => ( }) => { return (
@@ -325,7 +326,7 @@ const renderContent = { // TODO: This can be improved return (
@@ -411,11 +412,13 @@ const WebsiteCard = memo( title, description, image, + id, }: { url: string; title?: string | null; description?: string | null; image?: string | null; + id: string; }) => { // Memoize domain extraction to avoid recalculation const domain = useMemo(() => { @@ -500,9 +503,7 @@ const WebsiteCard = memo(

{displayTitle}

{displayDescription}

{ toast.success("Memory deleted successfully"); queryClient.invalidateQueries({ queryKey: ["memories"] }); + queryClient.invalidateQueries({ queryKey: ["spaces"] }); }, }); @@ -742,11 +744,6 @@ export default function SharedCard({ onToggleSelect(); return; } - - // Normal navigation behavior - if (data.url) { - window.location.href = data.url; - } }; return ( @@ -809,6 +806,10 @@ export const SpaceSelector = function SpaceSelector({ onSelect: (spaceId: string) => void; }) { const [search, setSearch] = useState(""); + const { spaceId } = useParams(); + + console.log(spaceId); + const { data: spacesData, isLoading, @@ -821,11 +822,13 @@ export const SpaceSelector = function SpaceSelector({ const filteredSpaces = useMemo(() => { if (!spacesData?.spaces) return []; - return spacesData.spaces.filter((space) => - space.name.toLowerCase().includes(search.toLowerCase()), + return spacesData.spaces.filter( + (space) => + space.name.toLowerCase().includes(search.toLowerCase()) && space.uuid !== (spaceId ? spaceId.split("---")[0] : ""), ); }, [spacesData?.spaces, search]); + if (isLoading) { return ( diff --git a/apps/web/app/routes/signin.tsx b/apps/web/app/routes/signin.tsx index 93fa792f..0ed0a848 100644 --- a/apps/web/app/routes/signin.tsx +++ b/apps/web/app/routes/signin.tsx @@ -1,8 +1,12 @@ import { LoaderFunctionArgs, redirect } from "@remix-run/cloudflare"; - import { getSignInUrl } from "@supermemory/authkit-remix-cloudflare"; +import { getSessionFromRequest } from "@supermemory/authkit-remix-cloudflare/src/session"; -export async function loader({ context }: LoaderFunctionArgs) { +export async function loader({ request, context }: LoaderFunctionArgs) { + const session = await getSessionFromRequest(request, context); + if (session) { + return redirect("/"); + } const signinUrl = await getSignInUrl(context); return redirect(signinUrl); }