feat: empty state action for new spaces

This commit is contained in:
Mahesh Sanikommmu 2026-03-10 20:51:58 +05:30
parent a0514e7a44
commit 5b9c1bf8d8
17 changed files with 404 additions and 164 deletions

View file

@ -221,7 +221,11 @@ export class SupermemoryClient {
}
// Search memories using SDK
async search(query: string, limit = 10, threshold?: number): Promise<SearchResult> {
async search(
query: string,
limit = 10,
threshold?: number,
): Promise<SearchResult> {
try {
const result = await this.client.search.memories({
q: query,

View file

@ -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<typeof DocumentsWithMemoriesResponseSchema>
@ -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
}
/>
</div>
)}

View file

@ -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<HTMLInputElement>) => setMcpUrl(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
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<HTMLInputElement>) => setProjectId(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setProjectId(e.target.value)
}
placeholder="Project ID (default: 'default')"
type="text"
value={projectId}

View file

@ -675,104 +675,104 @@ export function ChatSidebar({
<HistoryIcon className="size-4 text-[#737373]" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-lg bg-[#0A0E14] border-[#17181AB2] text-white">
<DialogHeader className="pb-4 border-b border-[#17181AB2]">
<DialogTitle>Chat History</DialogTitle>
<DialogDescription className="text-[#737373]">
Project: {selectedProject}
</DialogDescription>
</DialogHeader>
<ScrollArea className="max-h-96">
{isLoadingThreads ? (
<div className="flex items-center justify-center py-8">
<SuperLoader label="Loading..." />
</div>
) : threads.length === 0 ? (
<div className="text-sm text-[#737373] text-center py-8">
No conversations yet
</div>
) : (
<div className="flex flex-col gap-1">
{threads.map((thread) => {
const isActive = thread.id === currentChatId
return (
<button
key={thread.id}
type="button"
onClick={() => loadThread(thread.id)}
className={cn(
"flex items-center justify-between rounded-md px-3 py-2 w-full text-left transition-colors",
isActive
? "bg-[#267BF1]/10"
: "hover:bg-[#17181A]",
)}
>
<div className="min-w-0 flex-1">
<div className="text-sm font-medium truncate">
{thread.title || "Untitled Chat"}
</div>
<div className="text-xs text-[#737373]">
{formatRelativeTime(thread.updatedAt)}
</div>
<DialogContent className="sm:max-w-lg bg-[#0A0E14] border-[#17181AB2] text-white">
<DialogHeader className="pb-4 border-b border-[#17181AB2]">
<DialogTitle>Chat History</DialogTitle>
<DialogDescription className="text-[#737373]">
Project: {selectedProject}
</DialogDescription>
</DialogHeader>
<ScrollArea className="max-h-96">
{isLoadingThreads ? (
<div className="flex items-center justify-center py-8">
<SuperLoader label="Loading..." />
</div>
) : threads.length === 0 ? (
<div className="text-sm text-[#737373] text-center py-8">
No conversations yet
</div>
) : (
<div className="flex flex-col gap-1">
{threads.map((thread) => {
const isActive = thread.id === currentChatId
return (
<button
key={thread.id}
type="button"
onClick={() => loadThread(thread.id)}
className={cn(
"flex items-center justify-between rounded-md px-3 py-2 w-full text-left transition-colors",
isActive
? "bg-[#267BF1]/10"
: "hover:bg-[#17181A]",
)}
>
<div className="min-w-0 flex-1">
<div className="text-sm font-medium truncate">
{thread.title || "Untitled Chat"}
</div>
{confirmingDeleteId === thread.id ? (
<div className="flex items-center gap-1 ml-2">
<Button
type="button"
size="icon"
onClick={(e) => {
e.stopPropagation()
deleteThread(thread.id)
}}
className="bg-red-500 text-white hover:bg-red-600 h-7 w-7"
>
<Check className="size-3" />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation()
setConfirmingDeleteId(null)
}}
className="h-7 w-7"
>
<XIcon className="size-3 text-[#737373]" />
</Button>
</div>
) : (
<div className="text-xs text-[#737373]">
{formatRelativeTime(thread.updatedAt)}
</div>
</div>
{confirmingDeleteId === thread.id ? (
<div className="flex items-center gap-1 ml-2">
<Button
type="button"
size="icon"
onClick={(e) => {
e.stopPropagation()
deleteThread(thread.id)
}}
className="bg-red-500 text-white hover:bg-red-600 h-7 w-7"
>
<Check className="size-3" />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation()
setConfirmingDeleteId(thread.id)
setConfirmingDeleteId(null)
}}
className="h-7 w-7 ml-2"
className="h-7 w-7"
>
<Trash2 className="size-3 text-[#737373]" />
<XIcon className="size-3 text-[#737373]" />
</Button>
)}
</button>
)
})}
</div>
)}
</ScrollArea>
<Button
variant="outline"
className="w-full border-dashed border-[#73737333] bg-transparent hover:bg-[#17181A]"
onClick={() => {
handleNewChat()
setIsHistoryOpen(false)
}}
>
<Plus className="size-4 mr-1" /> New Conversation
</Button>
</DialogContent>
</Dialog>
</div>
) : (
<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation()
setConfirmingDeleteId(thread.id)
}}
className="h-7 w-7 ml-2"
>
<Trash2 className="size-3 text-[#737373]" />
</Button>
)}
</button>
)
})}
</div>
)}
</ScrollArea>
<Button
variant="outline"
className="w-full border-dashed border-[#73737333] bg-transparent hover:bg-[#17181A]"
onClick={() => {
handleNewChat()
setIsHistoryOpen(false)
}}
>
<Plus className="size-4 mr-1" /> New Conversation
</Button>
</DialogContent>
</Dialog>
<Button
variant="headers"
className="rounded-full text-base gap-3 h-10! border-[#73737333] bg-[#0D121A] cursor-pointer"

View file

@ -1,6 +1,7 @@
"use client"
import { useState } from "react"
import { useState, useEffect } from "react"
import { useQueryState } from "nuqs"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { Button } from "@ui/components/button"
@ -18,6 +19,10 @@ import {
} from "@/components/integration-icons"
import { GoogleDrive, Notion, OneDrive } from "@ui/assets/icons"
import { ArrowLeft, Sun } from "lucide-react"
import {
integrationParam,
type IntegrationParamValue,
} from "@/lib/search-params"
import Image from "next/image"
type CardId =
@ -140,10 +145,29 @@ function DetailWrapper({
)
}
const INTEGRATION_TO_CARD: Record<IntegrationParamValue, CardId> = {
import: "import",
chrome: "chrome",
connections: "connections",
}
export function IntegrationsView() {
const [integration, setIntegration] = useQueryState(
"integration",
integrationParam,
)
const [selectedCard, setSelectedCard] = useState<CardId | null>(null)
const handleBack = () => setSelectedCard(null)
useEffect(() => {
if (integration && INTEGRATION_TO_CARD[integration]) {
setSelectedCard(INTEGRATION_TO_CARD[integration])
}
}, [integration])
const handleBack = () => {
setSelectedCard(null)
setIntegration(null)
}
switch (selectedCard) {
case "mcp":

View file

@ -31,6 +31,7 @@ import { HighlightsCard, type HighlightItem } from "./highlights-card"
import { GraphCard } from "./memory-graph"
import { Button } from "@ui/components/button"
import { categoriesParam } from "@/lib/search-params"
import { NovaEmptyState } from "@/components/nova/nova-empty-state"
// Document category type
type DocumentCategory =
@ -81,11 +82,21 @@ interface HighlightsProps {
isLoading: boolean
}
interface NovaEmptyStateProps {
onAddMemory: (tab: "note" | "link") => void
onOpenIntegrations: (
integration?: "import" | "chrome" | "connections",
) => void
isAllSpaces: boolean
spaceName?: string
}
interface MemoriesGridProps {
isChatOpen: boolean
onOpenDocument: (document: DocumentWithMemories) => void
quickNoteProps?: QuickNoteProps
highlightsProps?: HighlightsProps
emptyStateProps?: NovaEmptyStateProps
}
export function MemoriesGrid({
@ -93,9 +104,10 @@ export function MemoriesGrid({
onOpenDocument,
quickNoteProps,
highlightsProps,
emptyStateProps,
}: MemoriesGridProps) {
const { user } = useAuth()
const { selectedProjects, effectiveContainerTags } = useProject()
const { effectiveContainerTags } = useProject()
const isMobile = useIsMobile()
const [selectedCategories, setSelectedCategories] = useQueryState(
"categories",
@ -284,39 +296,44 @@ export function MemoriesGrid({
)
}
const isEmpty = documents.length === 0 && !isPending
const showNovaEmptyState = isEmpty && emptyStateProps
return (
<div className="relative">
<div id="filter-pills" className="flex flex-wrap gap-1.5 mb-3">
<Button
className={cn(
dmSansClassName(),
"rounded-full border border-[#161F2C] bg-[#0D121A] px-2.5 py-1 text-xs h-auto hover:bg-[#00173C] hover:border-[#2261CA33]",
selectedCategories.length === 0 &&
"bg-[#00173C] border-[#2261CA33]",
)}
onClick={handleSelectAll}
>
All
{facetsData?.total !== undefined && (
<span className="ml-1 text-[#737373]">({facetsData.total})</span>
)}
</Button>
{facetsData?.facets.map((facet: DocumentFacet) => (
{!isEmpty && (
<div id="filter-pills" className="flex flex-wrap gap-1.5 mb-3">
<Button
key={facet.category}
className={cn(
dmSansClassName(),
"rounded-full border border-[#161F2C] bg-[#0D121A] px-2.5 py-1 text-xs h-auto hover:bg-[#00173C] hover:border-[#2261CA33]",
selectedCategories.includes(facet.category) &&
selectedCategories.length === 0 &&
"bg-[#00173C] border-[#2261CA33]",
)}
onClick={() => handleCategoryToggle(facet.category)}
onClick={handleSelectAll}
>
{facet.label}
<span className="ml-1 text-[#737373]">({facet.count})</span>
All
{facetsData?.total !== undefined && (
<span className="ml-1 text-[#737373]">({facetsData.total})</span>
)}
</Button>
))}
</div>
{facetsData?.facets.map((facet: DocumentFacet) => (
<Button
key={facet.category}
className={cn(
dmSansClassName(),
"rounded-full border border-[#161F2C] bg-[#0D121A] px-2.5 py-1 text-xs h-auto hover:bg-[#00173C] hover:border-[#2261CA33]",
selectedCategories.includes(facet.category) &&
"bg-[#00173C] border-[#2261CA33]",
)}
onClick={() => handleCategoryToggle(facet.category)}
>
{facet.label}
<span className="ml-1 text-[#737373]">({facet.count})</span>
</Button>
))}
</div>
)}
{error ? (
<div className="h-full flex items-center justify-center p-4">
<div className="text-center text-muted-foreground">
@ -327,7 +344,14 @@ export function MemoriesGrid({
<div className="h-full flex items-center justify-center p-4">
<SuperLoader />
</div>
) : documents.length === 0 && !isPending ? (
) : showNovaEmptyState ? (
<NovaEmptyState
onAddMemory={emptyStateProps.onAddMemory}
onOpenIntegrations={emptyStateProps.onOpenIntegrations}
isAllSpaces={emptyStateProps.isAllSpaces}
spaceName={emptyStateProps.spaceName}
/>
) : isEmpty ? (
<div className="h-full flex items-center justify-center p-4">
<div className="text-center text-muted-foreground">
No memories found

View file

@ -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}
<div
className="w-full h-full relative overflow-hidden touch-none select-none"

View file

@ -0,0 +1,146 @@
"use client"
import { CHROME_EXTENSION_URL } from "@repo/lib/constants"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { Button } from "@ui/components/button"
import NovaOrb from "./nova-orb"
import { ChromeIcon } from "@/components/integration-icons"
import { ArrowRight, Link2, FileText, Zap } from "lucide-react"
interface NovaEmptyStateProps {
onAddMemory: (tab: "note" | "link") => 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 (
<div
id="nova-empty-state"
className="min-h-[calc(100dvh-12rem)] flex items-center justify-center p-6 md:p-8 opacity-50 hover:opacity-100 transition-opacity duration-300"
>
<div className="max-w-xl w-full flex flex-col items-center text-center">
<NovaOrb size={80} className="blur-[2px]! mb-4" />
<h2
className={cn(
"text-white text-xl md:text-2xl font-medium mb-2",
dmSansClassName(),
)}
>
{title}
</h2>
<p className={cn("text-[#8B8B8B] text-sm mb-6", dmSansClassName())}>
{subtitle}
</p>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 w-full mb-4">
<button
type="button"
onClick={() => onAddMemory("link")}
className={cardClass}
>
<div className="flex items-center gap-2">
<Link2 className="size-5 text-[#737373]" />
<span
className={cn(
"font-medium text-white text-sm",
dmSansClassName(),
)}
>
Save a link
</span>
</div>
<span className="text-[#4BA0FA] text-xs font-medium flex items-center gap-1">
Add now <ArrowRight className="size-3" />
</span>
</button>
<button
type="button"
onClick={() => onAddMemory("note")}
className={cardClass}
>
<div className="flex items-center gap-2">
<FileText className="size-5 text-[#737373]" />
<span
className={cn(
"font-medium text-white text-sm",
dmSansClassName(),
)}
>
Write a note
</span>
</div>
<span className="text-[#4BA0FA] text-xs font-medium flex items-center gap-1">
Add now <ArrowRight className="size-3" />
</span>
</button>
<button
type="button"
onClick={handleInstallChrome}
className={cardClass}
>
<div className="flex items-center gap-2">
<ChromeIcon className="size-5" />
<span
className={cn(
"font-medium text-white text-sm",
dmSansClassName(),
)}
>
Chrome Extension
</span>
</div>
<span className="text-[#4BA0FA] text-xs font-medium flex items-center gap-1">
Install <ArrowRight className="size-3" />
</span>
</button>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => onOpenIntegrations()}
className={cn(
"text-[#737373] hover:text-white hover:bg-transparent",
"flex items-center gap-1.5",
dmSansClassName(),
)}
>
<Zap className="size-4" />
See more integrations
<ArrowRight className="size-3" />
</Button>
</div>
</div>
)
}

View file

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

View file

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

View file

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

View file

@ -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 */}
<div className={styles.graphContainer} ref={containerRef}>

View file

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

View file

@ -37,7 +37,9 @@ export interface ProfileResult {
export interface DocumentListResult {
success: boolean
documents?: Awaited<ReturnType<Supermemory["documents"]["list"]>>["documents"]
pagination?: Awaited<ReturnType<Supermemory["documents"]["list"]>>["pagination"]
pagination?: Awaited<
ReturnType<Supermemory["documents"]["list"]>
>["pagination"]
error?: string
}

View file

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

View file

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

View file

@ -409,10 +409,9 @@ export const MemoryGraph = ({
</AnimatePresence>
{/* 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 */}
<div