diff --git a/apps/browser-extension/wxt.config.ts b/apps/browser-extension/wxt.config.ts index 0ef00bd2..7b877abd 100644 --- a/apps/browser-extension/wxt.config.ts +++ b/apps/browser-extension/wxt.config.ts @@ -15,6 +15,7 @@ export default defineConfig({ permissions: [ "contextMenus", "storage", + "scripting", "activeTab", "webRequest", "tabs", diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index e6d9094d..9f8c24a1 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -7,6 +7,7 @@ import { ErrorTrackingProvider } from "@lib/error-tracking"; import { PostHogProvider } from "@lib/posthog"; import { QueryProvider } from "@lib/query-client"; import { AutumnProvider } from "autumn-js/react"; +import { ThemeProvider } from "next-themes"; import { Suspense } from "react"; import { Toaster } from "sonner"; import { TourProvider } from "@/components/tour"; @@ -36,33 +37,35 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - - - - - - - - - {children} - - - - - - - - - + + + + + + + + + + {children} + + + + + + + + + + ); diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 5018c337..a9a79e35 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,57 +1,25 @@ "use client"; -import { useIsMobile } from "@hooks/use-mobile"; import { useAuth } from "@lib/auth-context"; import { $fetch } from "@repo/lib/api"; -import { MemoryGraph } from "@repo/ui/memory-graph"; import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"; import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; -import { Logo, LogoFull } from "@ui/assets/Logo"; -import { Button } from "@ui/components/button"; -import { GlassMenuEffect } from "@ui/other/glass-effect"; -import { - HelpCircle, - LayoutGrid, - List, - LoaderIcon, - MessageSquare, - Unplug, -} from "lucide-react"; -import { AnimatePresence, motion } from "motion/react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; +import { LoaderIcon } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import type { z } from "zod"; -import { ConnectAIModal } from "@/components/connect-ai-modal"; -import { InstallPrompt } from "@/components/install-prompt"; +import { AppHeader } from "@/components/header"; import { MemoryListView } from "@/components/memory-list-view"; -import Menu from "@/components/menu"; -import { ProjectSelector } from "@/components/project-selector"; -import { ReferralUpgradeModal } from "@/components/referral-upgrade-modal"; -import type { TourStep } from "@/components/tour"; -import { TourAlertDialog, useTour } from "@/components/tour"; +import { InstallPrompt } from "@/components/install-prompt"; import { AddMemoryView } from "@/components/views/add-memory"; -import { ChatRewrite } from "@/components/views/chat"; -import { TOUR_STEP_IDS, TOUR_STORAGE_KEY } from "@/lib/tour-constants"; -import { useViewMode } from "@/lib/view-mode-context"; -import { useChatOpen, useProject } from "@/stores"; -import { useGraphHighlights } from "@/stores/highlights"; +import { useProject } from "@/stores"; type DocumentsResponse = z.infer; type DocumentWithMemories = DocumentsResponse["documents"][0]; -const MemoryGraphPage = () => { - const { documentIds: allHighlightDocumentIds } = useGraphHighlights(); - const isMobile = useIsMobile(); - const { viewMode, setViewMode, isInitialized } = useViewMode(); +const MemoryAppPage = () => { const { selectedProject } = useProject(); - const { setSteps, isTourCompleted } = useTour(); - const { isOpen, setIsOpen } = useChatOpen(); const [injectedDocs, setInjectedDocs] = useState([]); - const [showAddMemoryView, setShowAddMemoryView] = useState(false); - const [showReferralModal, setShowReferralModal] = useState(false); - const [showConnectAIModal, setShowConnectAIModal] = useState(false); - const [isHelpHovered, setIsHelpHovered] = useState(false); + const [showAddMemoryModal, setShowAddMemoryModal] = useState(false); // Fetch projects meta to detect experimental flag const { data: projectsMeta = [] } = useQuery({ @@ -64,170 +32,9 @@ const MemoryGraphPage = () => { }); const isCurrentProjectExperimental = !!projectsMeta.find( - (p: any) => p.containerTag === selectedProject, + (p: { containerTag: string; isExperimental?: boolean }) => p.containerTag === selectedProject, )?.isExperimental; - // Tour state - const [showTourDialog, setShowTourDialog] = useState(false); - - // Define tour steps with useMemo to prevent recreation - const tourSteps: TourStep[] = useMemo(() => { - return [ - { - content: ( -
-

- Memories Overview -

-

- This is your memory graph. Each node represents a memory, and - connections show relationships between them. -

-
- ), - selectorId: TOUR_STEP_IDS.MEMORY_GRAPH, - position: "center", - }, - { - content: ( -
-

- Add Memories -

-

- Click here to add new memories to your knowledge base. You can add - text, links, or connect external sources. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_ADD_MEMORY, - position: "right", - }, - { - content: ( -
-

- Connections -

-

- Connect your external accounts like Google Drive, Notion, or - OneDrive to automatically sync and organize your content. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_CONNECTIONS, - position: "right", - }, - { - content: ( -
-

Projects

-

- Organize your memories into projects. Switch between different - contexts easily. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_PROJECTS, - position: "right", - }, - { - content: ( -
-

- MCP Servers -

-

- Access Model Context Protocol servers to give AI tools access to - your memories securely. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_MCP, - position: "right", - }, - { - content: ( -
-

Billing

-

- Manage your subscription and billing information. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_BILLING, - position: "right", - }, - { - content: ( -
-

- View Toggle -

-

- Switch between graph view and list view to see your memories in - different ways. -

-
- ), - selectorId: TOUR_STEP_IDS.VIEW_TOGGLE, - position: "left", - }, - { - content: ( -
-

Legend

-

- Understand the different types of nodes and connections in your - memory graph. -

-
- ), - selectorId: TOUR_STEP_IDS.LEGEND, - position: "left", - }, - { - content: ( -
-

- Chat Assistant -

-

- Ask questions or add new memories using our AI-powered chat - interface. -

-
- ), - selectorId: TOUR_STEP_IDS.FLOATING_CHAT, - position: "left", - }, - ]; - }, []); - - // Check if tour has been completed before - useEffect(() => { - const hasCompletedTour = localStorage.getItem(TOUR_STORAGE_KEY) === "true"; - if (!hasCompletedTour && !isTourCompleted) { - const timer = setTimeout(() => { - setShowTourDialog(true); - setShowConnectAIModal(false); - }, 1000); // Show after 1 second - return () => clearTimeout(timer); - } - }, [isTourCompleted]); - - // Set up tour steps - useEffect(() => { - setSteps(tourSteps); - }, [setSteps, tourSteps]); - - // Save tour completion to localStorage - useEffect(() => { - if (isTourCompleted) { - localStorage.setItem(TOUR_STORAGE_KEY, "true"); - } - }, [isTourCompleted]); - // Progressive loading via useInfiniteQuery const IS_DEV = process.env.NODE_ENV === "development"; const PAGE_SIZE = IS_DEV ? 100 : 100; @@ -308,88 +115,23 @@ const MemoryGraphPage = () => { setInjectedDocs([]); }, [selectedProject]); - // Surgical fetch of missing highlighted documents (customId-based IDs from search) useEffect(() => { - if (!isOpen) return; - if (!allHighlightDocumentIds || allHighlightDocumentIds.length === 0) - return; - const present = new Set(); - for (const d of [...baseDocuments, ...injectedDocs]) { - if (d.id) present.add(d.id); - if ((d as any).customId) present.add((d as any).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/memories/documents/by-ids", { - body: { - ids: missing, - by: "customId", - containerTags: selectedProject ? [selectedProject] : undefined, - }, - disableValidation: true, - }); - if (cancelled || (resp as any)?.error) return; - const extraDocs = (resp as any)?.data?.documents as - | DocumentWithMemories[] - | undefined; - if (!extraDocs || extraDocs.length === 0) return; - setInjectedDocs((prev) => { - const seen = new Set([ - ...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 {} + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'c' && !['INPUT', 'TEXTAREA'].includes((event.target as Element)?.tagName)) { + event.preventDefault(); + setShowAddMemoryModal(true); + } }; - void run(); - return () => { - cancelled = true; - }; - }, [ - isOpen, - allHighlightDocumentIds.join("|"), - baseDocuments, - injectedDocs, - selectedProject, - $fetch, - ]); - // Handle view mode change - const handleViewModeChange = useCallback( - (mode: "graph" | "list") => { - setViewMode(mode); - }, - [setViewMode], - ); + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, []); useEffect(() => { - const hasCompletedTour = localStorage.getItem(TOUR_STORAGE_KEY) === "true"; - if (hasCompletedTour && allDocuments.length === 0 && !showTourDialog) { - setShowConnectAIModal(true); - } else if (showTourDialog) { - setShowConnectAIModal(false); - } - }, [allDocuments.length, showTourDialog]); - - // Prevent body scrolling - useEffect(() => { - document.body.style.overflow = "hidden"; - document.body.style.height = "100vh"; - document.documentElement.style.overflow = "hidden"; - document.documentElement.style.height = "100vh"; + document.body.style.overflow = "auto"; + document.body.style.height = "auto"; + document.documentElement.style.overflow = "auto"; + document.documentElement.style.height = "auto"; return () => { document.body.style.overflow = ""; @@ -400,380 +142,49 @@ const MemoryGraphPage = () => { }, []); return ( -
- {/* Main content area */} - - - -
- handleViewModeChange("graph")} - transition={{ duration: 0.2 }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - > - {viewMode === "graph" && ( - - )} - - - Graph - - +
+ + + - handleViewModeChange("list")} - transition={{ duration: 0.2 }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - > - {viewMode === "list" && ( - - )} - - - List - - -
- - - {/* Animated content switching */} - - {viewMode === "graph" ? ( - - -
- -
-
-

- Get Started with supermemory -

-
-

- Click here to set up your AI connection -

-

or

- -
-
-
-
-
-
-
- ) : ( - - -
- -
-
-

- Get Started with supermemory -

-
-

- Click here to set up your AI connection -

-

or

- -
-
-
-
-
-
-
- )} -
- - {/* Top Bar */} -
-
- - - - - -
- -
- - - - -
- -
- -
-
- - {/* Floating Open Chat Button */} - {!isOpen && !isMobile && ( - - - - )} - - - - - {/* Chat panel - positioned absolutely */} - - - - - - - {showAddMemoryView && ( + {showAddMemoryModal && ( setShowAddMemoryView(false)} + onClose={() => setShowAddMemoryModal(false)} /> )} - - {/* Tour Alert Dialog */} - - - {/* Referral/Upgrade Modal */} - setShowReferralModal(false)} - />
); }; -// Wrapper component to handle auth and waitlist checks +// Wrapper component to handle auth export default function Page() { - const router = useRouter(); const { user } = useAuth(); - useEffect(() => { - // save the token for chrome extension - const url = new URL(window.location.href); - const rawToken = url.searchParams.get("token"); - - if (rawToken) { - const encodedToken = encodeURIComponent(rawToken); - window.postMessage({ token: encodedToken }, "*"); - url.searchParams.delete("token"); - window.history.replaceState({}, "", url.toString()); - } - }, []); - - // Show loading state while checking authentication and waitlist status + // Show loading state while checking authentication if (!user) { return ( -
+
-

Loading...

+

Loading...

); } - // If we have a user and they have access, show the main component return ( <> - + ); diff --git a/apps/web/components/memory-list-view.tsx b/apps/web/components/memory-list-view.tsx index 2cff96fd..d137e134 100644 --- a/apps/web/components/memory-list-view.tsx +++ b/apps/web/components/memory-list-view.tsx @@ -4,7 +4,6 @@ import { useIsMobile } from "@hooks/use-mobile"; import { cn } from "@lib/utils"; import { Badge } from "@repo/ui/components/badge"; import { Card, CardContent, CardHeader } from "@repo/ui/components/card"; -import { colors } from "@repo/ui/memory-graph/constants"; import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"; import { useVirtualizer } from "@tanstack/react-virtual"; import { @@ -20,6 +19,8 @@ import { analytics } from "@/lib/analytics"; import { MemoryDetail } from "./memories/memory-detail"; import { getDocumentIcon } from "@/lib/document-icon"; import { formatDate, getSourceUrl } from "./memories"; +import { ChatInline } from "./chat-inline"; +import { GraphPreview } from "./graph-preview"; type DocumentsResponse = z.infer; type DocumentWithMemories = DocumentsResponse["documents"][0]; @@ -33,33 +34,9 @@ interface MemoryListViewProps { totalLoaded: number; hasMore: boolean; loadMoreDocuments: () => Promise; + isCurrentProjectExperimental?: boolean; } -const GreetingMessage = memo(() => { - const getGreeting = () => { - const hour = new Date().getHours(); - if (hour < 12) return "Good morning"; - if (hour < 17) return "Good afternoon"; - return "Good evening"; - }; - - return ( -
-
-

- {getGreeting()}! -

-

- Welcome back to your memory collection -

-
-
- ); -}); - const DocumentCard = memo( ({ document, @@ -75,14 +52,11 @@ const DocumentCard = memo( return ( { analytics.documentCardClicked(); onOpenDetails(document); }} - style={{ - backgroundColor: colors.document.primary, - }} >
@@ -99,16 +73,12 @@ const DocumentCard = memo(
{document.url && ( - {isOpen && (
diff --git a/packages/ui/memory-graph/types.ts b/packages/ui/memory-graph/types.ts index 4692d2c0..8dda9500 100644 --- a/packages/ui/memory-graph/types.ts +++ b/packages/ui/memory-graph/types.ts @@ -90,6 +90,7 @@ export interface MemoryGraphProps { showSpacesSelector?: boolean; // true for console, false for consumer variant?: "console" | "consumer"; // for different positioning and styling legendId?: string; // Optional ID for the legend component + showLegend?: boolean; // Optional document highlight list (document custom IDs) highlightDocumentIds?: string[]; // Whether highlights are currently visible (e.g., chat open)