diff options
| author | nexxeln <[email protected]> | 2025-11-22 07:04:05 +0000 |
|---|---|---|
| committer | nexxeln <[email protected]> | 2025-11-22 07:04:05 +0000 |
| commit | 895f37ac899597dc66c40fb94f9e5bb43d60a42a (patch) | |
| tree | d0825db4ba52cdf5f404058135a8f88961f77a6a /packages/memory-graph/src/components | |
| parent | package the graph (#563) (diff) | |
| download | supermemory-proxy-graph-requests.tar.xz supermemory-proxy-graph-requests.zip | |
runtime styles injection + let user proxy requests for data in graph package + new playground (#588)proxy-graph-requests
Diffstat (limited to 'packages/memory-graph/src/components')
| -rw-r--r-- | packages/memory-graph/src/components/memory-graph-wrapper.tsx | 198 | ||||
| -rw-r--r-- | packages/memory-graph/src/components/memory-graph.tsx | 27 |
2 files changed, 19 insertions, 206 deletions
diff --git a/packages/memory-graph/src/components/memory-graph-wrapper.tsx b/packages/memory-graph/src/components/memory-graph-wrapper.tsx deleted file mode 100644 index cfc8e148..00000000 --- a/packages/memory-graph/src/components/memory-graph-wrapper.tsx +++ /dev/null @@ -1,198 +0,0 @@ -"use client"; - -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { useEffect, useMemo, useRef } from "react"; -import { - flattenDocuments, - getLoadedCount, - getTotalDocuments, - useInfiniteDocumentsQuery, -} from "@/hooks/use-documents-query"; -import { MemoryGraph } from "./memory-graph"; -import { defaultTheme } from "@/styles/theme.css"; -import type { ApiClientError } from "@/lib/api-client"; - -export interface MemoryGraphWrapperProps { - /** API key for authentication */ - apiKey: string; - /** Optional base URL for the API (defaults to https://api.supermemory.ai) */ - baseUrl?: string; - /** Optional document ID to filter by */ - id?: string; - /** Visual variant - console for full view, consumer for embedded */ - variant?: "console" | "consumer"; - /** Show/hide the spaces filter dropdown */ - showSpacesSelector?: boolean; - /** Optional container tags to filter documents */ - containerTags?: string[]; - /** Callback when data fetching fails */ - onError?: (error: ApiClientError) => void; - /** Callback when data is successfully loaded */ - onSuccess?: (totalDocuments: number) => void; - /** Empty state content */ - children?: React.ReactNode; - /** Documents to highlight */ - highlightDocumentIds?: string[]; - /** Whether highlights are visible */ - highlightsVisible?: boolean; - /** Pixels occluded on the right side of the viewport */ - occludedRightPx?: number; -} - -/** - * Internal component that uses the query hooks - */ -function MemoryGraphWithQuery(props: MemoryGraphWrapperProps) { - const { - apiKey, - baseUrl, - containerTags, - variant = "console", - showSpacesSelector, - onError, - onSuccess, - children, - highlightDocumentIds, - highlightsVisible, - occludedRightPx, - } = props; - - // Derive showSpacesSelector from variant if not explicitly provided - // console variant shows spaces selector, consumer variant hides it - const finalShowSpacesSelector = showSpacesSelector ?? (variant === "console"); - - // Use infinite query for automatic pagination - const { - data, - isLoading, - isFetchingNextPage, - hasNextPage, - fetchNextPage, - error, - } = useInfiniteDocumentsQuery({ - apiKey, - baseUrl, - containerTags, - enabled: !!apiKey, - }); - - // Flatten documents from all pages - const documents = useMemo(() => flattenDocuments(data), [data]); - const totalLoaded = useMemo(() => getLoadedCount(data), [data]); - const totalDocuments = useMemo(() => getTotalDocuments(data), [data]); - - // Eagerly load all pages to ensure complete graph data - const isLoadingAllPages = useRef(false); - - useEffect(() => { - // Only start loading once, when initial data is loaded - if (isLoading || isLoadingAllPages.current || !data?.pages?.[0]) return; - - const abortController = new AbortController(); - - // Start recursive page loading - const loadAllPages = async () => { - isLoadingAllPages.current = true; - - try { - // Keep fetching until no more pages or aborted - let shouldContinue = hasNextPage; - - while (shouldContinue && !abortController.signal.aborted) { - const result = await fetchNextPage(); - shouldContinue = result.hasNextPage ?? false; - - // Throttle requests to avoid overwhelming server (50ms delay like console app) - if (shouldContinue && !abortController.signal.aborted) { - await new Promise(resolve => setTimeout(resolve, 50)); - } - } - } catch (error) { - if (!abortController.signal.aborted) { - console.error('[MemoryGraph] Error loading pages:', error); - } - } - }; - - if (hasNextPage) { - loadAllPages(); - } - - // Cleanup on unmount - return () => { - abortController.abort(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); // Only run once on mount - - // Call callbacks - if (error && onError) { - onError(error as ApiClientError); - } - - if (data && onSuccess && totalDocuments > 0) { - onSuccess(totalDocuments); - } - - // Load more function - const loadMoreDocuments = async () => { - if (hasNextPage && !isFetchingNextPage) { - await fetchNextPage(); - } - }; - - return ( - <MemoryGraph - documents={documents} - isLoading={isLoading} - isLoadingMore={isFetchingNextPage} - error={error as Error | null} - totalLoaded={totalLoaded} - hasMore={hasNextPage ?? false} - loadMoreDocuments={loadMoreDocuments} - variant={variant} - showSpacesSelector={finalShowSpacesSelector} - highlightDocumentIds={highlightDocumentIds} - highlightsVisible={highlightsVisible} - occludedRightPx={occludedRightPx} - autoLoadOnViewport={true} - themeClassName={defaultTheme} - > - {children} - </MemoryGraph> - ); -} - -// Create a default query client for the wrapper -const defaultQueryClient = new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - refetchOnMount: false, - retry: 2, - }, - }, -}); - -/** - * MemoryGraph component with built-in data fetching - * - * This component handles all data fetching internally using the provided API key. - * Simply pass your API key and it will fetch and render the graph automatically. - * - * @example - * ```tsx - * <MemoryGraphWrapper - * apiKey="your-api-key" - * variant="console" - * onError={(error) => console.error(error)} - * /> - * ``` - */ -export function MemoryGraphWrapper(props: MemoryGraphWrapperProps) { - return ( - <QueryClientProvider client={defaultQueryClient}> - <MemoryGraphWithQuery {...props} /> - </QueryClientProvider> - ); -} diff --git a/packages/memory-graph/src/components/memory-graph.tsx b/packages/memory-graph/src/components/memory-graph.tsx index 3eeed37b..21d4a08f 100644 --- a/packages/memory-graph/src/components/memory-graph.tsx +++ b/packages/memory-graph/src/components/memory-graph.tsx @@ -6,23 +6,25 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { GraphCanvas } from "./graph-canvas"; import { useGraphData } from "@/hooks/use-graph-data"; import { useGraphInteractions } from "@/hooks/use-graph-interactions"; +import { injectStyles } from "@/lib/inject-styles"; import { Legend } from "./legend"; import { LoadingIndicator } from "./loading-indicator"; import { NavigationControls } from "./navigation-controls"; import { NodeDetailPanel } from "./node-detail-panel"; import { SpacesDropdown } from "./spaces-dropdown"; import * as styles from "./memory-graph.css"; +import { defaultTheme } from "@/styles/theme.css"; import type { MemoryGraphProps } from "@/types"; export const MemoryGraph = ({ children, documents, - isLoading, - isLoadingMore, - error, + isLoading = false, + isLoadingMore = false, + error = null, totalLoaded, - hasMore, + hasMore = false, loadMoreDocuments, showSpacesSelector, variant = "console", @@ -33,6 +35,15 @@ export const MemoryGraph = ({ autoLoadOnViewport = true, themeClassName, }: MemoryGraphProps) => { + // Inject styles on first render (client-side only) + useEffect(() => { + injectStyles(); + }, []); + + // Derive totalLoaded from documents if not provided + const effectiveTotalLoaded = totalLoaded ?? documents.length; + // No-op for loadMoreDocuments if not provided + const effectiveLoadMoreDocuments = loadMoreDocuments ?? (async () => {}); // Derive showSpacesSelector from variant if not explicitly provided // console variant shows spaces selector, consumer variant hides it const finalShowSpacesSelector = showSpacesSelector ?? (variant === "console"); @@ -293,7 +304,7 @@ export const MemoryGraph = ({ // If 80% or more of documents are visible, load more const visibilityRatio = visibleDocuments.length / data.documents.length; if (visibilityRatio >= 0.8) { - loadMoreDocuments(); + effectiveLoadMoreDocuments(); } }, [ isLoadingMore, @@ -305,7 +316,7 @@ export const MemoryGraph = ({ containerSize.width, containerSize.height, nodes, - loadMoreDocuments, + effectiveLoadMoreDocuments, ]); // Throttled version to avoid excessive checks @@ -352,7 +363,7 @@ export const MemoryGraph = ({ } return ( - <div className={themeClassName ? `${themeClassName} ${styles.mainContainer}` : styles.mainContainer}> + <div className={`${themeClassName ?? defaultTheme} ${styles.mainContainer}`}> {/* Spaces selector - only shown for console */} {finalShowSpacesSelector && availableSpaces.length > 0 && ( <div className={styles.spacesSelectorContainer}> @@ -369,7 +380,7 @@ export const MemoryGraph = ({ <LoadingIndicator isLoading={isLoading} isLoadingMore={isLoadingMore} - totalLoaded={totalLoaded} + totalLoaded={effectiveTotalLoaded} variant={variant} /> |