"use client" import { useState, useCallback, useEffect, useMemo } from "react" import { MemoryGraph, type GraphApiDocument, type GraphApiMemory, type GraphThemeColors, type MemoryRelation, } from "@supermemory/memory-graph" import { generateMockGraphData } from "@supermemory/memory-graph/mock-data" interface PlaygroundApiMemory { id: string memory?: string | null content?: string | null isStatic?: boolean spaceId?: string | null isLatest?: boolean isForgotten?: boolean forgetAfter?: string | null forgetReason?: string | null version?: number parentMemoryId?: string | null rootMemoryId?: string | null createdAt: string updatedAt: string relation?: MemoryRelation | null updatesMemoryId?: string | null nextVersionId?: string | null memoryRelations?: Record | null spaceContainerTag?: string | null } interface PlaygroundApiDocument { id: string title: string | null summary?: string | null documentType?: string type?: string containerTags?: string[] createdAt: string updatedAt: string memories?: PlaygroundApiMemory[] memoryEntries?: PlaygroundApiMemory[] } interface DocumentsResponse { documents: PlaygroundApiDocument[] pagination: { currentPage: number limit: number totalItems: number totalPages: number } } interface ContainerTagOption { id: string name?: string | null containerTag: string documentCount?: number memoryCount?: number lastActivityAt?: string | null } type GraphVariant = "consumer" | "console" type LoadBehavior = "zoom" | "manual" | "background" const PAGE_SIZE = 100 const BACKGROUND_LOAD_DELAY_MS = 900 const CONSUMER_GRAPH_COLORS = { bg: "transparent", edgeDerives: "#9ca3af", } satisfies Partial /** Convert the external API format to the internal graph format */ function toGraphDocuments(docs: PlaygroundApiDocument[]): GraphApiDocument[] { return docs.map((doc) => { const memories = doc.memories ?? doc.memoryEntries ?? [] return { id: doc.id, title: doc.title, summary: doc.summary ?? null, documentType: doc.documentType ?? doc.type ?? "unknown", createdAt: doc.createdAt, updatedAt: doc.updatedAt, memories: memories.map( (mem): GraphApiMemory => ({ id: mem.id, memory: mem.memory ?? mem.content ?? "", isStatic: mem.isStatic ?? false, spaceId: mem.spaceId ?? "", isLatest: mem.isLatest ?? true, isForgotten: mem.isForgotten ?? false, forgetAfter: mem.forgetAfter ?? null, forgetReason: mem.forgetReason ?? null, version: mem.version ?? 1, parentMemoryId: mem.parentMemoryId ?? null, rootMemoryId: mem.rootMemoryId ?? null, createdAt: mem.createdAt, updatedAt: mem.updatedAt, relation: mem.relation ?? null, updatesMemoryId: mem.updatesMemoryId ?? null, nextVersionId: mem.nextVersionId ?? null, memoryRelations: mem.memoryRelations ?? null, spaceContainerTag: mem.spaceContainerTag ?? null, }), ), } }) } export default function Home() { const [apiKey, setApiKey] = useState("") const [containerTag, setContainerTag] = useState("") const [containerTags, setContainerTags] = useState([]) const [isLoadingContainerTags, setIsLoadingContainerTags] = useState(false) const [containerTagsError, setContainerTagsError] = useState( null, ) const [documents, setDocuments] = useState([]) const [isLoading, setIsLoading] = useState(false) const [isLoadingMore, setIsLoadingMore] = useState(false) const [error, setError] = useState(null) const [showGraph, setShowGraph] = useState(false) const [stressTestCount, setStressTestCount] = useState(0) const [graphVariant, setGraphVariant] = useState("consumer") const [loadBehavior, setLoadBehavior] = useState("zoom") const [pagination, setPagination] = useState< DocumentsResponse["pagination"] | null >(null) // State for slideshow const [isSlideshowActive, setIsSlideshowActive] = useState(false) // Mock data for stress testing const [mockData, setMockData] = useState<{ documents: GraphApiDocument[] } | null>(null) const selectedContainerTags = useMemo(() => { const trimmed = containerTag.trim() return trimmed ? [trimmed] : undefined }, [containerTag]) const fetchContainerTags = useCallback(async () => { if (!apiKey || isLoadingContainerTags) return setIsLoadingContainerTags(true) setContainerTagsError(null) try { const response = await fetch("/api/container-tags", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ apiKey }), }) if (!response.ok) { const errorData = await response.json() throw new Error(errorData.error || "Failed to fetch container tags") } const data = (await response.json()) as ContainerTagOption[] setContainerTags(data) } catch (err) { setContainerTagsError( err instanceof Error ? err : new Error("Unknown error"), ) } finally { setIsLoadingContainerTags(false) } }, [apiKey, isLoadingContainerTags]) const fetchDocuments = useCallback( async (page: number, append = false) => { if (!apiKey) return if (append) { setIsLoadingMore(true) } else { setIsLoading(true) } setError(null) try { const response = await fetch("/api/graph", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ apiKey, page, limit: PAGE_SIZE, sort: "createdAt", order: "desc", containerTags: selectedContainerTags, }), }) if (!response.ok) { const errorData = await response.json() throw new Error(errorData.error || "Failed to fetch documents") } const data: DocumentsResponse = await response.json() if (append) { setDocuments((prev) => [...prev, ...data.documents]) } else { setDocuments(data.documents) } setPagination(data.pagination) setShowGraph(true) setMockData(null) setStressTestCount(0) } catch (err) { setError(err instanceof Error ? err : new Error("Unknown error")) } finally { setIsLoading(false) setIsLoadingMore(false) } }, [apiKey, selectedContainerTags], ) const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (apiKey) { setDocuments([]) setPagination(null) void fetchContainerTags() fetchDocuments(1) } } const handleLoadMoreDocuments = useCallback(() => { if (!pagination || pagination.currentPage >= pagination.totalPages) return fetchDocuments(pagination.currentPage + 1, true) }, [fetchDocuments, pagination]) const handleStressTest = (count: number) => { const data = generateMockGraphData({ documentCount: count, memoriesPerDoc: [2, 5], seed: 12345, }) setMockData({ documents: data.documents }) setDocuments([]) setPagination(null) setStressTestCount(count) setShowGraph(true) setError(null) } // Toggle slideshow const handleToggleSlideshow = () => { setIsSlideshowActive((prev) => !prev) } // Handle slideshow node change const handleSlideshowNodeChange = useCallback((nodeId: string | null) => { console.log("Slideshow showing node:", nodeId) }, []) // Handle slideshow stop const handleSlideshowStop = useCallback(() => { setIsSlideshowActive(false) }, []) // Convert real documents to graph format const graphDocuments = useMemo(() => { if (mockData) return mockData.documents return toGraphDocuments(documents) }, [documents, mockData]) const availableContainerTags = useMemo(() => { const options = new Map() for (const tag of containerTags) { if (tag.containerTag) options.set(tag.containerTag, tag) } for (const doc of documents) { for (const tag of doc.containerTags ?? []) { if (tag && !options.has(tag)) { options.set(tag, { id: tag, containerTag: tag, name: tag }) } } const memories = doc.memories ?? doc.memoryEntries ?? [] for (const mem of memories) { const tag = mem.spaceContainerTag if (tag && !options.has(tag)) { options.set(tag, { id: tag, containerTag: tag, name: tag }) } } } return [...options.values()] }, [containerTags, documents]) const displayCount = mockData ? stressTestCount : documents.length const hasMore = !mockData && pagination != null && pagination.currentPage < pagination.totalPages const totalCount = mockData ? stressTestCount : (pagination?.totalItems ?? documents.length) const maxNodes = mockData ? 1000 : undefined const graphHandlesLoadMore = loadBehavior === "zoom" useEffect(() => { if ( loadBehavior !== "background" || !showGraph || mockData || !hasMore || isLoading || isLoadingMore || error ) { return } const timer = window.setTimeout( handleLoadMoreDocuments, BACKGROUND_LOAD_DELAY_MS, ) return () => window.clearTimeout(timer) }, [ error, handleLoadMoreDocuments, hasMore, isLoading, isLoadingMore, loadBehavior, mockData, showGraph, ]) return (
{/* Header */}

Memory Graph Playground

Test the @supermemory/memory-graph package

{ setApiKey(e.target.value) setContainerTags([]) setContainerTagsError(null) }} className="w-80 rounded-lg border border-zinc-700 bg-zinc-800 px-4 py-2 text-sm text-white placeholder-zinc-500 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
setContainerTag(e.target.value)} onFocus={() => { if (availableContainerTags.length === 0) { void fetchContainerTags() } }} disabled={!apiKey} placeholder={ isLoadingContainerTags ? "Loading container tags..." : "All container tags" } className="w-64 rounded-lg border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:cursor-not-allowed disabled:opacity-50" /> {availableContainerTags.map((tag) => ( ))}
{/* Controls Panel */}
Documents: {displayCount}
{stressTestCount > 0 && ( Stress Test Mode )} {pagination && !mockData && ( Page {pagination.currentPage}/{pagination.totalPages} )} {selectedContainerTags && ( {selectedContainerTags[0]} )} {containerTagsError && ( {containerTagsError.message} )}
Mode:
{(["consumer", "console"] as const).map((variant) => ( ))}
Load:
{(["zoom", "manual", "background"] as const).map((behavior) => ( ))}
{loadBehavior === "manual" && ( )} {loadBehavior === "background" && !mockData && hasMore && ( Auto paging )}
{/* Stress test buttons */} Stress Test: {[50, 100, 200, 500].map((count) => ( ))}
{/* Main content */}
{!showGraph ? (

Get Started

Enter your API key above, or click a stress test button to generate mock data.

Features to test:

  • Pan and zoom the graph
  • Click on nodes to see details
  • Drag nodes around
  • Arrow key navigation
  • Stress test with 50-500 documents
  • FPS counter (shown during stress tests)
) : (
0} isSlideshowActive={isSlideshowActive} onSlideshowNodeChange={handleSlideshowNodeChange} onSlideshowStop={handleSlideshowStop} totalCount={totalCount} colors={ graphVariant === "consumer" ? CONSUMER_GRAPH_COLORS : undefined } >

No memories found. Add some content to see your graph.

)}
) }