From 5b9c1bf8d8390370126e17d47433a82c1388d63a Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommmu Date: Tue, 10 Mar 2026 20:51:58 +0530 Subject: [PATCH] feat: empty state action for new spaces --- apps/mcp/src/client.ts | 6 +- apps/web/app/(app)/page.tsx | 50 ++++- apps/web/app/upgrade-mcp/page.tsx | 8 +- apps/web/components/chat/index.tsx | 176 +++++++++--------- apps/web/components/integrations-view.tsx | 28 ++- apps/web/components/memories-grid.tsx | 74 +++++--- .../components/memory-graph/memory-graph.tsx | 4 +- apps/web/components/nova/nova-empty-state.tsx | 146 +++++++++++++++ apps/web/globals.css | 3 +- apps/web/lib/search-params.ts | 3 + packages/lib/constants.ts | 3 +- .../src/components/memory-graph.tsx | 7 +- packages/tools/src/ai-sdk.ts | 25 +-- packages/tools/src/openai/tools.ts | 4 +- packages/tools/src/tools-shared.ts | 6 +- packages/tools/test/chatapp/app/globals.css | 18 +- packages/ui/memory-graph/memory-graph.tsx | 7 +- 17 files changed, 404 insertions(+), 164 deletions(-) create mode 100644 apps/web/components/nova/nova-empty-state.tsx diff --git a/apps/mcp/src/client.ts b/apps/mcp/src/client.ts index df185f3f..2924f6fb 100644 --- a/apps/mcp/src/client.ts +++ b/apps/mcp/src/client.ts @@ -221,7 +221,11 @@ export class SupermemoryClient { } // Search memories using SDK - async search(query: string, limit = 10, threshold?: number): Promise { + async search( + query: string, + limit = 10, + threshold?: number, + ): Promise { try { const result = await this.client.search.memories({ q: query, diff --git a/apps/web/app/(app)/page.tsx b/apps/web/app/(app)/page.tsx index b9366efb..9fde6c14 100644 --- a/apps/web/app/(app)/page.tsx +++ b/apps/web/app/(app)/page.tsx @@ -19,6 +19,8 @@ import { AnimatePresence } from "motion/react" import { useIsMobile } from "@hooks/use-mobile" import { useAuth } from "@lib/auth-context" import { useProject } from "@/stores" +import { useContainerTags } from "@/hooks/use-container-tags" +import { DEFAULT_PROJECT_ID } from "@lib/constants" import { useQuickNoteDraftReset, useQuickNoteDraft, @@ -38,6 +40,7 @@ import { docParam, fullscreenParam, chatParam, + integrationParam, } from "@/lib/search-params" type DocumentsResponse = z.infer @@ -63,7 +66,21 @@ function ViewErrorFallback() { export default function NewPage() { const isMobile = useIsMobile() const { user, session } = useAuth() - const { selectedProject, isNovaSpaces, novaContainerTags } = useProject() + const { selectedProject, isNovaSpaces, novaContainerTags, selectedProjects } = + useProject() + const selectedProjectTag = selectedProjects[0] + const isNovaContext = + isNovaSpaces || + (selectedProjectTag !== undefined && + novaContainerTags.includes(selectedProjectTag)) + const { allProjects } = useContainerTags() + const emptyStateSpaceName = + !isNovaSpaces && selectedProjectTag + ? selectedProjectTag === DEFAULT_PROJECT_ID + ? "My Space" + : (allProjects.find((p) => p.containerTag === selectedProjectTag) + ?.name ?? selectedProjectTag) + : undefined const { viewMode, setViewMode } = useViewMode() const queryClient = useQueryClient() @@ -93,6 +110,7 @@ export default function NewPage() { fullscreenParam, ) const [isChatOpen, setIsChatOpen] = useQueryState("chat", chatParam) + const [, setIntegration] = useQueryState("integration", integrationParam) // Ephemeral local state (not worth URL-encoding) const [fullscreenInitialContent, setFullscreenInitialContent] = useState("") @@ -295,6 +313,26 @@ export default function NewPage() { [setSearchPrefill, setIsSearchOpen], ) + const handleOpenIntegrations = useCallback( + (integration?: "import" | "chrome" | "connections") => { + setViewMode("integrations") + if (integration) { + setIntegration(integration) + } else { + setIntegration(null) + } + }, + [setViewMode, setIntegration], + ) + + const handleAddMemory = useCallback( + (tab: "note" | "link") => { + analytics.addDocumentModalOpened() + setAddDoc(tab) + }, + [setAddDoc], + ) + const chatOpen = isChatOpen !== null ? isChatOpen : !isMobile const isGraphMode = viewMode === "graph" && !isMobile @@ -360,6 +398,16 @@ export default function NewPage() { onShowRelated: handleHighlightsShowRelated, isLoading: isLoadingHighlights, }} + emptyStateProps={ + isNovaContext + ? { + onAddMemory: handleAddMemory, + onOpenIntegrations: handleOpenIntegrations, + isAllSpaces: isNovaSpaces, + spaceName: emptyStateSpaceName, + } + : undefined + } /> )} diff --git a/apps/web/app/upgrade-mcp/page.tsx b/apps/web/app/upgrade-mcp/page.tsx index d9f4ab56..0b4fb05b 100644 --- a/apps/web/app/upgrade-mcp/page.tsx +++ b/apps/web/app/upgrade-mcp/page.tsx @@ -185,7 +185,9 @@ export default function MigrateMCPPage() { className="bg-white/5 border-white/10 text-white placeholder:text-slate-500 focus:border-blue-500/50 focus:ring-blue-500/20 transition-all duration-200 pl-4 pr-4 py-3 rounded-xl" disabled={migrateMutation.isPending} id="mcpUrl" - onChange={(e: React.ChangeEvent) => setMcpUrl(e.target.value)} + onChange={(e: React.ChangeEvent) => + setMcpUrl(e.target.value) + } placeholder="https://mcp.supermemory.ai/userId/sse" type="url" value={mcpUrl} @@ -205,7 +207,9 @@ export default function MigrateMCPPage() { className="bg-white/5 border-white/10 text-white placeholder:text-slate-500 focus:border-blue-500/50 focus:ring-blue-500/20 transition-all duration-200 pl-4 pr-4 py-3 rounded-xl" disabled={migrateMutation.isPending} id="projectId" - onChange={(e: React.ChangeEvent) => setProjectId(e.target.value)} + onChange={(e: React.ChangeEvent) => + setProjectId(e.target.value) + } placeholder="Project ID (default: 'default')" type="text" value={projectId} diff --git a/apps/web/components/chat/index.tsx b/apps/web/components/chat/index.tsx index ea1ee4c0..bcf17ef7 100644 --- a/apps/web/components/chat/index.tsx +++ b/apps/web/components/chat/index.tsx @@ -675,104 +675,104 @@ export function ChatSidebar({ - - - Chat History - - Project: {selectedProject} - - - - {isLoadingThreads ? ( -
- -
- ) : threads.length === 0 ? ( -
- No conversations yet -
- ) : ( -
- {threads.map((thread) => { - const isActive = thread.id === currentChatId - return ( - - -
- ) : ( +
+ {formatRelativeTime(thread.updatedAt)} +
+ + {confirmingDeleteId === thread.id ? ( +
+ - )} - - ) - })} -
- )} -
- -
- + + ) : ( + + )} + + ) + })} + + )} + + + + - {facetsData?.facets.map((facet: DocumentFacet) => ( + {!isEmpty && ( +
- ))} -
+ {facetsData?.facets.map((facet: DocumentFacet) => ( + + ))} + + )} {error ? (
@@ -327,7 +344,14 @@ export function MemoriesGrid({
- ) : documents.length === 0 && !isPending ? ( + ) : showNovaEmptyState ? ( + + ) : isEmpty ? (
No memories found diff --git a/apps/web/components/memory-graph/memory-graph.tsx b/apps/web/components/memory-graph/memory-graph.tsx index f0ac58bb..27825787 100644 --- a/apps/web/components/memory-graph/memory-graph.tsx +++ b/apps/web/components/memory-graph/memory-graph.tsx @@ -445,9 +445,7 @@ export const MemoryGraph = ({ variant={variant} /> - {!isLoading && - !nodes.some((n) => n.type === "document") && - children} + {!isLoading && !nodes.some((n) => n.type === "document") && children}
void + onOpenIntegrations: ( + integration?: "import" | "chrome" | "connections", + ) => void + isAllSpaces: boolean + spaceName?: string +} + +const cardClass = cn( + "bg-[#14161A] rounded-xl p-4 border border-[rgba(82,89,102,0.2)]", + "hover:border-[#3374FF]/50 hover:bg-[#1B1F24]", + "transition-colors cursor-pointer text-left flex flex-col gap-2", +) + +export function NovaEmptyState({ + onAddMemory, + onOpenIntegrations, + isAllSpaces, + spaceName, +}: NovaEmptyStateProps) { + const handleInstallChrome = () => { + window.open(CHROME_EXTENSION_URL, "_blank", "noopener,noreferrer") + } + + const title = isAllSpaces + ? "Help Nova get to know you" + : "This space is empty" + const subtitle = isAllSpaces + ? "Add your first memory to get started." + : spaceName + ? `Add memories to ${spaceName} to get started.` + : "Add memories to this space to get started." + + return ( +
+
+ +

+ {title} +

+

+ {subtitle} +

+ +
+ + + + + +
+ + +
+
+ ) +} diff --git a/apps/web/globals.css b/apps/web/globals.css index 647a85de..82dccd8b 100644 --- a/apps/web/globals.css +++ b/apps/web/globals.css @@ -109,7 +109,8 @@ } /* Override prose paragraph margins for text editor */ -.text-editor-prose.prose :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { +.text-editor-prose.prose +:where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 0; margin-bottom: 0; } diff --git a/apps/web/lib/search-params.ts b/apps/web/lib/search-params.ts index 93594648..67d74ccb 100644 --- a/apps/web/lib/search-params.ts +++ b/apps/web/lib/search-params.ts @@ -24,6 +24,9 @@ export const feedbackParam = parseAsBoolean.withDefault(false) // View & filter states const viewLiterals = ["graph", "list", "integrations"] as const +const integrationLiterals = ["import", "chrome", "connections"] as const +export type IntegrationParamValue = (typeof integrationLiterals)[number] +export const integrationParam = parseAsStringLiteral(integrationLiterals) export type ViewParamValue = (typeof viewLiterals)[number] export const viewParam = parseAsStringLiteral(viewLiterals).withDefault("list") export const categoriesParam = parseAsArrayOf(parseAsString, ",").withDefault( diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index 6762ec1b..43fd56a3 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -5,7 +5,8 @@ const SEARCH_MEMORY_SHORTCUT_URL = const ADD_MEMORY_SHORTCUT_URL = "https://www.icloud.com/shortcuts/0fd3e855be444845b457f94c78c2c8d9" const RAYCAST_EXTENSION_URL = "https://www.raycast.com/supermemory/supermemory" -const CHROME_EXTENSION_URL = "https://chromewebstore.google.com/detail/supermemory/afpgkkipfdpeaflnpoaffkcankadgjfc" +const CHROME_EXTENSION_URL = + "https://chromewebstore.google.com/detail/supermemory/afpgkkipfdpeaflnpoaffkcankadgjfc" export { BIG_DIMENSIONS_NEW, diff --git a/packages/memory-graph/src/components/memory-graph.tsx b/packages/memory-graph/src/components/memory-graph.tsx index c847ecca..a6c16a2e 100644 --- a/packages/memory-graph/src/components/memory-graph.tsx +++ b/packages/memory-graph/src/components/memory-graph.tsx @@ -740,10 +740,9 @@ export const MemoryGraph = ({ )} {/* Show welcome screen when no memories exist */} - {!isLoading && - (!data || !nodes.some((n) => n.type === "document")) && ( - <>{children} - )} + {!isLoading && (!data || !nodes.some((n) => n.type === "document")) && ( + <>{children} + )} {/* Graph container */}
diff --git a/packages/tools/src/ai-sdk.ts b/packages/tools/src/ai-sdk.ts index 6cd2d612..77f3ad03 100644 --- a/packages/tools/src/ai-sdk.ts +++ b/packages/tools/src/ai-sdk.ts @@ -135,14 +135,8 @@ export const getProfileTool = ( inputSchema: z.object({ containerTag: strict ? z.string().describe(PARAMETER_DESCRIPTIONS.containerTag) - : z - .string() - .optional() - .describe(PARAMETER_DESCRIPTIONS.containerTag), - query: z - .string() - .optional() - .describe(PARAMETER_DESCRIPTIONS.query), + : z.string().optional().describe(PARAMETER_DESCRIPTIONS.containerTag), + query: z.string().optional().describe(PARAMETER_DESCRIPTIONS.query), }), execute: async ({ containerTag, query }) => { try { @@ -197,14 +191,8 @@ export const documentListTool = ( .optional() .default(DEFAULT_VALUES.limit) .describe(PARAMETER_DESCRIPTIONS.limit), - offset: z - .number() - .optional() - .describe(PARAMETER_DESCRIPTIONS.offset), - status: z - .string() - .optional() - .describe(PARAMETER_DESCRIPTIONS.status), + offset: z.number().optional().describe(PARAMETER_DESCRIPTIONS.offset), + status: z.string().optional().describe(PARAMETER_DESCRIPTIONS.status), }), execute: async ({ containerTag, limit, offset, status }) => { try { @@ -329,10 +317,7 @@ export const memoryForgetTool = ( .string() .optional() .describe(PARAMETER_DESCRIPTIONS.containerTag), - memoryId: z - .string() - .optional() - .describe(PARAMETER_DESCRIPTIONS.memoryId), + memoryId: z.string().optional().describe(PARAMETER_DESCRIPTIONS.memoryId), memoryContent: z .string() .optional() diff --git a/packages/tools/src/openai/tools.ts b/packages/tools/src/openai/tools.ts index aa1f3114..da7d6642 100644 --- a/packages/tools/src/openai/tools.ts +++ b/packages/tools/src/openai/tools.ts @@ -37,7 +37,9 @@ export interface ProfileResult { export interface DocumentListResult { success: boolean documents?: Awaited>["documents"] - pagination?: Awaited>["pagination"] + pagination?: Awaited< + ReturnType + >["pagination"] error?: string } diff --git a/packages/tools/src/tools-shared.ts b/packages/tools/src/tools-shared.ts index 1ea20db5..ac5dfcd3 100644 --- a/packages/tools/src/tools-shared.ts +++ b/packages/tools/src/tools-shared.ts @@ -31,13 +31,15 @@ export const PARAMETER_DESCRIPTIONS = { containerTag: "Tag to filter/scope the operation (e.g., user ID, project ID)", query: "Optional search query to include relevant search results", offset: "Number of items to skip for pagination (default: 0)", - status: "Filter documents by processing status (e.g., 'completed', 'processing', 'failed')", + status: + "Filter documents by processing status (e.g., 'completed', 'processing', 'failed')", documentId: "The unique identifier of the document to operate on", content: "The content to add - can be text, URL, or other supported formats", title: "Optional title for the document", description: "Optional description for the document", memoryId: "The unique identifier of the memory entry", - memoryContent: "Exact content match of the memory entry to operate on (alternative to ID)", + memoryContent: + "Exact content match of the memory entry to operate on (alternative to ID)", reason: "Optional reason for forgetting this memory", } as const diff --git a/packages/tools/test/chatapp/app/globals.css b/packages/tools/test/chatapp/app/globals.css index a2dc41ec..78bbd724 100644 --- a/packages/tools/test/chatapp/app/globals.css +++ b/packages/tools/test/chatapp/app/globals.css @@ -1,8 +1,8 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; + --background: #ffffff; + --foreground: #171717; } @theme inline { @@ -13,14 +13,14 @@ } @media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } + :root { + --background: #0a0a0a; + --foreground: #ededed; + } } body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; } diff --git a/packages/ui/memory-graph/memory-graph.tsx b/packages/ui/memory-graph/memory-graph.tsx index 97b0a604..855350fe 100644 --- a/packages/ui/memory-graph/memory-graph.tsx +++ b/packages/ui/memory-graph/memory-graph.tsx @@ -409,10 +409,9 @@ export const MemoryGraph = ({ {/* Show welcome screen when no memories exist */} - {!isLoading && - (!data || !nodes.some((n) => n.type === "document")) && ( - <>{children} - )} + {!isLoading && (!data || !nodes.some((n) => n.type === "document")) && ( + <>{children} + )} {/* Graph container */}