diff --git a/e2e-tests/edit_provider.spec.ts b/e2e-tests/edit_provider.spec.ts new file mode 100644 index 0000000..1a445bd --- /dev/null +++ b/e2e-tests/edit_provider.spec.ts @@ -0,0 +1,26 @@ +import { test } from "./helpers/test_helper"; + +test("can edit custom provider", async ({ po }) => { + await po.setUp(); + await po.goToSettingsTab(); + + // Create a provider first + + // Edit it + await po.page.getByTestId("custom-provider-more-options").click(); + await po.page.getByRole("button", { name: "Edit Provider" }).click(); + + await po.page.getByRole("textbox", { name: "Display Name" }).clear(); + await po.page + .getByRole("textbox", { name: "Display Name" }) + .fill("Updated Test Provider"); + + await po.page.getByRole("textbox", { name: "API Base URL" }).clear(); + await po.page + .getByRole("textbox", { name: "API Base URL" }) + .fill("https://api.updated-test.com/v1"); + + await po.page.getByRole("button", { name: "Update Provider" }).click(); + + // Make sure UI hasn't freezed +}); diff --git a/src/components/CreateCustomProviderDialog.tsx b/src/components/CreateCustomProviderDialog.tsx index d8dcd19..d33a2a4 100644 --- a/src/components/CreateCustomProviderDialog.tsx +++ b/src/components/CreateCustomProviderDialog.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Dialog, DialogContent, @@ -11,38 +11,73 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Loader2 } from "lucide-react"; import { useCustomLanguageModelProvider } from "@/hooks/useCustomLanguageModelProvider"; +import type { LanguageModelProvider } from "@/ipc/ipc_types"; interface CreateCustomProviderDialogProps { isOpen: boolean; onClose: () => void; onSuccess: () => void; + editingProvider?: LanguageModelProvider | null; } export function CreateCustomProviderDialog({ isOpen, onClose, onSuccess, + editingProvider = null, }: CreateCustomProviderDialogProps) { const [id, setId] = useState(""); const [name, setName] = useState(""); const [apiBaseUrl, setApiBaseUrl] = useState(""); const [envVarName, setEnvVarName] = useState(""); const [errorMessage, setErrorMessage] = useState(""); + const isEditMode = Boolean(editingProvider); - const { createProvider, isCreating, error } = + const { createProvider, editProvider, isCreating, isEditing, error } = useCustomLanguageModelProvider(); + // Load provider data when editing + useEffect(() => { + if (editingProvider && isOpen) { + const cleanId = editingProvider.id?.startsWith("custom::") + ? editingProvider.id.replace("custom::", "") + : editingProvider.id || ""; + setId(cleanId); + setName(editingProvider.name || ""); + setApiBaseUrl(editingProvider.apiBaseUrl || ""); + setEnvVarName(editingProvider.envVarName || ""); + } else if (!isOpen) { + // Reset form when dialog closes + setId(""); + setName(""); + setApiBaseUrl(""); + setEnvVarName(""); + setErrorMessage(""); + } + }, [editingProvider, isOpen]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setErrorMessage(""); try { - await createProvider({ - id: id.trim(), - name: name.trim(), - apiBaseUrl: apiBaseUrl.trim(), - envVarName: envVarName.trim() || undefined, - }); + if (isEditMode && editingProvider) { + const cleanId = editingProvider.id?.startsWith("custom::") + ? editingProvider.id.replace("custom::", "") + : editingProvider.id || ""; + await editProvider({ + id: cleanId, + name: name.trim(), + apiBaseUrl: apiBaseUrl.trim(), + envVarName: envVarName.trim() || undefined, + }); + } else { + await createProvider({ + id: id.trim(), + name: name.trim(), + apiBaseUrl: apiBaseUrl.trim(), + envVarName: envVarName.trim() || undefined, + }); + } // Reset form setId(""); @@ -55,25 +90,30 @@ export function CreateCustomProviderDialog({ setErrorMessage( error instanceof Error ? error.message - : "Failed to create custom provider", + : `Failed to ${isEditMode ? "edit" : "create"} custom provider`, ); } }; const handleClose = () => { - if (!isCreating) { + if (!isCreating && !isEditing) { setErrorMessage(""); onClose(); } }; + const isLoading = isCreating || isEditing; return ( - Add Custom Provider + + {isEditMode ? "Edit Custom Provider" : "Add Custom Provider"} + - Connect to a custom language model provider API + {isEditMode + ? "Update your custom language model provider configuration." + : "Connect to a custom language model provider API."} @@ -86,7 +126,7 @@ export function CreateCustomProviderDialog({ onChange={(e) => setId(e.target.value)} placeholder="E.g., my-provider" required - disabled={isCreating} + disabled={isLoading || isEditMode} />

A unique identifier for this provider (no spaces). @@ -101,7 +141,7 @@ export function CreateCustomProviderDialog({ onChange={(e) => setName(e.target.value)} placeholder="E.g., My Provider" required - disabled={isCreating} + disabled={isLoading} />

The name that will be displayed in the UI. @@ -116,7 +156,7 @@ export function CreateCustomProviderDialog({ onChange={(e) => setApiBaseUrl(e.target.value)} placeholder="E.g., https://api.example.com/v1" required - disabled={isCreating} + disabled={isLoading} />

The base URL for the API endpoint. @@ -130,7 +170,7 @@ export function CreateCustomProviderDialog({ value={envVarName} onChange={(e) => setEnvVarName(e.target.value)} placeholder="E.g., MY_PROVIDER_API_KEY" - disabled={isCreating} + disabled={isLoading} />

Environment variable name for the API key. @@ -151,13 +191,19 @@ export function CreateCustomProviderDialog({ type="button" variant="outline" onClick={handleClose} - disabled={isCreating} + disabled={isLoading} > Cancel - diff --git a/src/components/ProviderSettings.tsx b/src/components/ProviderSettings.tsx index 0292ca3..73a7e07 100644 --- a/src/components/ProviderSettings.tsx +++ b/src/components/ProviderSettings.tsx @@ -10,7 +10,7 @@ import type { LanguageModelProvider } from "@/ipc/ipc_types"; import { useLanguageModelProviders } from "@/hooks/useLanguageModelProviders"; import { useCustomLanguageModelProvider } from "@/hooks/useCustomLanguageModelProvider"; -import { GiftIcon, PlusIcon, MoreVertical, Trash2 } from "lucide-react"; +import { GiftIcon, PlusIcon, MoreVertical, Trash2, Edit } from "lucide-react"; import { Skeleton } from "./ui/skeleton"; import { Alert, AlertDescription, AlertTitle } from "./ui/alert"; import { AlertTriangle } from "lucide-react"; @@ -38,6 +38,8 @@ import { CreateCustomProviderDialog } from "./CreateCustomProviderDialog"; export function ProviderSettingsGrid() { const navigate = useNavigate(); const [isDialogOpen, setIsDialogOpen] = useState(false); + const [editingProvider, setEditingProvider] = + useState(null); const [providerToDelete, setProviderToDelete] = useState(null); const { @@ -65,6 +67,11 @@ export function ProviderSettingsGrid() { } }; + const handleEditProvider = (provider: LanguageModelProvider) => { + setEditingProvider(provider); + setIsDialogOpen(true); + }; + if (isLoading) { return (

@@ -116,7 +123,7 @@ export function ProviderSettingsGrid() { className="p-4 cursor-pointer" onClick={() => handleProviderClick(provider.id)} > - + {provider.name} {isProviderSetup(provider.id) ? ( @@ -140,7 +147,7 @@ export function ProviderSettingsGrid() { {isCustom && (
e.stopPropagation()} > @@ -155,6 +162,15 @@ export function ProviderSettingsGrid() { +