aboutsummaryrefslogtreecommitdiff
path: root/apps/memory-graph-playground/src
diff options
context:
space:
mode:
authornexxeln <[email protected]>2025-12-02 18:37:24 +0000
committernexxeln <[email protected]>2025-12-02 18:37:24 +0000
commitdfb0c05ab33cb20537002eaeb896e6b2ab35af25 (patch)
tree49ecaa46903671d96f2f9ebc5af688ab2ea2c7bd /apps/memory-graph-playground/src
parentFix: Update discord links in README.md and CONTRIBUTING.md (#598) (diff)
downloadsupermemory-dfb0c05ab33cb20537002eaeb896e6b2ab35af25.tar.xz
supermemory-dfb0c05ab33cb20537002eaeb896e6b2ab35af25.zip
add spaces selector with search (#600)update-memory-graph
relevant files to review: \- memory-graph.tsx \- spaces-dropdown.tsx \- spaces-dropdown.css.ts
Diffstat (limited to 'apps/memory-graph-playground/src')
-rw-r--r--apps/memory-graph-playground/src/app/api/graph/route.ts87
-rw-r--r--apps/memory-graph-playground/src/app/globals.css26
-rw-r--r--apps/memory-graph-playground/src/app/layout.tsx46
-rw-r--r--apps/memory-graph-playground/src/app/page.tsx393
4 files changed, 316 insertions, 236 deletions
diff --git a/apps/memory-graph-playground/src/app/api/graph/route.ts b/apps/memory-graph-playground/src/app/api/graph/route.ts
index b7901966..c722c625 100644
--- a/apps/memory-graph-playground/src/app/api/graph/route.ts
+++ b/apps/memory-graph-playground/src/app/api/graph/route.ts
@@ -1,46 +1,55 @@
-import { NextResponse } from 'next/server'
+import { NextResponse } from "next/server"
export async function POST(request: Request) {
- try {
- const body = await request.json()
- const { apiKey, page = 1, limit = 500, sort = 'createdAt', order = 'desc' } = body
+ try {
+ const body = await request.json()
+ const {
+ apiKey,
+ page = 1,
+ limit = 500,
+ sort = "createdAt",
+ order = "desc",
+ } = body
- if (!apiKey) {
- return NextResponse.json(
- { error: 'API key is required' },
- { status: 400 }
- )
- }
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: "API key is required" },
+ { status: 400 },
+ )
+ }
- const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${apiKey}`,
- },
- body: JSON.stringify({
- page,
- limit,
- sort,
- order,
- }),
- })
+ const response = await fetch(
+ "https://api.supermemory.ai/v3/documents/documents",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify({
+ page,
+ limit,
+ sort,
+ order,
+ }),
+ },
+ )
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}))
- return NextResponse.json(
- { error: errorData.message || `API error: ${response.status}` },
- { status: response.status }
- )
- }
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ return NextResponse.json(
+ { error: errorData.message || `API error: ${response.status}` },
+ { status: response.status },
+ )
+ }
- const data = await response.json()
- return NextResponse.json(data)
- } catch (error) {
- console.error('Graph API error:', error)
- return NextResponse.json(
- { error: 'Failed to fetch documents' },
- { status: 500 }
- )
- }
+ const data = await response.json()
+ return NextResponse.json(data)
+ } catch (error) {
+ console.error("Graph API error:", error)
+ return NextResponse.json(
+ { error: "Failed to fetch documents" },
+ { status: 500 },
+ )
+ }
}
diff --git a/apps/memory-graph-playground/src/app/globals.css b/apps/memory-graph-playground/src/app/globals.css
index a2dc41ec..0673ff02 100644
--- a/apps/memory-graph-playground/src/app/globals.css
+++ b/apps/memory-graph-playground/src/app/globals.css
@@ -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;
}
diff --git a/apps/memory-graph-playground/src/app/layout.tsx b/apps/memory-graph-playground/src/app/layout.tsx
index f7fa87eb..bb3b3e84 100644
--- a/apps/memory-graph-playground/src/app/layout.tsx
+++ b/apps/memory-graph-playground/src/app/layout.tsx
@@ -1,34 +1,34 @@
-import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
-import "./globals.css";
+import type { Metadata } from "next"
+import { Geist, Geist_Mono } from "next/font/google"
+import "./globals.css"
const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
+ variable: "--font-geist-sans",
+ subsets: ["latin"],
+})
const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
+ variable: "--font-geist-mono",
+ subsets: ["latin"],
+})
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
-};
+ title: "Create Next App",
+ description: "Generated by create next app",
+}
export default function RootLayout({
- children,
+ children,
}: Readonly<{
- children: React.ReactNode;
+ children: React.ReactNode
}>) {
- return (
- <html lang="en">
- <body
- className={`${geistSans.variable} ${geistMono.variable} antialiased`}
- >
- {children}
- </body>
- </html>
- );
+ return (
+ <html lang="en">
+ <body
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
+ >
+ {children}
+ </body>
+ </html>
+ )
}
diff --git a/apps/memory-graph-playground/src/app/page.tsx b/apps/memory-graph-playground/src/app/page.tsx
index bf202554..7192c4c2 100644
--- a/apps/memory-graph-playground/src/app/page.tsx
+++ b/apps/memory-graph-playground/src/app/page.tsx
@@ -1,169 +1,240 @@
"use client"
-import { useState, useCallback } from 'react'
-import { MemoryGraph, type DocumentWithMemories } from '@supermemory/memory-graph'
+import { useState, useCallback } from "react"
+import {
+ MemoryGraph,
+ type DocumentWithMemories,
+} from "@supermemory/memory-graph"
interface DocumentsResponse {
- documents: DocumentWithMemories[]
- pagination: {
- currentPage: number
- limit: number
- totalItems: number
- totalPages: number
- }
+ documents: DocumentWithMemories[]
+ pagination: {
+ currentPage: number
+ limit: number
+ totalItems: number
+ totalPages: number
+ }
}
export default function Home() {
- const [apiKey, setApiKey] = useState('')
- const [documents, setDocuments] = useState<DocumentWithMemories[]>([])
- const [isLoading, setIsLoading] = useState(false)
- const [isLoadingMore, setIsLoadingMore] = useState(false)
- const [error, setError] = useState<Error | null>(null)
- const [hasMore, setHasMore] = useState(false)
- const [currentPage, setCurrentPage] = useState(0)
- const [showGraph, setShowGraph] = useState(false)
-
- const PAGE_SIZE = 500
-
- const fetchDocuments = useCallback(async (page: number, append = false) => {
- if (!apiKey) return
-
- if (page === 1) {
- setIsLoading(true)
- } else {
- setIsLoadingMore(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',
- }),
- })
-
- 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)
- }
-
- setCurrentPage(data.pagination.currentPage)
- setHasMore(data.pagination.currentPage < data.pagination.totalPages)
- setShowGraph(true)
- } catch (err) {
- setError(err instanceof Error ? err : new Error('Unknown error'))
- } finally {
- setIsLoading(false)
- setIsLoadingMore(false)
- }
- }, [apiKey])
-
- const loadMoreDocuments = useCallback(async () => {
- if (hasMore && !isLoadingMore) {
- await fetchDocuments(currentPage + 1, true)
- }
- }, [hasMore, isLoadingMore, currentPage, fetchDocuments])
-
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault()
- if (apiKey) {
- setDocuments([])
- setCurrentPage(0)
- fetchDocuments(1)
- }
- }
-
- return (
- <div className="flex flex-col h-screen bg-zinc-950">
- {/* Header */}
- <header className="shrink-0 border-b border-zinc-800 bg-zinc-900 px-6 py-4">
- <div className="flex items-center justify-between">
- <div>
- <h1 className="text-xl font-semibold text-white">Memory Graph Playground</h1>
- <p className="text-sm text-zinc-400">Test the @supermemory/memory-graph package</p>
- </div>
-
- <form onSubmit={handleSubmit} className="flex items-center gap-3">
- <input
- type="password"
- placeholder="Enter your Supermemory API key"
- value={apiKey}
- onChange={(e) => setApiKey(e.target.value)}
- 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"
- />
- <button
- type="submit"
- disabled={!apiKey || isLoading}
- className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
- >
- {isLoading ? 'Loading...' : 'Load Graph'}
- </button>
- </form>
- </div>
- </header>
-
- {/* Main content */}
- <main className="flex-1 overflow-hidden">
- {!showGraph ? (
- <div className="flex h-full items-center justify-center">
- <div className="max-w-md text-center">
- <div className="mb-6 text-6xl">
- <svg className="mx-auto h-16 w-16 text-zinc-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
- </svg>
- </div>
- <h2 className="mb-2 text-xl font-semibold text-white">Get Started</h2>
- <p className="mb-6 text-zinc-400">
- Enter your Supermemory API key above to visualize your memory graph.
- </p>
- <div className="text-left text-sm text-zinc-500">
- <p className="mb-2 font-medium text-zinc-400">Features to test:</p>
- <ul className="list-inside list-disc space-y-1">
- <li>Pan and zoom the graph</li>
- <li>Click on nodes to see details</li>
- <li>Drag nodes around</li>
- <li>Use the space selector to filter</li>
- <li>Pagination loads more documents</li>
- </ul>
- </div>
- </div>
- </div>
- ) : (
- <div className="h-full w-full">
- <MemoryGraph
- documents={documents}
- isLoading={isLoading}
- isLoadingMore={isLoadingMore}
- error={error}
- hasMore={hasMore}
- loadMoreDocuments={loadMoreDocuments}
- totalLoaded={documents.length}
- variant="console"
- showSpacesSelector={true}
- >
- <div className="flex h-full items-center justify-center">
- <p className="text-zinc-400">No memories found. Add some content to see your graph.</p>
- </div>
- </MemoryGraph>
- </div>
- )}
- </main>
- </div>
- )
+ const [apiKey, setApiKey] = useState("")
+ const [documents, setDocuments] = useState<DocumentWithMemories[]>([])
+ const [isLoading, setIsLoading] = useState(false)
+ const [isLoadingMore, setIsLoadingMore] = useState(false)
+ const [error, setError] = useState<Error | null>(null)
+ const [hasMore, setHasMore] = useState(false)
+ const [currentPage, setCurrentPage] = useState(0)
+ const [showGraph, setShowGraph] = useState(false)
+
+ // State for controlled space selection
+ const [selectedSpace, setSelectedSpace] = useState<string>("all")
+
+ const PAGE_SIZE = 500
+
+ const fetchDocuments = useCallback(
+ async (page: number, append = false) => {
+ if (!apiKey) return
+
+ if (page === 1) {
+ setIsLoading(true)
+ } else {
+ setIsLoadingMore(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",
+ }),
+ })
+
+ 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)
+ }
+
+ setCurrentPage(data.pagination.currentPage)
+ setHasMore(data.pagination.currentPage < data.pagination.totalPages)
+ setShowGraph(true)
+ } catch (err) {
+ setError(err instanceof Error ? err : new Error("Unknown error"))
+ } finally {
+ setIsLoading(false)
+ setIsLoadingMore(false)
+ }
+ },
+ [apiKey],
+ )
+
+ const loadMoreDocuments = useCallback(async () => {
+ if (hasMore && !isLoadingMore) {
+ await fetchDocuments(currentPage + 1, true)
+ }
+ }, [hasMore, isLoadingMore, currentPage, fetchDocuments])
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault()
+ if (apiKey) {
+ setDocuments([])
+ setCurrentPage(0)
+ setSelectedSpace("all")
+ fetchDocuments(1)
+ }
+ }
+
+ // Handle space change
+ const handleSpaceChange = useCallback((spaceId: string) => {
+ setSelectedSpace(spaceId)
+ }, [])
+
+ // Reset to defaults
+ const handleReset = () => {
+ setSelectedSpace("all")
+ }
+
+ return (
+ <div className="flex flex-col h-screen bg-zinc-950">
+ {/* Header */}
+ <header className="shrink-0 border-b border-zinc-800 bg-zinc-900 px-6 py-4">
+ <div className="flex items-center justify-between">
+ <div>
+ <h1 className="text-xl font-semibold text-white">
+ Memory Graph Playground
+ </h1>
+ <p className="text-sm text-zinc-400">
+ Test the @supermemory/memory-graph package
+ </p>
+ </div>
+
+ <form onSubmit={handleSubmit} className="flex items-center gap-3">
+ <input
+ type="password"
+ placeholder="Enter your Supermemory API key"
+ value={apiKey}
+ onChange={(e) => setApiKey(e.target.value)}
+ 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"
+ />
+ <button
+ type="submit"
+ disabled={!apiKey || isLoading}
+ className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ {isLoading ? "Loading..." : "Load Graph"}
+ </button>
+ </form>
+ </div>
+ </header>
+
+ {/* State Display Panel - For Testing */}
+ {showGraph && (
+ <div className="shrink-0 border-b border-zinc-800 bg-zinc-900/50 px-6 py-3">
+ <div className="flex items-center justify-between text-sm">
+ <div className="flex items-center gap-6">
+ <div className="flex items-center gap-2">
+ <span className="text-zinc-400">Selected Space:</span>
+ <span className="font-mono text-blue-400">{selectedSpace}</span>
+ </div>
+ <div className="flex items-center gap-2">
+ <span className="text-zinc-400">Documents:</span>
+ <span className="font-mono text-emerald-400">
+ {documents.length}
+ </span>
+ </div>
+ </div>
+ <button
+ onClick={handleReset}
+ className="rounded-lg border border-zinc-700 px-3 py-1 text-xs font-medium text-zinc-300 transition-colors hover:bg-zinc-800"
+ >
+ Reset Filters
+ </button>
+ </div>
+ </div>
+ )}
+
+ {/* Main content */}
+ <main className="flex-1 overflow-hidden">
+ {!showGraph ? (
+ <div className="flex h-full items-center justify-center">
+ <div className="max-w-md text-center">
+ <div className="mb-6 text-6xl">
+ <svg
+ className="mx-auto h-16 w-16 text-zinc-600"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth={1.5}
+ d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
+ />
+ </svg>
+ </div>
+ <h2 className="mb-2 text-xl font-semibold text-white">
+ Get Started
+ </h2>
+ <p className="mb-6 text-zinc-400">
+ Enter your Supermemory API key above to visualize your memory
+ graph.
+ </p>
+ <div className="text-left text-sm text-zinc-500">
+ <p className="mb-2 font-medium text-zinc-400">
+ Features to test:
+ </p>
+ <ul className="list-inside list-disc space-y-1">
+ <li>✨ Search and filter by spaces</li>
+ <li>✨ Arrow key navigation in spaces dropdown</li>
+ <li>Pan and zoom the graph</li>
+ <li>Click on nodes to see details</li>
+ <li>Drag nodes around</li>
+ <li>Filter by space</li>
+ <li>Pagination loads more documents</li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ ) : (
+ <div className="h-full w-full">
+ <MemoryGraph
+ documents={documents}
+ isLoading={isLoading}
+ isLoadingMore={isLoadingMore}
+ error={error}
+ hasMore={hasMore}
+ loadMoreDocuments={loadMoreDocuments}
+ totalLoaded={documents.length}
+ variant="consumer"
+ // Controlled space selection
+ selectedSpace={selectedSpace}
+ onSpaceChange={handleSpaceChange}
+ >
+ <div className="flex h-full items-center justify-center">
+ <p className="text-zinc-400">
+ No memories found. Add some content to see your graph.
+ </p>
+ </div>
+ </MemoryGraph>
+ </div>
+ )}
+ </main>
+ </div>
+ )
}