Delete custom provider (#137)
This commit is contained in:
@@ -9,17 +9,36 @@ import { providerSettingsRoute } from "@/routes/settings/providers/$provider";
|
||||
import type { LanguageModelProvider } from "@/ipc/ipc_types";
|
||||
|
||||
import { useLanguageModelProviders } from "@/hooks/useLanguageModelProviders";
|
||||
import { GiftIcon, PlusIcon } from "lucide-react";
|
||||
import { useCustomLanguageModelProvider } from "@/hooks/useCustomLanguageModelProvider";
|
||||
import { GiftIcon, PlusIcon, MoreVertical, Trash2 } from "lucide-react";
|
||||
import { Skeleton } from "./ui/skeleton";
|
||||
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
|
||||
import { CreateCustomProviderDialog } from "./CreateCustomProviderDialog";
|
||||
|
||||
export function ProviderSettingsGrid() {
|
||||
const navigate = useNavigate();
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [providerToDelete, setProviderToDelete] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
data: providers,
|
||||
@@ -29,6 +48,8 @@ export function ProviderSettingsGrid() {
|
||||
refetch,
|
||||
} = useLanguageModelProviders();
|
||||
|
||||
const { deleteProvider, isDeleting } = useCustomLanguageModelProvider();
|
||||
|
||||
const handleProviderClick = (providerId: string) => {
|
||||
navigate({
|
||||
to: providerSettingsRoute.id,
|
||||
@@ -36,6 +57,14 @@ export function ProviderSettingsGrid() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteProvider = async () => {
|
||||
if (providerToDelete) {
|
||||
await deleteProvider(providerToDelete);
|
||||
setProviderToDelete(null);
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
@@ -74,13 +103,17 @@ export function ProviderSettingsGrid() {
|
||||
<h2 className="text-2xl font-bold mb-6">AI Providers</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{providers?.map((provider: LanguageModelProvider) => {
|
||||
const isCustom = provider.type === "custom";
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={provider.id}
|
||||
className="cursor-pointer transition-all hover:shadow-md border-border"
|
||||
onClick={() => handleProviderClick(provider.id)}
|
||||
className="relative transition-all hover:shadow-md border-border"
|
||||
>
|
||||
<CardHeader className="p-4">
|
||||
<CardHeader
|
||||
className="p-4 cursor-pointer"
|
||||
onClick={() => handleProviderClick(provider.id)}
|
||||
>
|
||||
<CardTitle className="text-xl flex items-center justify-between">
|
||||
{provider.name}
|
||||
{isProviderSetup(provider.id) ? (
|
||||
@@ -102,6 +135,30 @@ export function ProviderSettingsGrid() {
|
||||
)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
{isCustom && (
|
||||
<div
|
||||
className="absolute top-2 right-2"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="focus:outline-none">
|
||||
<div className="p-1 hover:bg-muted rounded-full">
|
||||
<MoreVertical className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={() => setProviderToDelete(provider.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Delete Provider
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
@@ -131,6 +188,30 @@ export function ProviderSettingsGrid() {
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
|
||||
<AlertDialog
|
||||
open={!!providerToDelete}
|
||||
onOpenChange={(open) => !open && setProviderToDelete(null)}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Custom Provider</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will permanently delete this custom provider and all its
|
||||
associated models. This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleDeleteProvider}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? "Deleting..." : "Delete Provider"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,15 +40,38 @@ export function useCustomLanguageModelProvider() {
|
||||
},
|
||||
});
|
||||
|
||||
const deleteProviderMutation = useMutation({
|
||||
mutationFn: async (providerId: string): Promise<void> => {
|
||||
if (!providerId) {
|
||||
throw new Error("Provider ID is required");
|
||||
}
|
||||
|
||||
return ipcClient.deleteCustomLanguageModelProvider(providerId);
|
||||
},
|
||||
onSuccess: () => {
|
||||
// Invalidate and refetch
|
||||
queryClient.invalidateQueries({ queryKey: ["languageModelProviders"] });
|
||||
},
|
||||
onError: (error) => {
|
||||
showError(error);
|
||||
},
|
||||
});
|
||||
|
||||
const createProvider = async (
|
||||
params: CreateCustomLanguageModelProviderParams,
|
||||
): Promise<LanguageModelProvider> => {
|
||||
return createProviderMutation.mutateAsync(params);
|
||||
};
|
||||
|
||||
const deleteProvider = async (providerId: string): Promise<void> => {
|
||||
return deleteProviderMutation.mutateAsync(providerId);
|
||||
};
|
||||
|
||||
return {
|
||||
createProvider,
|
||||
deleteProvider,
|
||||
isCreating: createProviderMutation.isPending,
|
||||
error: createProviderMutation.error,
|
||||
isDeleting: deleteProviderMutation.isPending,
|
||||
error: createProviderMutation.error || deleteProviderMutation.error,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -204,6 +204,72 @@ export function registerLanguageModelHandlers() {
|
||||
},
|
||||
);
|
||||
|
||||
handle(
|
||||
"delete-custom-language-model-provider",
|
||||
async (
|
||||
event: IpcMainInvokeEvent,
|
||||
params: { providerId: string },
|
||||
): Promise<void> => {
|
||||
const { providerId } = params;
|
||||
|
||||
// Validation
|
||||
if (!providerId) {
|
||||
throw new Error("Provider ID is required");
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Handling delete-custom-language-model-provider for providerId: ${providerId}`,
|
||||
);
|
||||
|
||||
// Check if the provider exists before attempting deletion
|
||||
const existingProvider = await db
|
||||
.select({ id: languageModelProvidersSchema.id })
|
||||
.from(languageModelProvidersSchema)
|
||||
.where(eq(languageModelProvidersSchema.id, providerId))
|
||||
.get();
|
||||
|
||||
if (!existingProvider) {
|
||||
// If the provider doesn't exist, maybe it was already deleted. Log and return.
|
||||
logger.warn(
|
||||
`Provider with ID "${providerId}" not found. It might have been deleted already.`,
|
||||
);
|
||||
// Optionally, throw new Error(`Provider with ID "${providerId}" not found`);
|
||||
// Deciding to return gracefully instead of throwing an error if not found.
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a transaction to ensure atomicity
|
||||
await db.transaction(async (tx) => {
|
||||
// 1. Delete associated models
|
||||
const deleteModelsResult = await tx
|
||||
.delete(languageModelsSchema)
|
||||
.where(eq(languageModelsSchema.provider_id, providerId))
|
||||
.run();
|
||||
logger.info(
|
||||
`Deleted ${deleteModelsResult.changes} model(s) associated with provider ${providerId}`,
|
||||
);
|
||||
|
||||
// 2. Delete the provider
|
||||
const deleteProviderResult = await tx
|
||||
.delete(languageModelProvidersSchema)
|
||||
.where(eq(languageModelProvidersSchema.id, providerId))
|
||||
.run();
|
||||
|
||||
if (deleteProviderResult.changes === 0) {
|
||||
// This case should ideally not happen if existingProvider check passed,
|
||||
// but adding safety check within transaction.
|
||||
logger.error(
|
||||
`Failed to delete provider with ID "${providerId}" during transaction, although it was found initially. Rolling back.`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to delete provider with ID "${providerId}" which should have existed.`,
|
||||
);
|
||||
}
|
||||
logger.info(`Successfully deleted provider with ID "${providerId}".`);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
handle(
|
||||
"get-language-models",
|
||||
async (
|
||||
|
||||
@@ -774,6 +774,12 @@ export class IpcClient {
|
||||
return this.ipcRenderer.invoke("delete-custom-model", params);
|
||||
}
|
||||
|
||||
async deleteCustomLanguageModelProvider(providerId: string): Promise<void> {
|
||||
return this.ipcRenderer.invoke("delete-custom-language-model-provider", {
|
||||
providerId,
|
||||
});
|
||||
}
|
||||
|
||||
// --- End window control methods ---
|
||||
|
||||
// --- Language Model Operations ---
|
||||
|
||||
@@ -8,6 +8,7 @@ const validInvokeChannels = [
|
||||
"get-language-models",
|
||||
"create-custom-language-model",
|
||||
"get-language-model-providers",
|
||||
"delete-custom-language-model-provider",
|
||||
"create-custom-language-model-provider",
|
||||
"delete-custom-language-model",
|
||||
"delete-custom-model",
|
||||
|
||||
Reference in New Issue
Block a user