diff options
| author | MaheshtheDev <[email protected]> | 2025-09-23 23:25:34 +0000 |
|---|---|---|
| committer | MaheshtheDev <[email protected]> | 2025-09-23 23:25:34 +0000 |
| commit | 3c4045ba8948c648c4db22cce9952fe9e9e7b427 (patch) | |
| tree | 60a2bff2cdb973130f735ca5b58be7b05ee1fc6e /apps | |
| parent | Update ADD_MEMORY_SHORTCUT_URL to new URL (#434) (diff) | |
| download | supermemory-3c4045ba8948c648c4db22cce9952fe9e9e7b427.tar.xz supermemory-3c4045ba8948c648c4db22cce9952fe9e9e7b427.zip | |
feat: add one click install to cursor on connect to ai modal (#436)
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/web/components/connect-ai-modal.tsx | 215 |
1 files changed, 176 insertions, 39 deletions
diff --git a/apps/web/components/connect-ai-modal.tsx b/apps/web/components/connect-ai-modal.tsx index 6500ba23..2d29dd55 100644 --- a/apps/web/components/connect-ai-modal.tsx +++ b/apps/web/components/connect-ai-modal.tsx @@ -27,6 +27,7 @@ import { useEffect, useState } from "react" import { toast } from "sonner" import { z } from "zod/v4" import { analytics } from "@/lib/analytics" +import { cn } from "@lib/utils" const clients = { cursor: "Cursor", @@ -79,6 +80,9 @@ export function ConnectAIModal({ const setIsOpen = onOpenChange || setInternalIsOpen const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false) const [selectedProject, setSelectedProject] = useState<string | null>("none") + const [cursorInstallTab, setCursorInstallTab] = useState< + "oneClick" | "manual" + >("oneClick") const projectId = localStorage.getItem("selectedProject") ?? "default" useEffect(() => { @@ -164,6 +168,10 @@ export function ConnectAIModal({ return command } + function getCursorDeeplink() { + return "https://cursor.com/en/install-mcp?name=supermemory-mcp&config=eyJjb21tYW5kIjoibnB4IC15IG1jcC1yZW1vdGUgaHR0cHM6Ly9hcGkuc3VwZXJtZW1vcnkuYWkvbWNwIn0%3D" + } + const copyToClipboard = () => { const command = generateInstallCommand() navigator.clipboard.writeText(command) @@ -252,21 +260,142 @@ export function ConnectAIModal({ </div> </div> - {/* Step 2: Project Selection or MCP URL */} + {/* Step 2: One-click Install for Cursor, Project Selection for others, or MCP URL */} {selectedClient && ( <div className="space-y-4"> - <div className="flex items-center gap-3"> - <div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium"> - 2 + <div className="flex justify-between"> + <div className="flex items-center gap-3"> + <div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium"> + 2 + </div> + <h3 className="text-sm font-medium"> + {selectedClient === "cursor" + ? "Install Supermemory MCP" + : selectedClient === "mcp-url" + ? "MCP Server URL" + : "Select Target Project (Optional)"} + </h3> + </div> + + <div + className={cn( + "flex-col gap-2 hidden", + selectedClient === "cursor" && "flex", + )} + > + {/* Tabs */} + <div className="flex justify-end"> + <div className="flex bg-white/5 rounded-full p-1 border border-white/10"> + <button + className={`px-3 py-1.5 text-xs font-medium rounded-full transition-all ${ + cursorInstallTab === "oneClick" + ? "bg-white/10 text-white border border-white/20" + : "text-white/60 hover:text-white/80" + }`} + onClick={() => setCursorInstallTab("oneClick")} + type="button" + > + One Click Install + </button> + <button + className={`px-3 py-1.5 text-xs font-medium rounded-full transition-all ${ + cursorInstallTab === "manual" + ? "bg-white/10 text-white border border-white/20" + : "text-white/60 hover:text-white/80" + }`} + onClick={() => setCursorInstallTab("manual")} + type="button" + > + Manual Install + </button> + </div> + </div> </div> - <h3 className="text-sm font-medium"> - {selectedClient === "mcp-url" - ? "MCP Server URL" - : "Select Target Project (Optional)"} - </h3> </div> - {selectedClient === "mcp-url" ? ( + {selectedClient === "cursor" ? ( + <div className="space-y-4"> + {/* Tab Content */} + {cursorInstallTab === "oneClick" ? ( + <div className="space-y-4"> + <div className="flex flex-col items-center gap-4 p-6 border border-green-500/20 rounded-lg bg-green-500/5"> + <div className="text-center"> + <p className="text-sm text-white/80 mb-2"> + Click the button below to automatically install and + configure Supermemory in Cursor + </p> + <p className="text-xs text-white/50"> + This will install the MCP server without any + additional setup required + </p> + </div> + <a + href={getCursorDeeplink()} + onClick={() => { + analytics.mcpInstallCmdCopied() + toast.success("Opening Cursor installer...") + }} + > + <img + alt="Add Supermemory MCP server to Cursor" + className="hover:opacity-80 transition-opacity cursor-pointer" + height="40" + src="https://cursor.com/deeplink/mcp-install-dark.svg" + /> + </a> + </div> + <p className="text-xs text-white/40 text-center"> + Make sure you have Cursor installed on your system + </p> + </div> + ) : ( + <div className="space-y-4"> + <p className="text-sm text-white/70"> + Choose a project and follow the installation steps below + </p> + <div className="max-w-md"> + <Select + disabled={isLoadingProjects} + onValueChange={setSelectedProject} + value={selectedProject || "none"} + > + <SelectTrigger className="w-full"> + <SelectValue placeholder="Select project" /> + </SelectTrigger> + <SelectContent className="bg-black/90 backdrop-blur-xl border-white/10"> + <SelectItem + className="text-white hover:bg-white/10" + value="none" + > + Auto-select project + </SelectItem> + <SelectItem + className="text-white hover:bg-white/10" + value="sm_project_default" + > + Default Project + </SelectItem> + {projects + .filter( + (p: Project) => + p.containerTag !== "sm_project_default", + ) + .map((project: Project) => ( + <SelectItem + className="text-white hover:bg-white/10" + key={project.id} + value={project.containerTag} + > + {project.name} + </SelectItem> + ))} + </SelectContent> + </Select> + </div> + </div> + )} + </div> + ) : selectedClient === "mcp-url" ? ( <div className="space-y-2"> <div className="relative"> <Input @@ -336,39 +465,47 @@ export function ConnectAIModal({ </div> )} - {/* Step 3: Command Line */} - {selectedClient && selectedClient !== "mcp-url" && ( - <div className="space-y-4"> - <div className="flex items-center gap-3"> - <div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium"> - 3 + {/* Step 3: Command Line - Show for manual installation or non-cursor clients */} + {selectedClient && + selectedClient !== "mcp-url" && + (selectedClient !== "cursor" || cursorInstallTab === "manual") && ( + <div className="space-y-4"> + <div className="flex items-center gap-3"> + <div className="w-8 h-8 rounded-full bg-white/10 text-white/60 flex items-center justify-center text-sm font-medium"> + 3 + </div> + <h3 className="text-sm font-medium"> + {selectedClient === "cursor" && + cursorInstallTab === "manual" + ? "Manual Installation Command" + : "Installation Command"} + </h3> </div> - <h3 className="text-sm font-medium">Installation Command</h3> - </div> - <div className="relative"> - <Input - className="font-mono text-xs w-full pr-10" - readOnly - value={generateInstallCommand()} - /> - <Button - className="absolute top-[-1px] right-0 cursor-pointer" - onClick={copyToClipboard} - variant="ghost" - > - <CopyIcon className="size-4" /> - </Button> - </div> + <div className="relative"> + <Input + className="font-mono text-xs w-full pr-10" + readOnly + value={generateInstallCommand()} + /> + <Button + className="absolute top-[-1px] right-0 cursor-pointer" + onClick={copyToClipboard} + variant="ghost" + > + <CopyIcon className="size-4" /> + </Button> + </div> - <p className="text-xs text-white/50"> - Copy and run this command in your terminal to install the MCP - server - </p> - </div> - )} + <p className="text-xs text-white/50"> + {selectedClient === "cursor" && cursorInstallTab === "manual" + ? "Copy and run this command in your terminal for manual installation (or switch to the one-click option above)" + : "Copy and run this command in your terminal to install the MCP server"} + </p> + </div> + )} - {/* Blurred Command Placeholder */} + {/* Blurred Command Placeholder - Only show when no client selected */} {!selectedClient && ( <div className="space-y-4"> <div className="flex items-center gap-3"> |