aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvorahardik7 <[email protected]>2025-10-15 19:04:23 -0700
committervorahardik7 <[email protected]>2025-10-15 19:04:23 -0700
commit33ee19e7aaaabf902341f3fafc745cc661e9e8a0 (patch)
treeab64f4396ef38858957fa222772b5b887794e693
parentMerge pull request #488 from naman06dev/main (diff)
downloadsupermemory-33ee19e7aaaabf902341f3fafc745cc661e9e8a0.tar.xz
supermemory-33ee19e7aaaabf902341f3fafc745cc661e9e8a0.zip
fix: mount graph dialog globally to fix chat page issue
-rw-r--r--apps/web/app/(navigation)/layout.tsx3
-rw-r--r--apps/web/components/graph-dialog.tsx244
-rw-r--r--apps/web/components/memories.tsx77
3 files changed, 250 insertions, 74 deletions
diff --git a/apps/web/app/(navigation)/layout.tsx b/apps/web/app/(navigation)/layout.tsx
index da5a87fb..4c37c10e 100644
--- a/apps/web/app/(navigation)/layout.tsx
+++ b/apps/web/app/(navigation)/layout.tsx
@@ -1,5 +1,6 @@
"use client"
+import { GraphDialog } from "@/components/graph-dialog"
import { Header } from "@/components/header"
import { AddMemoryView } from "@/components/views/add-memory"
import { useEffect, useState } from "react"
@@ -52,6 +53,8 @@ export default function NavigationLayout({
onClose={() => setShowAddMemoryView(false)}
/>
)}
+
+ <GraphDialog />
</div>
)
}
diff --git a/apps/web/components/graph-dialog.tsx b/apps/web/components/graph-dialog.tsx
new file mode 100644
index 00000000..aaf1bc2c
--- /dev/null
+++ b/apps/web/components/graph-dialog.tsx
@@ -0,0 +1,244 @@
+// apps/web/components/graph-dialog.tsx
+"use client"
+
+import { useAuth } from "@lib/auth-context"
+import { $fetch } from "@repo/lib/api"
+import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
+import { useInfiniteQuery } from "@tanstack/react-query"
+import { useCallback, useEffect, useMemo, useState } from "react"
+import type { z } from "zod"
+import { MemoryGraph } from "@repo/ui/memory-graph"
+import { Dialog, DialogContent } from "@repo/ui/components/dialog"
+import { ConnectAIModal } from "@/components/connect-ai-modal"
+import { AddMemoryView } from "@/components/views/add-memory"
+import { useChatOpen, useProject, useGraphModal } from "@/stores"
+import { useGraphHighlights } from "@/stores/highlights"
+import { useIsMobile } from "@hooks/use-mobile"
+
+type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema>
+type DocumentWithMemories = DocumentsResponse["documents"][0]
+
+export function GraphDialog() {
+ const { user } = useAuth()
+ const { documentIds: allHighlightDocumentIds } = useGraphHighlights()
+ const { selectedProject } = useProject()
+ const { isOpen } = useChatOpen()
+ const { isOpen: showGraphModal, setIsOpen: setShowGraphModal } =
+ useGraphModal()
+ const [injectedDocs, setInjectedDocs] = useState<DocumentWithMemories[]>([])
+ const [showAddMemoryView, setShowAddMemoryView] = useState(false)
+ const [showConnectAIModal, setShowConnectAIModal] = useState(false)
+ const isMobile = useIsMobile()
+
+ const IS_DEV = process.env.NODE_ENV === "development"
+ const PAGE_SIZE = IS_DEV ? 100 : 100
+ const MAX_TOTAL = 1000
+
+ const {
+ data,
+ error,
+ isPending,
+ isFetchingNextPage,
+ hasNextPage,
+ fetchNextPage,
+ } = useInfiniteQuery<DocumentsResponse, Error>({
+ queryKey: ["documents-with-memories", selectedProject],
+ initialPageParam: 1,
+ queryFn: async ({ pageParam }) => {
+ const response = await $fetch("@post/documents/documents", {
+ body: {
+ page: pageParam as number,
+ limit: (pageParam as number) === 1 ? (IS_DEV ? 500 : 500) : PAGE_SIZE,
+ sort: "createdAt",
+ order: "desc",
+ containerTags: selectedProject ? [selectedProject] : undefined,
+ },
+ disableValidation: true,
+ })
+
+ if (response.error) {
+ throw new Error(response.error?.message || "Failed to fetch documents")
+ }
+
+ return response.data
+ },
+ getNextPageParam: (lastPage, allPages) => {
+ const loaded = allPages.reduce(
+ (acc, p) => acc + (p.documents?.length ?? 0),
+ 0,
+ )
+ if (loaded >= MAX_TOTAL) return undefined
+
+ const { currentPage, totalPages } = lastPage.pagination
+ if (currentPage < totalPages) {
+ return currentPage + 1
+ }
+ return undefined
+ },
+ staleTime: 5 * 60 * 1000,
+ enabled: !!user, // Only run query if user is authenticated
+ })
+
+ const baseDocuments = useMemo(() => {
+ return (
+ data?.pages.flatMap((p: DocumentsResponse) => p.documents ?? []) ?? []
+ )
+ }, [data])
+
+ const allDocuments = useMemo(() => {
+ if (injectedDocs.length === 0) return baseDocuments
+ const byId = new Map<string, DocumentWithMemories>()
+ for (const d of injectedDocs) byId.set(d.id, d)
+ for (const d of baseDocuments) if (!byId.has(d.id)) byId.set(d.id, d)
+ return Array.from(byId.values())
+ }, [baseDocuments, injectedDocs])
+
+ const totalLoaded = allDocuments.length
+ const hasMore = hasNextPage
+ const isLoadingMore = isFetchingNextPage
+
+ const loadMoreDocuments = useCallback(async (): Promise<void> => {
+ if (hasNextPage && !isFetchingNextPage) {
+ await fetchNextPage()
+ return
+ }
+ return
+ }, [hasNextPage, isFetchingNextPage, fetchNextPage])
+
+ // Handle highlighted documents injection for chat
+ useEffect(() => {
+ if (!isOpen) return
+ if (!allHighlightDocumentIds || allHighlightDocumentIds.length === 0) return
+ const present = new Set<string>()
+ for (const d of [...baseDocuments, ...injectedDocs]) {
+ if (d.id) present.add(d.id)
+ if (d.customId) present.add(d.customId as string)
+ }
+ const missing = allHighlightDocumentIds.filter(
+ (id: string) => !present.has(id),
+ )
+ if (missing.length === 0) return
+ let cancelled = false
+ const run = async () => {
+ try {
+ const resp = await $fetch("@post/documents/documents/by-ids", {
+ body: {
+ ids: missing,
+ by: "customId",
+ containerTags: selectedProject ? [selectedProject] : undefined,
+ },
+ disableValidation: true,
+ })
+ if (cancelled || resp?.error) return
+ const extraDocs = resp?.data?.documents as
+ | DocumentWithMemories[]
+ | undefined
+ if (!extraDocs || extraDocs.length === 0) return
+ setInjectedDocs((prev) => {
+ const seen = new Set<string>([
+ ...prev.map((d) => d.id),
+ ...baseDocuments.map((d) => d.id),
+ ])
+ const merged = [...prev]
+ for (const doc of extraDocs) {
+ if (!seen.has(doc.id)) {
+ merged.push(doc)
+ seen.add(doc.id)
+ }
+ }
+ return merged
+ })
+ } catch {}
+ }
+ void run()
+ return () => {
+ cancelled = true
+ }
+ }, [
+ isOpen,
+ allHighlightDocumentIds,
+ baseDocuments,
+ injectedDocs,
+ selectedProject,
+ ])
+
+ if (!user) return null
+
+ return (
+ <>
+ <Dialog open={showGraphModal} onOpenChange={setShowGraphModal}>
+ <DialogContent
+ className="w-[95vw] h-[95vh] p-0 max-w-6xl sm:max-w-6xl"
+ showCloseButton={true}
+ >
+ <div className="w-full h-full">
+ <MemoryGraph
+ documents={allDocuments}
+ error={error}
+ hasMore={hasMore}
+ isLoading={isPending}
+ isLoadingMore={isLoadingMore}
+ loadMoreDocuments={loadMoreDocuments}
+ totalLoaded={totalLoaded}
+ variant="console"
+ showSpacesSelector={true}
+ highlightDocumentIds={allHighlightDocumentIds}
+ highlightsVisible={isOpen}
+ >
+ <div className="absolute inset-0 flex items-center justify-center">
+ {!isMobile ? (
+ <ConnectAIModal
+ onOpenChange={setShowConnectAIModal}
+ open={showConnectAIModal}
+ >
+ <div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
+ <div className="relative z-10 text-slate-200 text-center">
+ <div className="flex flex-col gap-3">
+ <button
+ className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline"
+ onClick={(e) => {
+ e.stopPropagation()
+ setShowAddMemoryView(true)
+ setShowConnectAIModal(false)
+ }}
+ type="button"
+ >
+ Add your first memory
+ </button>
+ </div>
+ </div>
+ </div>
+ </ConnectAIModal>
+ ) : (
+ <div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
+ <div className="relative z-10 text-slate-200 text-center">
+ <div className="flex flex-col gap-3">
+ <button
+ className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline"
+ onClick={(e) => {
+ e.stopPropagation()
+ setShowAddMemoryView(true)
+ }}
+ type="button"
+ >
+ Add your first memory
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+ </MemoryGraph>
+ </div>
+ </DialogContent>
+ </Dialog>
+
+ {showAddMemoryView && (
+ <AddMemoryView
+ initialTab="note"
+ onClose={() => setShowAddMemoryView(false)}
+ />
+ )}
+ </>
+ )
+} \ No newline at end of file
diff --git a/apps/web/components/memories.tsx b/apps/web/components/memories.tsx
index 42e70fba..161d4f14 100644
--- a/apps/web/components/memories.tsx
+++ b/apps/web/components/memories.tsx
@@ -1,3 +1,4 @@
+// apps/web/components/memories.tsx
"use client"
import { useAuth } from "@lib/auth-context"
@@ -6,12 +7,10 @@ import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
import { useInfiniteQuery } from "@tanstack/react-query"
import { useCallback, useEffect, useMemo, useState } from "react"
import type { z } from "zod"
-import { MemoryGraph } from "@repo/ui/memory-graph"
-import { Dialog, DialogContent } from "@repo/ui/components/dialog"
import { ConnectAIModal } from "@/components/connect-ai-modal"
import { MasonryMemoryList } from "@/components/masonry-memory-list"
import { AddMemoryView } from "@/components/views/add-memory"
-import { useChatOpen, useProject, useGraphModal } from "@/stores"
+import { useChatOpen, useProject } from "@/stores"
import { useGraphHighlights } from "@/stores/highlights"
import { useIsMobile } from "@hooks/use-mobile"
@@ -23,8 +22,6 @@ export function Memories() {
const { documentIds: allHighlightDocumentIds } = useGraphHighlights()
const { selectedProject } = useProject()
const { isOpen } = useChatOpen()
- const { isOpen: showGraphModal, setIsOpen: setShowGraphModal } =
- useGraphModal()
const [injectedDocs, setInjectedDocs] = useState<DocumentWithMemories[]>([])
const [showAddMemoryView, setShowAddMemoryView] = useState(false)
const [showConnectAIModal, setShowConnectAIModal] = useState(false)
@@ -243,74 +240,6 @@ export function Memories() {
/>
)}
</div>
-
- {/* Memory Graph Modal */}
- <Dialog open={showGraphModal} onOpenChange={setShowGraphModal}>
- <DialogContent
- className="w-[95vw] h-[95vh] p-0 max-w-6xl sm:max-w-6xl"
- showCloseButton={true}
- >
- <div className="w-full h-full">
- <MemoryGraph
- documents={allDocuments}
- error={error}
- hasMore={hasMore}
- isLoading={isPending}
- isLoadingMore={isLoadingMore}
- loadMoreDocuments={loadMoreDocuments}
- totalLoaded={totalLoaded}
- variant="console"
- showSpacesSelector={true}
- highlightDocumentIds={allHighlightDocumentIds}
- highlightsVisible={isOpen}
- >
- <div className="absolute inset-0 flex items-center justify-center">
- {!isMobile ? (
- <ConnectAIModal
- onOpenChange={setShowConnectAIModal}
- open={showConnectAIModal}
- >
- <div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
- <div className="relative z-10 text-slate-200 text-center">
- <div className="flex flex-col gap-3">
- <button
- className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline"
- onClick={(e) => {
- e.stopPropagation()
- setShowAddMemoryView(true)
- setShowConnectAIModal(false)
- }}
- type="button"
- >
- Add your first memory
- </button>
- </div>
- </div>
- </div>
- </ConnectAIModal>
- ) : (
- <div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6">
- <div className="relative z-10 text-slate-200 text-center">
- <div className="flex flex-col gap-3">
- <button
- className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline"
- onClick={(e) => {
- e.stopPropagation()
- setShowAddMemoryView(true)
- }}
- type="button"
- >
- Add your first memory
- </button>
- </div>
- </div>
- </div>
- )}
- </div>
- </MemoryGraph>
- </div>
- </DialogContent>
- </Dialog>
</>
)
-}
+} \ No newline at end of file