mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
feat: redesign MCP connection flow with step-based UI and v1 migration (#399)
[Screen Recording 2025-08-31 at 12.03.49 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/user-attachments/thumbnails/f6c88b1f-9f16-47aa-8d5f-6cde61b61e4f.mov" />](https://app.graphite.dev/user-attachments/video/f6c88b1f-9f16-47aa-8d5f-6cde61b61e4f.mov)
This commit is contained in:
parent
dedd2fe612
commit
3157b987fc
2 changed files with 422 additions and 145 deletions
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useIsMobile } from "@hooks/use-mobile";
|
||||
import { $fetch } from "@lib/api";
|
||||
import { useForm } from "@tanstack/react-form";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { Button } from "@ui/components/button";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -19,10 +21,12 @@ import {
|
|||
SelectValue,
|
||||
} from "@ui/components/select";
|
||||
import { CopyableCell } from "@ui/copyable-cell";
|
||||
import { CopyIcon, ExternalLink } from "lucide-react";
|
||||
import { CopyIcon, ExternalLink, Loader2 } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod/v4";
|
||||
import { analytics } from "@/lib/analytics";
|
||||
|
||||
const clients = {
|
||||
cursor: "Cursor",
|
||||
|
|
@ -36,24 +40,138 @@ const clients = {
|
|||
enconvo: "Enconvo",
|
||||
} as const;
|
||||
|
||||
interface ConnectAIModalProps {
|
||||
children: React.ReactNode;
|
||||
const mcpMigrationSchema = z.object({
|
||||
url: z
|
||||
.string()
|
||||
.min(1, "MCP Link is required")
|
||||
.regex(
|
||||
/^https:\/\/mcp\.supermemory\.ai\/[^/]+\/sse$/,
|
||||
"Link must be in format: https://mcp.supermemory.ai/userId/sse",
|
||||
),
|
||||
});
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
containerTag: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
isExperimental?: boolean;
|
||||
}
|
||||
|
||||
export function ConnectAIModal({ children }: ConnectAIModalProps) {
|
||||
const [client, setClient] = useState<keyof typeof clients>("cursor");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [showAllTools, setShowAllTools] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
const installCommand = `npx -y install-mcp@latest https://api.supermemory.ai/mcp --client ${client} --oauth=yes`;
|
||||
interface ConnectAIModalProps {
|
||||
children: React.ReactNode;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function ConnectAIModal({
|
||||
children,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: ConnectAIModalProps) {
|
||||
const [selectedClient, setSelectedClient] = useState<
|
||||
keyof typeof clients | null
|
||||
>(null);
|
||||
const [internalIsOpen, setInternalIsOpen] = useState(false);
|
||||
const isOpen = open !== undefined ? open : internalIsOpen;
|
||||
const setIsOpen = onOpenChange || setInternalIsOpen;
|
||||
const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false);
|
||||
const [selectedProject, setSelectedProject] = useState<string | null>("none");
|
||||
const projectId = localStorage.getItem("selectedProject") ?? "default";
|
||||
|
||||
useEffect(() => {
|
||||
analytics.mcpViewOpened();
|
||||
}, []);
|
||||
|
||||
const { data: projects = [], isLoading: isLoadingProjects } = useQuery({
|
||||
queryKey: ["projects"],
|
||||
queryFn: async () => {
|
||||
const response = await $fetch("@get/projects");
|
||||
if (response.error) {
|
||||
throw new Error(response.error?.message || "Failed to load projects");
|
||||
}
|
||||
return response.data?.projects || [];
|
||||
},
|
||||
staleTime: 30 * 1000,
|
||||
});
|
||||
|
||||
const mcpMigrationForm = useForm({
|
||||
defaultValues: { url: "" },
|
||||
onSubmit: async ({ value, formApi }) => {
|
||||
const userId = extractUserIdFromMCPUrl(value.url);
|
||||
if (userId) {
|
||||
migrateMCPMutation.mutate({ userId, projectId });
|
||||
formApi.reset();
|
||||
}
|
||||
},
|
||||
validators: {
|
||||
onChange: mcpMigrationSchema,
|
||||
},
|
||||
});
|
||||
|
||||
const extractUserIdFromMCPUrl = (url: string): string | null => {
|
||||
const regex = /^https:\/\/mcp\.supermemory\.ai\/([^/]+)\/sse$/;
|
||||
const match = url.trim().match(regex);
|
||||
return match?.[1] || null;
|
||||
};
|
||||
|
||||
const migrateMCPMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
userId,
|
||||
projectId,
|
||||
}: {
|
||||
userId: string;
|
||||
projectId: string;
|
||||
}) => {
|
||||
const response = await $fetch("@post/memories/migrate-mcp", {
|
||||
body: { userId, projectId },
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(
|
||||
response.error?.message || "Failed to migrate documents",
|
||||
);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
toast.success("Migration completed!", {
|
||||
description: `Successfully migrated ${data?.migratedCount} documents`,
|
||||
});
|
||||
setIsMigrateDialogOpen(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error("Migration failed", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function generateInstallCommand() {
|
||||
if (!selectedClient) return "";
|
||||
|
||||
let command = `npx -y install-mcp@latest https://api.supermemory.ai/mcp --client ${selectedClient} --oauth=yes`;
|
||||
|
||||
if (selectedProject && selectedProject !== "none") {
|
||||
// Remove the "sm_project_" prefix from the containerTag
|
||||
const projectIdForCommand = selectedProject.replace(/^sm_project_/, "");
|
||||
command += ` --project ${projectIdForCommand}`;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(installCommand);
|
||||
const command = generateInstallCommand();
|
||||
navigator.clipboard.writeText(command);
|
||||
analytics.mcpInstallCmdCopied();
|
||||
toast.success("Copied to clipboard!");
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<Dialog onOpenChange={setIsOpen} open={isOpen}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-4xl">
|
||||
<DialogHeader>
|
||||
|
|
@ -65,144 +183,194 @@ export function ConnectAIModal({ children }: ConnectAIModalProps) {
|
|||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="mb-6 block md:hidden">
|
||||
<label
|
||||
className="text-sm font-medium text-white/80 block mb-2"
|
||||
htmlFor="mcp-server-url"
|
||||
>
|
||||
MCP Server URL
|
||||
</label>
|
||||
<div className="p-3 bg-white/5 rounded border border-white/10">
|
||||
<CopyableCell
|
||||
className="font-mono text-sm text-blue-400"
|
||||
value="https://api.supermemory.ai/mcp"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-white/50 mt-2">
|
||||
Click URL to copy to clipboard. Use this URL to configure
|
||||
supermemory in your AI assistant.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="hidden md:block">
|
||||
<h3 className="text-sm font-medium mb-3">Supported AI Tools</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 text-sm">
|
||||
{/* Step 1: Client Selection */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${"bg-white/10 text-white/60"}`}
|
||||
>
|
||||
1
|
||||
</div>
|
||||
<h3 className="text-sm font-medium">Select Your AI Client</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-x-2">
|
||||
{Object.entries(clients)
|
||||
.slice(0, showAllTools ? undefined : isMobile ? 4 : 6)
|
||||
.slice(0, 6)
|
||||
.map(([key, clientName]) => (
|
||||
<div
|
||||
key={clientName}
|
||||
className="flex items-center gap-3 px-3 py-3 bg-muted rounded-md"
|
||||
<button
|
||||
className={`pr-3 pl-1 rounded-full border cursor-pointer transition-all ${
|
||||
selectedClient === key
|
||||
? "border-blue-500 bg-blue-500/10"
|
||||
: "border-white/10 hover:border-white/20 hover:bg-white/5"
|
||||
}`}
|
||||
key={key}
|
||||
onClick={() =>
|
||||
setSelectedClient(key as keyof typeof clients)
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<div className="w-8 h-8 relative flex-shrink-0 flex items-center justify-center">
|
||||
<Image
|
||||
src={`/mcp-supported-tools/${key == "claude-code" ? "claude" : key}.png`}
|
||||
alt={clientName}
|
||||
width={isMobile ? 20 : 32}
|
||||
height={isMobile ? 20 : 32}
|
||||
className={"rounded object-contain"}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
const parent = target.parentElement;
|
||||
if (
|
||||
parent &&
|
||||
!parent.querySelector(".fallback-text")
|
||||
) {
|
||||
const fallback = document.createElement("span");
|
||||
fallback.className =
|
||||
"fallback-text text-xs font-bold text-muted-foreground";
|
||||
fallback.textContent = clientName
|
||||
.substring(0, 2)
|
||||
.toUpperCase();
|
||||
parent.appendChild(fallback);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-8 h-8 flex items-center justify-center">
|
||||
<Image
|
||||
alt={clientName}
|
||||
className="rounded object-contain"
|
||||
height={20}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
const parent = target.parentElement;
|
||||
if (
|
||||
parent &&
|
||||
!parent.querySelector(".fallback-text")
|
||||
) {
|
||||
const fallback = document.createElement("span");
|
||||
fallback.className =
|
||||
"fallback-text text-sm font-bold text-white/40";
|
||||
fallback.textContent = clientName
|
||||
.substring(0, 2)
|
||||
.toUpperCase();
|
||||
parent.appendChild(fallback);
|
||||
}
|
||||
}}
|
||||
src={`/mcp-supported-tools/${key === "claude-code" ? "claude" : key}.png`}
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-white/80">
|
||||
{clientName}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">{clientName}</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{Object.entries(clients).length > 6 && (
|
||||
<div className="mt-3 text-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowAllTools(!showAllTools)}
|
||||
className="text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{showAllTools ? "Show Less" : `Show All`}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="hidden md:block">
|
||||
<h3 className="text-sm font-medium mb-3">Quick Installation</h3>
|
||||
<div className="space-y-3 flex gap-2 items-center justify-between">
|
||||
<Select
|
||||
value={client}
|
||||
onValueChange={(value) =>
|
||||
setClient(value as keyof typeof clients)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-48 mb-0">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(clients).map(([key, value]) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 relative flex-shrink-0 flex items-center justify-center">
|
||||
<Image
|
||||
src={`/mcp-supported-tools/${key == "claude-code" ? "claude" : key}.png`}
|
||||
alt={value}
|
||||
width={16}
|
||||
height={16}
|
||||
className="rounded object-contain"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
const parent = target.parentElement;
|
||||
if (
|
||||
parent &&
|
||||
!parent.querySelector(".fallback-text")
|
||||
) {
|
||||
const fallback = document.createElement("span");
|
||||
fallback.className =
|
||||
"fallback-text text-xs font-bold text-muted-foreground";
|
||||
fallback.textContent = value
|
||||
.substring(0, 1)
|
||||
.toUpperCase();
|
||||
parent.appendChild(fallback);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{value}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{/* Step 2: Project Selection */}
|
||||
{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>
|
||||
<h3 className="text-sm font-medium">
|
||||
Select Target Project (Optional)
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full flex items-center">
|
||||
<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>
|
||||
)}
|
||||
|
||||
{/* Step 3: Command Line */}
|
||||
{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">
|
||||
3
|
||||
</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={installCommand}
|
||||
value={generateInstallCommand()}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="absolute top-[-1px] right-0 cursor-pointer"
|
||||
onClick={copyToClipboard}
|
||||
className="absolute right-0 cursor-pointer"
|
||||
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>
|
||||
)}
|
||||
|
||||
{/* Blurred Command Placeholder */}
|
||||
{!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">
|
||||
3
|
||||
</div>
|
||||
<h3 className="text-sm font-medium">Installation Command</h3>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="w-full h-10 bg-white/5 border border-white/10 rounded-md flex items-center px-3">
|
||||
<div className="w-full h-4 bg-white/20 rounded animate-pulse blur-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-white/30">
|
||||
Select a client above to see the installation command
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="gap-2 hidden">
|
||||
<div>
|
||||
<label
|
||||
className="text-sm font-medium text-white/80 block mb-2"
|
||||
htmlFor="mcp-server-url-desktop"
|
||||
>
|
||||
MCP Server URL
|
||||
</label>
|
||||
<p className="text-xs text-white/50 mt-2">
|
||||
Use this URL to configure supermemory in your AI assistant
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-1 bg-white/5 rounded-lg border border-white/10 items-center flex px-2">
|
||||
<CopyableCell
|
||||
className="font-mono text-xs text-blue-400"
|
||||
value="https://api.supermemory.ai/mcp"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -217,22 +385,120 @@ export function ConnectAIModal({ children }: ConnectAIModalProps) {
|
|||
</div>
|
||||
|
||||
<div className="flex justify-between items-center pt-4 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
"https://docs.supermemory.ai/supermemory-mcp/introduction",
|
||||
"_blank",
|
||||
)
|
||||
}
|
||||
>
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
Learn More
|
||||
</Button>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
onClick={() =>
|
||||
window.open(
|
||||
"https://docs.supermemory.ai/supermemory-mcp/introduction",
|
||||
"_blank",
|
||||
)
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
<ExternalLink className="w-2 h-2 mr-2" />
|
||||
Learn More
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => setIsMigrateDialogOpen(true)}
|
||||
variant="outline"
|
||||
>
|
||||
Migrate from v1
|
||||
</Button>
|
||||
</div>
|
||||
<Button onClick={() => setIsOpen(false)}>Done</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
{/* Migration Dialog */}
|
||||
{isMigrateDialogOpen && (
|
||||
<Dialog
|
||||
onOpenChange={setIsMigrateDialogOpen}
|
||||
open={isMigrateDialogOpen}
|
||||
>
|
||||
<DialogContent className="sm:max-w-2xl bg-black/90 backdrop-blur-xl border-white/10 text-white">
|
||||
<div>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Migrate from MCP v1</DialogTitle>
|
||||
<DialogDescription className="text-white/60">
|
||||
Migrate your MCP documents from the legacy system.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
mcpMigrationForm.handleSubmit();
|
||||
}}
|
||||
>
|
||||
<div className="grid gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium" htmlFor="mcpUrl">
|
||||
MCP Link
|
||||
</label>
|
||||
<mcpMigrationForm.Field name="url">
|
||||
{({ state, handleChange, handleBlur }) => (
|
||||
<>
|
||||
<Input
|
||||
className="bg-white/5 border-white/10 text-white"
|
||||
id="mcpUrl"
|
||||
onBlur={handleBlur}
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
placeholder="https://mcp.supermemory.ai/your-user-id/sse"
|
||||
value={state.value}
|
||||
/>
|
||||
{state.meta.errors.length > 0 && (
|
||||
<p className="text-sm text-red-400 mt-1">
|
||||
{state.meta.errors.join(", ")}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</mcpMigrationForm.Field>
|
||||
<p className="text-xs text-white/50">
|
||||
Enter your old MCP Link in the format: <br />
|
||||
<span className="font-mono">
|
||||
https://mcp.supermemory.ai/userId/sse
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 mt-4">
|
||||
<Button
|
||||
className="bg-white/5 hover:bg-white/10 border-white/10 text-white"
|
||||
onClick={() => {
|
||||
setIsMigrateDialogOpen(false);
|
||||
mcpMigrationForm.reset();
|
||||
}}
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
|
||||
disabled={
|
||||
migrateMCPMutation.isPending ||
|
||||
!mcpMigrationForm.state.canSubmit
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
{migrateMCPMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||||
Migrating...
|
||||
</>
|
||||
) : (
|
||||
"Migrate"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
fetchMemoriesFeature,
|
||||
} from "@repo/lib/queries";
|
||||
import { Button } from "@repo/ui/components/button";
|
||||
import { ConnectAIModal } from "./connect-ai-modal";
|
||||
import { HeadingH2Bold } from "@repo/ui/text/heading/heading-h2-bold";
|
||||
import { GlassMenuEffect } from "@ui/other/glass-effect";
|
||||
import { useCustomer } from "autumn-js/react";
|
||||
|
|
@ -20,7 +21,6 @@ import { ProjectSelector } from "./project-selector";
|
|||
import { useTour } from "./tour";
|
||||
import { AddMemoryExpandedView, AddMemoryView } from "./views/add-memory";
|
||||
import { IntegrationsView } from "./views/integrations";
|
||||
import { MCPView } from "./views/mcp";
|
||||
import { ProfileView } from "./views/profile";
|
||||
|
||||
const MCPIcon = ({ className }: { className?: string }) => {
|
||||
|
|
@ -47,6 +47,7 @@ function Menu({ id }: { id?: string }) {
|
|||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [isCollapsing, setIsCollapsing] = useState(false);
|
||||
const [showAddMemoryView, setShowAddMemoryView] = useState(false);
|
||||
const [showConnectAIModal, setShowConnectAIModal] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
const { activePanel, setActivePanel } = useMobilePanel();
|
||||
const { setMenuExpanded } = useTour();
|
||||
|
|
@ -125,6 +126,11 @@ function Menu({ id }: { id?: string }) {
|
|||
if (isMobile) {
|
||||
setActivePanel("chat");
|
||||
}
|
||||
} else if (key === "mcp") {
|
||||
// Open ConnectAIModal directly for MCP
|
||||
setIsMobileMenuOpen(false);
|
||||
setExpandedView(null);
|
||||
setShowConnectAIModal(true);
|
||||
} else {
|
||||
if (expandedView === key) {
|
||||
setIsCollapsing(true);
|
||||
|
|
@ -392,7 +398,6 @@ function Menu({ id }: { id?: string }) {
|
|||
ease: [0.4, 0, 0.2, 1],
|
||||
}}
|
||||
>
|
||||
{expandedView === "mcp" && <MCPView />}
|
||||
{expandedView === "profile" && <ProfileView />}
|
||||
{expandedView === "integrations" && (
|
||||
<IntegrationsView />
|
||||
|
|
@ -608,7 +613,6 @@ function Menu({ id }: { id?: string }) {
|
|||
{expandedView === "addUrl" && (
|
||||
<AddMemoryExpandedView />
|
||||
)}
|
||||
{expandedView === "mcp" && <MCPView />}
|
||||
{expandedView === "profile" && <ProfileView />}
|
||||
{expandedView === "integrations" && (
|
||||
<IntegrationsView />
|
||||
|
|
@ -631,6 +635,13 @@ function Menu({ id }: { id?: string }) {
|
|||
onClose={() => setShowAddMemoryView(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ConnectAIModal
|
||||
onOpenChange={setShowConnectAIModal}
|
||||
open={showConnectAIModal}
|
||||
>
|
||||
<Button className="hidden">Connect AI Assistant</Button>
|
||||
</ConnectAIModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue