mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
feat: delete connection without removing the documents (#778)
- Deleting Connection without removing documents - Updating the Docs with new param
This commit is contained in:
parent
6ce7357ffb
commit
c534008001
8 changed files with 421 additions and 64 deletions
|
|
@ -448,7 +448,7 @@ Deleting a GitHub connection will:
|
|||
- Stop all future syncs from configured repositories
|
||||
- Remove all webhooks from the repositories
|
||||
- Revoke the OAuth authorization
|
||||
- **Permanently delete all synced documents** from your Supermemory knowledge base
|
||||
- **Permanently delete all synced documents** from your Supermemory knowledge base (unless you pass `deleteDocuments=false` as a query parameter to keep them)
|
||||
</Warning>
|
||||
|
||||
### Manual Sync
|
||||
|
|
|
|||
|
|
@ -287,6 +287,16 @@ curl -X POST "https://api.supermemory.ai/v3/connections/list" \
|
|||
|
||||
### Delete Connections
|
||||
|
||||
The `DELETE /v3/connections/:connectionId` endpoint accepts an optional `deleteDocuments` query parameter:
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `deleteDocuments` | boolean | `true` | When `true`, all documents imported by the connection are permanently deleted. When `false`, the connection is removed but documents are kept. |
|
||||
|
||||
<Note>
|
||||
Setting `deleteDocuments=false` is useful when you want to disconnect an integration without losing the memories that were already imported.
|
||||
</Note>
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript Typescript
|
||||
|
|
@ -296,9 +306,14 @@ const client = new Supermemory({
|
|||
apiKey: process.env.SUPERMEMORY_API_KEY!
|
||||
});
|
||||
|
||||
// Delete by connection ID
|
||||
// Delete connection and all imported documents (default)
|
||||
const result = await client.connections.deleteByID(connectionId);
|
||||
|
||||
// Delete connection but keep imported documents
|
||||
const result = await client.connections.deleteByID(connectionId, {
|
||||
deleteDocuments: false
|
||||
});
|
||||
|
||||
// Or delete by provider (requires container tags)
|
||||
const result = await client.connections.deleteByProvider('notion', {
|
||||
containerTags: ['user-123']
|
||||
|
|
@ -314,9 +329,12 @@ import os
|
|||
|
||||
client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY"))
|
||||
|
||||
# Delete by connection ID
|
||||
# Delete connection and all imported documents (default)
|
||||
result = client.connections.delete_by_id(connection_id)
|
||||
|
||||
# Delete connection but keep imported documents
|
||||
result = client.connections.delete_by_id(connection_id, delete_documents=False)
|
||||
|
||||
# Or delete by provider (requires container tags)
|
||||
result = client.connections.delete_by_provider(
|
||||
provider='notion',
|
||||
|
|
@ -328,9 +346,14 @@ print(f"Deleted: {result.id} {result.provider}")
|
|||
```
|
||||
|
||||
```bash cURL
|
||||
# Delete connection and all imported documents (default)
|
||||
curl -X DELETE "https://api.supermemory.ai/v3/connections/conn_abc123" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
||||
|
||||
# Delete connection but keep imported documents
|
||||
curl -X DELETE "https://api.supermemory.ai/v3/connections/conn_abc123?deleteDocuments=false" \
|
||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
||||
|
||||
# Response: {
|
||||
# "id": "conn_abc123",
|
||||
# "provider": "notion"
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ The regex must contain a named capture group `(?<userId>...)` and be less than 2
|
|||
</Tabs>
|
||||
|
||||
<Warning>
|
||||
Deleting a connection removes all synced documents from Supermemory.
|
||||
By default, deleting a connection removes all synced documents from Supermemory. To keep documents, pass `deleteDocuments=false` as a query parameter: `DELETE /v3/connections/:id?deleteDocuments=false`
|
||||
</Warning>
|
||||
|
||||
### Manual Sync
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type { z } from "zod"
|
|||
import { dmSansClassName } from "@/lib/fonts"
|
||||
import { cn } from "@lib/utils"
|
||||
import { Button } from "@ui/components/button"
|
||||
import { RemoveConnectionDialog } from "@/components/remove-connection-dialog"
|
||||
|
||||
type Connection = z.infer<typeof ConnectionResponseSchema>
|
||||
|
||||
|
|
@ -54,6 +55,10 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
|
|||
const [connectingProvider, setConnectingProvider] =
|
||||
useState<ConnectorProvider | null>(null)
|
||||
const [isUpgrading, setIsUpgrading] = useState(false)
|
||||
const [removeDialog, setRemoveDialog] = useState<{
|
||||
open: boolean
|
||||
connection: Connection | null
|
||||
}>({ open: false, connection: null })
|
||||
|
||||
// Check Pro status
|
||||
useEffect(() => {
|
||||
|
|
@ -159,15 +164,26 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
|
|||
},
|
||||
})
|
||||
|
||||
// Disconnect mutation
|
||||
const deleteConnectionMutation = useMutation({
|
||||
mutationFn: async (connectionId: string) => {
|
||||
await $fetch(`@delete/connections/${connectionId}`)
|
||||
mutationFn: async ({
|
||||
connectionId,
|
||||
deleteDocuments,
|
||||
}: {
|
||||
connectionId: string
|
||||
deleteDocuments: boolean
|
||||
}) => {
|
||||
await $fetch(`@delete/connections/${connectionId}`, {
|
||||
query: { deleteDocuments },
|
||||
})
|
||||
return { deleteDocuments }
|
||||
},
|
||||
onSuccess: () => {
|
||||
onSuccess: (_data, variables) => {
|
||||
toast.success(
|
||||
"Connection removal has started. supermemory will permanently delete all documents related to the connection in the next few minutes.",
|
||||
variables.deleteDocuments
|
||||
? "Connection removal has started. supermemory will permanently delete all documents related to the connection in the next few minutes."
|
||||
: "Connection removed. Your memories have been kept.",
|
||||
)
|
||||
setRemoveDialog({ open: false, connection: null })
|
||||
queryClient.invalidateQueries({ queryKey: ["connections"] })
|
||||
},
|
||||
onError: (error) => {
|
||||
|
|
@ -182,8 +198,8 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
|
|||
addConnectionMutation.mutate(provider)
|
||||
}
|
||||
|
||||
const handleDisconnect = (connectionId: string) => {
|
||||
deleteConnectionMutation.mutate(connectionId)
|
||||
const handleDisconnect = (connection: Connection) => {
|
||||
setRemoveDialog({ open: true, connection })
|
||||
}
|
||||
|
||||
const hasConnections = connections.length > 0
|
||||
|
|
@ -278,7 +294,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDisconnect(connection.id)}
|
||||
onClick={() => handleDisconnect(connection)}
|
||||
disabled={deleteConnectionMutation.isPending}
|
||||
className="text-[#737373] hover:text-white hover:bg-[#1B1F24] h-8 w-8 p-0"
|
||||
>
|
||||
|
|
@ -351,7 +367,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDisconnect(connection.id)}
|
||||
onClick={() => handleDisconnect(connection)}
|
||||
disabled={deleteConnectionMutation.isPending}
|
||||
className="text-[#737373] hover:text-white hover:bg-[#1B1F24] h-8 w-8 p-0 shrink-0"
|
||||
>
|
||||
|
|
@ -428,6 +444,25 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
<RemoveConnectionDialog
|
||||
open={removeDialog.open}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setRemoveDialog({ open: false, connection: null })
|
||||
}}
|
||||
provider={removeDialog.connection?.provider}
|
||||
documentCount={
|
||||
(removeDialog.connection?.metadata?.documentCount as number) ?? 0
|
||||
}
|
||||
onConfirm={(deleteDocuments) => {
|
||||
if (removeDialog.connection) {
|
||||
deleteConnectionMutation.mutate({
|
||||
connectionId: removeDialog.connection.id,
|
||||
deleteDocuments,
|
||||
})
|
||||
}
|
||||
}}
|
||||
isDeleting={deleteConnectionMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ import { GoogleDrive, Notion, OneDrive } from "@ui/assets/icons"
|
|||
import { useCustomer } from "autumn-js/react"
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import { Check, Plus, Trash2, Zap } from "lucide-react"
|
||||
import { useEffect } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { useQueryState } from "nuqs"
|
||||
import type { ConnectionResponseSchema } from "@repo/validation/api"
|
||||
import type { z } from "zod"
|
||||
import { analytics } from "@/lib/analytics"
|
||||
import { AddDocumentModal } from "@/components/add-document"
|
||||
import { RemoveConnectionDialog } from "@/components/remove-connection-dialog"
|
||||
import { addDocumentParam } from "@/lib/search-params"
|
||||
import { DEFAULT_PROJECT_ID } from "@lib/constants"
|
||||
import type { Project } from "@lib/types"
|
||||
|
|
@ -168,6 +170,11 @@ function ConnectionRow({
|
|||
export function ConnectionsDetail() {
|
||||
const queryClient = useQueryClient()
|
||||
const autumn = useCustomer()
|
||||
const [isAddDocumentOpen, setIsAddDocumentOpen] = useState(false)
|
||||
const [removeDialog, setRemoveDialog] = useState<{
|
||||
open: boolean
|
||||
connection: Connection | null
|
||||
}>({ open: false, connection: null })
|
||||
const [, setAddDoc] = useQueryState("add", addDocumentParam)
|
||||
|
||||
const projects = (queryClient.getQueryData<Project[]>(["projects"]) ||
|
||||
|
|
@ -216,14 +223,26 @@ export function ConnectionsDetail() {
|
|||
}, [connectionsError])
|
||||
|
||||
const deleteConnectionMutation = useMutation({
|
||||
mutationFn: async (connectionId: string) => {
|
||||
await $fetch(`@delete/connections/${connectionId}`)
|
||||
mutationFn: async ({
|
||||
connectionId,
|
||||
deleteDocuments,
|
||||
}: {
|
||||
connectionId: string
|
||||
deleteDocuments: boolean
|
||||
}) => {
|
||||
await $fetch(`@delete/connections/${connectionId}`, {
|
||||
query: { deleteDocuments },
|
||||
})
|
||||
return { deleteDocuments }
|
||||
},
|
||||
onSuccess: () => {
|
||||
onSuccess: (_data, variables) => {
|
||||
analytics.connectionDeleted()
|
||||
toast.success(
|
||||
"Connection removal has started. Documents will be permanently deleted in the next few minutes.",
|
||||
variables.deleteDocuments
|
||||
? "Connection removal has started. Documents will be permanently deleted in the next few minutes."
|
||||
: "Connection removed. Your memories have been kept.",
|
||||
)
|
||||
setRemoveDialog({ open: false, connection: null })
|
||||
queryClient.invalidateQueries({ queryKey: ["connections"] })
|
||||
},
|
||||
onError: (error) => {
|
||||
|
|
@ -331,44 +350,61 @@ export function ConnectionsDetail() {
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
{isLoadingConnections ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="size-6 border-2 border-[#737373] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
) : connections.length > 0 ? (
|
||||
connections.map((connection) => (
|
||||
<ConnectionRow
|
||||
key={connection.id}
|
||||
connection={connection}
|
||||
onDelete={() => deleteConnectionMutation.mutate(connection.id)}
|
||||
isDeleting={deleteConnectionMutation.isPending}
|
||||
disabled={!hasProProduct}
|
||||
projects={projects}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||
<Zap className="size-6 text-[#737373] mb-2" />
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"text-[14px] text-[#737373]",
|
||||
)}
|
||||
>
|
||||
No connections yet
|
||||
</p>
|
||||
<p
|
||||
className={cn(
|
||||
dmSans125ClassName(),
|
||||
"text-[12px] text-[#737373]",
|
||||
)}
|
||||
>
|
||||
Connect a service below to import your knowledge
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isLoadingConnections ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="size-6 border-2 border-[#737373] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
) : connections.length > 0 ? (
|
||||
connections.map((connection) => (
|
||||
<ConnectionRow
|
||||
key={connection.id}
|
||||
connection={connection}
|
||||
onDelete={() => setRemoveDialog({ open: true, connection })}
|
||||
isDeleting={deleteConnectionMutation.isPending}
|
||||
disabled={!hasProProduct}
|
||||
projects={projects}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||
<Zap className="size-6 text-[#737373] mb-2" />
|
||||
<p
|
||||
className={cn(dmSans125ClassName(), "text-[14px] text-[#737373]")}
|
||||
>
|
||||
No connections yet
|
||||
</p>
|
||||
<p
|
||||
className={cn(dmSans125ClassName(), "text-[12px] text-[#737373]")}
|
||||
>
|
||||
Connect a service below to import your knowledge
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AddDocumentModal
|
||||
isOpen={isAddDocumentOpen}
|
||||
onClose={() => setIsAddDocumentOpen(false)}
|
||||
/>
|
||||
|
||||
<RemoveConnectionDialog
|
||||
open={removeDialog.open}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setRemoveDialog({ open: false, connection: null })
|
||||
}}
|
||||
provider={removeDialog.connection?.provider}
|
||||
documentCount={
|
||||
(removeDialog.connection?.metadata?.documentCount as number) ?? 0
|
||||
}
|
||||
onConfirm={(deleteDocuments) => {
|
||||
if (removeDialog.connection) {
|
||||
deleteConnectionMutation.mutate({
|
||||
connectionId: removeDialog.connection.id,
|
||||
deleteDocuments,
|
||||
})
|
||||
}
|
||||
}}
|
||||
isDeleting={deleteConnectionMutation.isPending}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
226
apps/web/components/remove-connection-dialog.tsx
Normal file
226
apps/web/components/remove-connection-dialog.tsx
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { cn } from "@lib/utils"
|
||||
import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts"
|
||||
import { Loader2, XIcon } from "lucide-react"
|
||||
import { Button } from "@ui/components/button"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from "@repo/ui/components/dialog"
|
||||
|
||||
const PROVIDER_LABELS: Record<string, string> = {
|
||||
"google-drive": "Google Drive",
|
||||
notion: "Notion",
|
||||
onedrive: "OneDrive",
|
||||
gmail: "Gmail",
|
||||
github: "GitHub",
|
||||
"web-crawler": "Web Crawler",
|
||||
s3: "S3",
|
||||
}
|
||||
|
||||
interface RemoveConnectionDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
providerName?: string
|
||||
provider?: string
|
||||
documentCount?: number
|
||||
onConfirm: (deleteDocuments: boolean) => void
|
||||
isDeleting: boolean
|
||||
}
|
||||
|
||||
export function RemoveConnectionDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
providerName,
|
||||
provider,
|
||||
documentCount = 0,
|
||||
onConfirm,
|
||||
isDeleting,
|
||||
}: RemoveConnectionDialogProps) {
|
||||
const [action, setAction] = useState<"keep" | "delete">("keep")
|
||||
const displayName =
|
||||
providerName || (provider ? PROVIDER_LABELS[provider] : "this connection")
|
||||
|
||||
const handleConfirm = () => {
|
||||
onConfirm(action === "delete")
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(o) => {
|
||||
if (!isDeleting) {
|
||||
onOpenChange(o)
|
||||
if (!o) setAction("keep")
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent
|
||||
className={cn(
|
||||
"w-[90%]! max-w-[500px]! border-none bg-[#1B1F24] flex flex-col p-4 gap-4 rounded-[22px]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
style={{
|
||||
boxShadow:
|
||||
"0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset",
|
||||
}}
|
||||
showCloseButton={false}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex justify-between items-start gap-4">
|
||||
<div className="pl-1 space-y-1 flex-1">
|
||||
<DialogTitle
|
||||
className={cn(
|
||||
"font-semibold text-[#fafafa]",
|
||||
dmSans125ClassName(),
|
||||
)}
|
||||
>
|
||||
Remove connection
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-[#737373] font-medium text-[16px] leading-[1.35]">
|
||||
What would you like to do with the{" "}
|
||||
{documentCount > 0 ? (
|
||||
<>
|
||||
<span className="text-[#fafafa] font-medium">
|
||||
{documentCount}
|
||||
</span>{" "}
|
||||
memories from{" "}
|
||||
</>
|
||||
) : (
|
||||
<>memories from </>
|
||||
)}
|
||||
<span className="text-[#fafafa] font-medium">
|
||||
{displayName}
|
||||
</span>
|
||||
?
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<DialogPrimitive.Close
|
||||
disabled={isDeleting}
|
||||
className="bg-[#0D121A] w-7 h-7 flex items-center justify-center focus:ring-ring rounded-full transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 border border-[rgba(115,115,115,0.2)] shrink-0"
|
||||
style={{
|
||||
boxShadow: "inset 1.313px 1.313px 3.938px 0px rgba(0,0,0,0.7)",
|
||||
}}
|
||||
>
|
||||
<XIcon stroke="#737373" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAction("keep")}
|
||||
className={cn(
|
||||
"flex items-center gap-3 p-3 rounded-[12px] cursor-pointer transition-colors w-full text-left",
|
||||
action === "keep"
|
||||
? "bg-[#14161A] border border-[rgba(82,89,102,0.3)]"
|
||||
: "bg-[#14161A]/50 border border-transparent hover:border-[rgba(82,89,102,0.2)]",
|
||||
)}
|
||||
style={{
|
||||
boxShadow:
|
||||
action === "keep"
|
||||
? "0px 1px 2px 0px rgba(0,43,87,0.1), inset 0px 0px 0px 1px rgba(43,49,67,0.08), inset 0px 1px 1px 0px rgba(0,0,0,0.08)"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"w-4 h-4 rounded-full border-2 flex items-center justify-center shrink-0",
|
||||
action === "keep" ? "border-blue-500" : "border-[#737373]",
|
||||
)}
|
||||
>
|
||||
{action === "keep" && (
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[#fafafa] text-sm font-medium">
|
||||
Remove connection only
|
||||
</span>
|
||||
<span className="text-[#737373] text-xs">
|
||||
Disconnect the integration but keep all imported memories
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAction("delete")}
|
||||
className={cn(
|
||||
"flex items-center gap-3 p-3 rounded-[12px] cursor-pointer transition-colors w-full text-left",
|
||||
action === "delete"
|
||||
? "bg-[#14161A] border border-[rgba(220,38,38,0.3)]"
|
||||
: "bg-[#14161A]/50 border border-transparent hover:border-[rgba(82,89,102,0.2)]",
|
||||
)}
|
||||
style={{
|
||||
boxShadow:
|
||||
action === "delete"
|
||||
? "0px 1px 2px 0px rgba(87,0,0,0.1), inset 0px 0px 0px 1px rgba(67,43,43,0.08), inset 0px 1px 1px 0px rgba(0,0,0,0.08)"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"w-4 h-4 rounded-full border-2 flex items-center justify-center shrink-0",
|
||||
action === "delete" ? "border-red-500" : "border-[#737373]",
|
||||
)}
|
||||
>
|
||||
{action === "delete" && (
|
||||
<div className="w-2 h-2 rounded-full bg-red-500" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[#fafafa] text-sm font-medium">
|
||||
Remove connection and memories
|
||||
</span>
|
||||
<span className="text-[#737373] text-xs">
|
||||
Permanently delete all memories imported from this connection
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 pt-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
disabled={isDeleting}
|
||||
onClick={() => {
|
||||
onOpenChange(false)
|
||||
setAction("keep")
|
||||
}}
|
||||
className="text-[#737373] cursor-pointer rounded-full"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="insideOut"
|
||||
disabled={isDeleting}
|
||||
onClick={handleConfirm}
|
||||
className={cn(
|
||||
action === "delete" &&
|
||||
"bg-red-600! hover:bg-red-700! text-white",
|
||||
)}
|
||||
>
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
Removing...
|
||||
</>
|
||||
) : action === "delete" ? (
|
||||
"Remove & delete memories"
|
||||
) : (
|
||||
"Remove connection"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import type { z } from "zod"
|
|||
import { analytics } from "@/lib/analytics"
|
||||
import { ConnectAIModal } from "@/components/connect-ai-modal"
|
||||
import { AddDocumentModal } from "@/components/add-document"
|
||||
import { RemoveConnectionDialog } from "@/components/remove-connection-dialog"
|
||||
import { addDocumentParam } from "@/lib/search-params"
|
||||
import { DEFAULT_PROJECT_ID } from "@lib/constants"
|
||||
import type { Project } from "@lib/types"
|
||||
|
|
@ -337,6 +338,10 @@ export default function ConnectionsMCP() {
|
|||
const autumn = useCustomer()
|
||||
const [addDoc, setAddDoc] = useQueryState("add", addDocumentParam)
|
||||
const [mcpModalOpen, setMcpModalOpen] = useState(false)
|
||||
const [removeDialog, setRemoveDialog] = useState<{
|
||||
open: boolean
|
||||
connection: Connection | null
|
||||
}>({ open: false, connection: null })
|
||||
|
||||
const projects = (queryClient.getQueryData<Project[]>(["projects"]) ||
|
||||
[]) as Project[]
|
||||
|
|
@ -394,16 +399,27 @@ export default function ConnectionsMCP() {
|
|||
}
|
||||
}, [connectionsError])
|
||||
|
||||
// Delete connection mutation
|
||||
const deleteConnectionMutation = useMutation({
|
||||
mutationFn: async (connectionId: string) => {
|
||||
await $fetch(`@delete/connections/${connectionId}`)
|
||||
mutationFn: async ({
|
||||
connectionId,
|
||||
deleteDocuments,
|
||||
}: {
|
||||
connectionId: string
|
||||
deleteDocuments: boolean
|
||||
}) => {
|
||||
await $fetch(`@delete/connections/${connectionId}`, {
|
||||
query: { deleteDocuments },
|
||||
})
|
||||
return { deleteDocuments }
|
||||
},
|
||||
onSuccess: () => {
|
||||
onSuccess: (_data, variables) => {
|
||||
analytics.connectionDeleted()
|
||||
toast.success(
|
||||
"Connection removal has started. Supermemory will permanently delete the documents in the next few minutes.",
|
||||
variables.deleteDocuments
|
||||
? "Connection removal has started. Supermemory will permanently delete the documents in the next few minutes."
|
||||
: "Connection removed. Your memories have been kept.",
|
||||
)
|
||||
setRemoveDialog({ open: false, connection: null })
|
||||
queryClient.invalidateQueries({ queryKey: ["connections"] })
|
||||
},
|
||||
onError: (error) => {
|
||||
|
|
@ -480,9 +496,7 @@ export default function ConnectionsMCP() {
|
|||
<ConnectionRow
|
||||
key={connection.id}
|
||||
connection={connection}
|
||||
onDelete={() =>
|
||||
deleteConnectionMutation.mutate(connection.id)
|
||||
}
|
||||
onDelete={() => setRemoveDialog({ open: true, connection })}
|
||||
isDeleting={deleteConnectionMutation.isPending}
|
||||
disabled={!hasProProduct}
|
||||
projects={projects}
|
||||
|
|
@ -564,6 +578,26 @@ export default function ConnectionsMCP() {
|
|||
isOpen={addDoc !== null}
|
||||
onClose={() => setAddDoc(null)}
|
||||
/>
|
||||
|
||||
<RemoveConnectionDialog
|
||||
open={removeDialog.open}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setRemoveDialog({ open: false, connection: null })
|
||||
}}
|
||||
provider={removeDialog.connection?.provider}
|
||||
documentCount={
|
||||
(removeDialog.connection?.metadata?.documentCount as number) ?? 0
|
||||
}
|
||||
onConfirm={(deleteDocuments) => {
|
||||
if (removeDialog.connection) {
|
||||
deleteConnectionMutation.mutate({
|
||||
connectionId: removeDialog.connection.id,
|
||||
deleteDocuments,
|
||||
})
|
||||
}
|
||||
}}
|
||||
isDeleting={deleteConnectionMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,6 +119,9 @@ export const apiSchema = createSchema({
|
|||
provider: z.string(),
|
||||
}),
|
||||
params: z.object({ connectionId: z.string() }),
|
||||
query: z.object({
|
||||
deleteDocuments: z.boolean().optional(),
|
||||
}),
|
||||
},
|
||||
|
||||
// Settings operations
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue