mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
feat: empty state action for new spaces (#780)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ba56b3699b
commit
962fb85cd3
13 changed files with 369 additions and 123 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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("")
|
||||
|
|
@ -348,6 +366,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
|
||||
|
||||
|
|
@ -421,6 +459,16 @@ export default function NewPage() {
|
|||
onShowRelated: handleHighlightsShowRelated,
|
||||
isLoading: isLoadingHighlights,
|
||||
}}
|
||||
emptyStateProps={
|
||||
isNovaContext
|
||||
? {
|
||||
onAddMemory: handleAddMemory,
|
||||
onOpenIntegrations: handleOpenIntegrations,
|
||||
isAllSpaces: isNovaSpaces,
|
||||
spaceName: emptyStateSpaceName,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
|
@ -92,6 +93,15 @@ 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
|
||||
|
|
@ -105,6 +115,7 @@ interface MemoriesGridProps {
|
|||
isBulkDeleting?: boolean
|
||||
quickNoteProps?: QuickNoteProps
|
||||
highlightsProps?: HighlightsProps
|
||||
emptyStateProps?: NovaEmptyStateProps
|
||||
}
|
||||
|
||||
export function MemoriesGrid({
|
||||
|
|
@ -120,6 +131,7 @@ export function MemoriesGrid({
|
|||
isBulkDeleting = false,
|
||||
quickNoteProps,
|
||||
highlightsProps,
|
||||
emptyStateProps,
|
||||
}: MemoriesGridProps) {
|
||||
const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false)
|
||||
const { user } = useAuth()
|
||||
|
|
@ -340,99 +352,107 @@ export function MemoriesGrid({
|
|||
)
|
||||
}
|
||||
|
||||
const isEmpty = documents.length === 0 && !isPending
|
||||
const showNovaEmptyState = isEmpty && emptyStateProps
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
id="filter-pills"
|
||||
className="flex items-center justify-between gap-4 mb-3"
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<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 items-center justify-between gap-4 mb-3"
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<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>
|
||||
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{isSelectionMode && (
|
||||
<>
|
||||
{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>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{isSelectionMode && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Exit selection mode"
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full border border-[#161F2C] bg-[#0D121A] hover:bg-[#00173C] transition-colors cursor-pointer"
|
||||
onClick={onClearSelection}
|
||||
>
|
||||
<XIcon className="w-4 h-4 text-[#737373]" />
|
||||
</button>
|
||||
{selectedDocumentIds.size > 0 ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"text-xs text-[#737373] hover:text-white transition-colors cursor-pointer",
|
||||
)}
|
||||
onClick={handleSelectAllVisible}
|
||||
>
|
||||
Select all
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"flex items-center gap-1 text-xs text-red-400 hover:text-red-300 transition-colors cursor-pointer disabled:opacity-50",
|
||||
)}
|
||||
onClick={handleBulkDeleteClick}
|
||||
disabled={isBulkDeleting}
|
||||
>
|
||||
<Trash2Icon className="w-3 h-3" />
|
||||
Delete ({selectedDocumentIds.size})
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<p
|
||||
className={cn(dmSansClassName(), "text-xs text-[#737373]")}
|
||||
>
|
||||
Select one or more documents
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isSelectionMode && onEnterSelectionMode && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Exit selection mode"
|
||||
aria-label="Enter selection mode"
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full border border-[#161F2C] bg-[#0D121A] hover:bg-[#00173C] transition-colors cursor-pointer"
|
||||
onClick={onClearSelection}
|
||||
onClick={onEnterSelectionMode}
|
||||
>
|
||||
<XIcon className="w-4 h-4 text-[#737373]" />
|
||||
<div className="w-3 h-3 rounded-[2.25px] border border-[#737373]" />
|
||||
</button>
|
||||
{selectedDocumentIds.size > 0 ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"text-xs text-[#737373] hover:text-white transition-colors cursor-pointer",
|
||||
)}
|
||||
onClick={handleSelectAllVisible}
|
||||
>
|
||||
Select all
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"flex items-center gap-1 text-xs text-red-400 hover:text-red-300 transition-colors cursor-pointer disabled:opacity-50",
|
||||
)}
|
||||
onClick={handleBulkDeleteClick}
|
||||
disabled={isBulkDeleting}
|
||||
>
|
||||
<Trash2Icon className="w-3 h-3" />
|
||||
Delete ({selectedDocumentIds.size})
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<p className={cn(dmSansClassName(), "text-xs text-[#737373]")}>
|
||||
Select one or more documents
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isSelectionMode && onEnterSelectionMode && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Enter selection mode"
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full border border-[#161F2C] bg-[#0D121A] hover:bg-[#00173C] transition-colors cursor-pointer"
|
||||
onClick={onEnterSelectionMode}
|
||||
>
|
||||
<div className="w-3 h-3 rounded-[2.25px] border border-[#737373]" />
|
||||
</button>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AlertDialog
|
||||
open={showBulkDeleteConfirm}
|
||||
|
|
@ -486,7 +506,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
|
||||
|
|
|
|||
146
apps/web/components/nova/nova-empty-state.tsx
Normal file
146
apps/web/components/nova/nova-empty-state.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.2/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.6/schema.json",
|
||||
"css": {
|
||||
"parser": {
|
||||
"tailwindDirectives": true
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -410,9 +410,8 @@ export const MemoryGraph = ({
|
|||
|
||||
{/* Show welcome screen when no memories exist */}
|
||||
{!isLoading &&
|
||||
(!data || !nodes.some((n) => n.type === "document")) && (
|
||||
<>{children}</>
|
||||
)}
|
||||
(!data || !nodes.some((n) => n.type === "document")) &&
|
||||
children}
|
||||
|
||||
{/* Graph container */}
|
||||
<div
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue