aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDhravya Shah <[email protected]>2025-09-19 16:05:12 -0700
committerDhravya Shah <[email protected]>2025-09-19 16:05:12 -0700
commitf82104ecc8835bbf61d94b12e6eff8487c2f7a8a (patch)
treed2fad0b6670b7ba3ccefe9046e1ec4edd9e05d76
parentfix: one more build error (diff)
downloadsupermemory-f82104ecc8835bbf61d94b12e6eff8487c2f7a8a.tar.xz
supermemory-f82104ecc8835bbf61d94b12e6eff8487c2f7a8a.zip
fix
-rw-r--r--apps/web/components/connect-ai-modal.tsx172
-rw-r--r--apps/web/components/create-project-dialog.tsx62
2 files changed, 119 insertions, 115 deletions
diff --git a/apps/web/components/connect-ai-modal.tsx b/apps/web/components/connect-ai-modal.tsx
index f3008796..6500ba23 100644
--- a/apps/web/components/connect-ai-modal.tsx
+++ b/apps/web/components/connect-ai-modal.tsx
@@ -1,9 +1,9 @@
-"use client";
+"use client"
-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 { $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,
DialogContent,
@@ -11,22 +11,22 @@ import {
DialogHeader,
DialogTitle,
DialogTrigger,
-} from "@ui/components/dialog";
-import { Input } from "@ui/components/input";
+} from "@ui/components/dialog"
+import { Input } from "@ui/components/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
-} from "@ui/components/select";
-import { CopyableCell } from "@ui/copyable-cell";
-import { CopyIcon, ExternalLink, Loader2 } from "lucide-react";
-import Image from "next/image";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import { z } from "zod/v4";
-import { analytics } from "@/lib/analytics";
+} from "@ui/components/select"
+import { CopyableCell } from "@ui/copyable-cell"
+import { CopyIcon, ExternalLink, Loader2 } from "lucide-react"
+import Image from "next/image"
+import { useEffect, useState } from "react"
+import { toast } from "sonner"
+import { z } from "zod/v4"
+import { analytics } from "@/lib/analytics"
const clients = {
cursor: "Cursor",
@@ -39,7 +39,7 @@ const clients = {
"roo-cline": "Roo Cline",
witsy: "Witsy",
enconvo: "Enconvo",
-} as const;
+} as const
const mcpMigrationSchema = z.object({
url: z
@@ -49,21 +49,21 @@ const mcpMigrationSchema = z.object({
/^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;
+ id: string
+ name: string
+ containerTag: string
+ createdAt: string
+ updatedAt: string
+ isExperimental?: boolean
}
interface ConnectAIModalProps {
- children: React.ReactNode;
- open?: boolean;
- onOpenChange?: (open: boolean) => void;
+ children: React.ReactNode
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
}
export function ConnectAIModal({
@@ -73,103 +73,103 @@ export function ConnectAIModal({
}: 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";
+ >(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();
- }, []);
+ analytics.mcpViewOpened()
+ }, [])
const { data: projects = [], isLoading: isLoadingProjects } = useQuery({
queryKey: ["projects"],
queryFn: async () => {
- const response = await $fetch("@get/projects");
+ const response = await $fetch("@get/projects")
if (response.error) {
- throw new Error(response.error?.message || "Failed to load projects");
+ throw new Error(response.error?.message || "Failed to load projects")
}
- return response.data?.projects || [];
+ return response.data?.projects || []
},
staleTime: 30 * 1000,
- });
+ })
const mcpMigrationForm = useForm({
defaultValues: { url: "" },
onSubmit: async ({ value, formApi }) => {
- const userId = extractUserIdFromMCPUrl(value.url);
+ const userId = extractUserIdFromMCPUrl(value.url)
if (userId) {
- migrateMCPMutation.mutate({ userId, projectId });
- formApi.reset();
+ 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 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;
+ userId: string
+ projectId: string
}) => {
- const response = await $fetch("@post/memories/migrate-mcp", {
+ const response = await $fetch("@post/documents/migrate-mcp", {
body: { userId, projectId },
- });
+ })
if (response.error) {
throw new Error(
response.error?.message || "Failed to migrate documents",
- );
+ )
}
- return response.data;
+ return response.data
},
onSuccess: (data) => {
toast.success("Migration completed!", {
description: `Successfully migrated ${data?.migratedCount} documents`,
- });
- setIsMigrateDialogOpen(false);
+ })
+ setIsMigrateDialogOpen(false)
},
onError: (error) => {
toast.error("Migration failed", {
description: error instanceof Error ? error.message : "Unknown error",
- });
+ })
},
- });
+ })
function generateInstallCommand() {
- if (!selectedClient) return "";
+ if (!selectedClient) return ""
- let command = `npx -y install-mcp@latest https://api.supermemory.ai/mcp --client ${selectedClient} --oauth=yes`;
+ 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}`;
+ const projectIdForCommand = selectedProject.replace(/^sm_project_/, "")
+ command += ` --project ${projectIdForCommand}`
}
- return command;
+ return command
}
const copyToClipboard = () => {
- const command = generateInstallCommand();
- navigator.clipboard.writeText(command);
- analytics.mcpInstallCmdCopied();
- toast.success("Copied to clipboard!");
- };
+ const command = generateInstallCommand()
+ navigator.clipboard.writeText(command)
+ analytics.mcpInstallCmdCopied()
+ toast.success("Copied to clipboard!")
+ }
return (
<Dialog onOpenChange={setIsOpen} open={isOpen}>
@@ -219,23 +219,27 @@ export function ConnectAIModal({
className="rounded object-contain text-white fill-white"
height={20}
onError={(e) => {
- const target = e.target as HTMLImageElement;
- target.style.display = "none";
- const parent = target.parentElement;
+ 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");
+ const fallback = document.createElement("span")
fallback.className =
- "fallback-text text-sm font-bold text-white/40";
+ "fallback-text text-sm font-bold text-white/40"
fallback.textContent = clientName
.substring(0, 2)
- .toUpperCase();
- parent.appendChild(fallback);
+ .toUpperCase()
+ parent.appendChild(fallback)
}
}}
- src={key === "mcp-url" ? "/mcp-icon.svg" : `/mcp-supported-tools/${key === "claude-code" ? "claude" : key}.png`}
+ src={
+ key === "mcp-url"
+ ? "/mcp-icon.svg"
+ : `/mcp-supported-tools/${key === "claude-code" ? "claude" : key}.png`
+ }
width={20}
/>
</div>
@@ -275,9 +279,9 @@ export function ConnectAIModal({
onClick={() => {
navigator.clipboard.writeText(
"https://api.supermemory.ai/mcp",
- );
- analytics.mcpInstallCmdCopied();
- toast.success("Copied to clipboard!");
+ )
+ analytics.mcpInstallCmdCopied()
+ toast.success("Copied to clipboard!")
}}
variant="ghost"
>
@@ -459,9 +463,9 @@ export function ConnectAIModal({
</DialogHeader>
<form
onSubmit={(e) => {
- e.preventDefault();
- e.stopPropagation();
- mcpMigrationForm.handleSubmit();
+ e.preventDefault()
+ e.stopPropagation()
+ mcpMigrationForm.handleSubmit()
}}
>
<div className="grid gap-4">
@@ -500,8 +504,8 @@ export function ConnectAIModal({
<Button
className="bg-white/5 hover:bg-white/10 border-white/10 text-white"
onClick={() => {
- setIsMigrateDialogOpen(false);
- mcpMigrationForm.reset();
+ setIsMigrateDialogOpen(false)
+ mcpMigrationForm.reset()
}}
type="button"
variant="outline"
@@ -532,5 +536,5 @@ export function ConnectAIModal({
</Dialog>
)}
</Dialog>
- );
+ )
}
diff --git a/apps/web/components/create-project-dialog.tsx b/apps/web/components/create-project-dialog.tsx
index 1672a689..9a65b894 100644
--- a/apps/web/components/create-project-dialog.tsx
+++ b/apps/web/components/create-project-dialog.tsx
@@ -1,6 +1,6 @@
-"use client";
+"use client"
-import { Button } from "@repo/ui/components/button";
+import { Button } from "@repo/ui/components/button"
import {
Dialog,
DialogContent,
@@ -8,50 +8,50 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
-} from "@repo/ui/components/dialog";
-import { Input } from "@repo/ui/components/input";
-import { Label } from "@repo/ui/components/label";
-import { Loader2 } from "lucide-react";
-import { AnimatePresence, motion } from "motion/react";
-import { useState } from "react";
-import { useProjectMutations } from "@/hooks/use-project-mutations";
+} from "@repo/ui/components/dialog"
+import { Input } from "@repo/ui/components/input"
+import { Label } from "@repo/ui/components/label"
+import { Loader2 } from "lucide-react"
+import { AnimatePresence, motion } from "motion/react"
+import { useState } from "react"
+import { useProjectMutations } from "@/hooks/use-project-mutations"
interface CreateProjectDialogProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
+ open: boolean
+ onOpenChange: (open: boolean) => void
}
export function CreateProjectDialog({
open,
onOpenChange,
}: CreateProjectDialogProps) {
- const [projectName, setProjectName] = useState("");
- const { createProjectMutation } = useProjectMutations();
+ const [projectName, setProjectName] = useState("")
+ const { createProjectMutation } = useProjectMutations()
const handleClose = () => {
- onOpenChange(false);
- setProjectName("");
- };
+ onOpenChange(false)
+ setProjectName("")
+ }
const handleCreate = () => {
if (projectName.trim()) {
createProjectMutation.mutate(projectName, {
onSuccess: () => {
- handleClose();
+ handleClose()
},
- });
+ })
}
- };
+ }
return (
<AnimatePresence>
{open && (
- <Dialog open={open} onOpenChange={onOpenChange}>
+ <Dialog onOpenChange={onOpenChange} open={open}>
<DialogContent className="sm:max-w-2xl bg-[#0f1419] backdrop-blur-xl border-white/10 text-white">
<motion.div
- initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
+ initial={{ opacity: 0, scale: 0.95 }}
>
<DialogHeader>
<DialogTitle>Create New Project</DialogTitle>
@@ -61,23 +61,23 @@ export function CreateProjectDialog({
</DialogHeader>
<div className="grid gap-4 py-4">
<motion.div
- initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
- transition={{ delay: 0.1 }}
className="flex flex-col gap-2"
+ initial={{ opacity: 0, y: 10 }}
+ transition={{ delay: 0.1 }}
>
<Label htmlFor="projectName">Project Name</Label>
<Input
- id="projectName"
className="bg-white/5 border-white/10 text-white"
- placeholder="My Awesome Project"
- value={projectName}
+ id="projectName"
onChange={(e) => setProjectName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && projectName.trim()) {
- handleCreate();
+ handleCreate()
}
}}
+ placeholder="My Awesome Project"
+ value={projectName}
/>
<p className="text-xs text-white/50">
This will help you organize your memories
@@ -90,10 +90,10 @@ export function CreateProjectDialog({
whileTap={{ scale: 0.95 }}
>
<Button
- type="button"
- variant="outline"
className="bg-white/5 hover:bg-white/10 border-white/10 text-white"
onClick={handleClose}
+ type="button"
+ variant="outline"
>
Cancel
</Button>
@@ -103,12 +103,12 @@ export function CreateProjectDialog({
whileTap={{ scale: 0.95 }}
>
<Button
- type="button"
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
disabled={
createProjectMutation.isPending || !projectName.trim()
}
onClick={handleCreate}
+ type="button"
>
{createProjectMutation.isPending ? (
<>
@@ -126,5 +126,5 @@ export function CreateProjectDialog({
</Dialog>
)}
</AnimatePresence>
- );
+ )
}