diff --git a/apps/web/components/get-started.tsx b/apps/web/components/get-started.tsx new file mode 100644 index 00000000..1619ca2e --- /dev/null +++ b/apps/web/components/get-started.tsx @@ -0,0 +1,362 @@ +import { AnimatePresence, motion } from "framer-motion" +import { Brain, Chrome, ExternalLink, Plus } from "lucide-react" +import { useEffect, useState } from "react" +import { ConnectAIModal } from "@/components/connect-ai-modal" +import { analytics } from "@/lib/analytics" + +interface GetStartedProps { + setShowAddMemoryView?: (show: boolean) => void +} + +export const GetStarted = ({ setShowAddMemoryView }: GetStartedProps) => { + const [imageLoaded, setImageLoaded] = useState(false) + const [showContent, setShowContent] = useState(false) + const [showConnectAIModal, setShowConnectAIModal] = useState(false) + + useEffect(() => { + const img = new Image() + img.onload = () => { + setImageLoaded(true) + setTimeout(() => setShowContent(true), 100) + } + img.src = "/images/onboarding.png" + }, []) + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + duration: 0.8, + ease: [0.25, 0.1, 0.25, 1] as const, + staggerChildren: 0.15, + delayChildren: 0.2, + }, + }, + } + + const leftColumnVariants = { + hidden: { + opacity: 0, + x: -40, + }, + visible: { + opacity: 1, + x: 0, + transition: { + duration: 1, + ease: [0.25, 0.1, 0.25, 1] as const, + staggerChildren: 0.1, + }, + }, + } + + const rightColumnVariants = { + hidden: { + opacity: 0, + x: 40, + }, + visible: { + opacity: 1, + x: 0, + transition: { + duration: 0.8, + ease: [0.25, 0.1, 0.25, 1] as const, + delay: 0.6, + staggerChildren: 0.1, + }, + }, + } + + const textLineVariants = { + hidden: { + opacity: 0, + y: 30, + }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.8, + ease: [0.25, 0.1, 0.25, 1] as const, + }, + }, + } + + const cardVariants = { + hidden: { + opacity: 0, + y: 20, + scale: 0.95, + }, + visible: { + opacity: 1, + y: 0, + scale: 1, + transition: { + duration: 0.6, + ease: [0.25, 0.1, 0.25, 1] as const, + }, + }, + } + + const backgroundVariants = { + hidden: { + scale: 1.1, + opacity: 0, + }, + visible: { + scale: 1, + opacity: 1, + transition: { + duration: 1.5, + ease: [0.25, 0.1, 0.25, 1] as const, + }, + }, + } + + const handleChromeExtension = () => { + analytics.extensionInstallClicked() + window.open( + "https://chromewebstore.google.com/detail/supermemory/afpgkkipfdpeaflnpoaffkcankadgjfc", + "_blank", + ) + } + + const handleAddMemory = () => { + setShowConnectAIModal(false) + if (setShowAddMemoryView) { + setShowAddMemoryView(true) + } + } + + return ( +
+ + {!imageLoaded && ( + + + SUPERMEMORY + + + )} + + + {imageLoaded && ( + <> + + + + + + {showContent && ( + + {/* Left Column - Quote */} + + + + Intelligence without memory + + + + + + is just sophisticated + + + + + + randomness + + + + + {/* Right Column - Actions */} + + {/* Mobile Chrome Extension Link */} + + Download Chrome extension to use with ChatGPT + + +
+ {/* Chrome Extension Card - Hidden on mobile */} + + +
+

+ Add to Chrome +

+

+ Save items and use supermemory with ChatGPT and other + apps. Import your ChatGPT memories, twitter bookmarks, + and other data. +

+
+ +
+ + {/* Connect AI Card */} + + + +
+

+ Connect to AI +

+

+ Set up your AI connection for intelligent assistance +

+
+
+
+ + {/* Add Memory Card */} + + +
+

+ Add Memory +

+

+ Start building your knowledge base +

+
+
+
+
+
+ )} +
+ + )} +
+ ) +} diff --git a/apps/web/components/views/add-memory/fixed-mutation.tsx b/apps/web/components/views/add-memory/fixed-mutation.tsx new file mode 100644 index 00000000..c71793b5 --- /dev/null +++ b/apps/web/components/views/add-memory/fixed-mutation.tsx @@ -0,0 +1,244 @@ +import { $fetch } from "@lib/api" +import { useMutation } from "@tanstack/react-query" +import { toast } from "sonner" + +// Simplified mutation that doesn't block UI with polling +export const createSimpleAddContentMutation = ( + queryClient: any, + onClose?: () => void, +) => { + return useMutation({ + mutationFn: async ({ + content, + project, + contentType, + }: { + content: string + project: string + contentType: "note" | "link" + }) => { + // Just create the memory, don't wait for processing + const response = await $fetch("@post/documents", { + body: { + content: content, + containerTags: [project], + metadata: { + sm_source: "consumer", + }, + }, + }) + + if (response.error) { + throw new Error( + response.error?.message || `Failed to add ${contentType}`, + ) + } + + return { id: response.data.id, contentType } + }, + onMutate: async ({ content, project, contentType }) => { + // Cancel any outgoing refetches + await queryClient.cancelQueries({ + queryKey: ["documents-with-memories", project], + }) + + // Snapshot the previous value + const previousMemories = queryClient.getQueryData([ + "documents-with-memories", + project, + ]) + + // Create optimistic memory + const tempId = `temp-${Date.now()}` + const optimisticMemory = { + id: tempId, + content: contentType === "link" ? "" : content, + url: contentType === "link" ? content : null, + title: + contentType === "link" ? "Processing..." : content.substring(0, 100), + description: + contentType === "link" + ? "Extracting content..." + : "Processing content...", + containerTags: [project], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + status: "processing", + type: contentType, + metadata: { + processingStage: "queued", + processingMessage: "Added to processing queue", + }, + memoryEntries: [], + isOptimistic: true, + } + + // Optimistically update to include the new memory + queryClient.setQueryData( + ["documents-with-memories", project], + (old: any) => { + // Handle infinite query structure + if (old?.pages) { + const newPages = [...old.pages] + if (newPages.length > 0) { + // Add to the first page + const firstPage = { ...newPages[0] } + firstPage.documents = [ + optimisticMemory, + ...(firstPage.documents || []), + ] + newPages[0] = firstPage + } else { + // No pages yet, create the first page + newPages.push({ + documents: [optimisticMemory], + pagination: { currentPage: 1, totalPages: 1, totalCount: 1 }, + totalCount: 1, + }) + } + + return { + ...old, + pages: newPages, + } + } + // Fallback for regular query structure + const newData = old + ? { + ...old, + documents: [optimisticMemory, ...(old.documents || [])], + totalCount: (old.totalCount || 0) + 1, + } + : { documents: [optimisticMemory], totalCount: 1 } + return newData + }, + ) + + return { previousMemories, optimisticId: tempId } + }, + onError: (error, variables, context) => { + // Roll back on error + if (context?.previousMemories) { + queryClient.setQueryData( + ["documents-with-memories", variables.project], + context.previousMemories, + ) + } + toast.error(`Failed to add ${variables.contentType}`, { + description: error instanceof Error ? error.message : "Unknown error", + }) + }, + onSuccess: (data, variables, context) => { + // Show success message + toast.success( + `${variables.contentType === "link" ? "Link" : "Note"} created successfully!`, + ) + + // Close modal + onClose?.() + + // Start polling for this specific memory ID + // The polling will happen in the background and update the optimistic memory when done + startMemoryPolling( + data.id, + variables.project, + context?.optimisticId, + queryClient, + ) + }, + }) +} + +// Background polling function +const startMemoryPolling = ( + memoryId: string, + project: string, + optimisticId: string | undefined, + queryClient: any, +) => { + const pollMemory = async () => { + try { + const memory = await $fetch(`@get/documents/${memoryId}`) + + if (memory.error) { + console.error("Failed to fetch memory status:", memory.error) + return false + } + + const isComplete = + memory.data?.status === "done" || + memory.data?.content || + memory.data?.memoryEntries?.length > 0 + + if (isComplete) { + // Replace optimistic memory with real data + queryClient.setQueryData( + ["documents-with-memories", project], + (old: any) => { + if (old?.pages) { + // Handle infinite query structure + const newPages = old.pages.map((page: any) => ({ + ...page, + documents: page.documents.map((doc: any) => { + if (doc.isOptimistic || doc.id === optimisticId) { + // Replace with real memory + return { + ...memory.data, + isOptimistic: false, + } + } + return doc + }), + })) + + return { + ...old, + pages: newPages, + } + } + // Handle regular query structure + return { + ...old, + documents: old.documents.map((doc: any) => { + if (doc.isOptimistic || doc.id === optimisticId) { + return { + ...memory.data, + isOptimistic: false, + } + } + return doc + }), + } + }, + ) + return true // Stop polling + } + + return false // Continue polling + } catch (error) { + console.error("Error polling memory:", error) + return false // Continue polling + } + } + + // Poll every 3 seconds, max 60 attempts (3 minutes) + let attempts = 0 + const maxAttempts = 60 + + const poll = async () => { + if (attempts >= maxAttempts) { + console.log("Memory polling timed out") + return + } + + const isComplete = await pollMemory() + attempts++ + + if (!isComplete && attempts < maxAttempts) { + setTimeout(poll, 3000) // Poll again in 3 seconds + } + } + + // Start polling after a short delay + setTimeout(poll, 2000) +} diff --git a/apps/web/public/3d-shapes/1.png b/apps/web/public/3d-shapes/1.png new file mode 100644 index 00000000..ebf3cf58 Binary files /dev/null and b/apps/web/public/3d-shapes/1.png differ diff --git a/apps/web/public/3d-shapes/2.png b/apps/web/public/3d-shapes/2.png new file mode 100644 index 00000000..aed06509 Binary files /dev/null and b/apps/web/public/3d-shapes/2.png differ diff --git a/apps/web/public/3d-shapes/3.png b/apps/web/public/3d-shapes/3.png new file mode 100644 index 00000000..c1d6775e Binary files /dev/null and b/apps/web/public/3d-shapes/3.png differ diff --git a/apps/web/public/3d-shapes/4.png b/apps/web/public/3d-shapes/4.png new file mode 100644 index 00000000..a89c75fa Binary files /dev/null and b/apps/web/public/3d-shapes/4.png differ diff --git a/apps/web/public/3d-shapes/5.png b/apps/web/public/3d-shapes/5.png new file mode 100644 index 00000000..6eb298b1 Binary files /dev/null and b/apps/web/public/3d-shapes/5.png differ diff --git a/apps/web/public/images/onboarding.png b/apps/web/public/images/onboarding.png new file mode 100644 index 00000000..5be183fd Binary files /dev/null and b/apps/web/public/images/onboarding.png differ