Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { useState, useEffect } from "react"; | |
| import PropTypes from "prop-types"; | |
| import Modal from "@/shared/components/Modal"; | |
| import Input from "@/shared/components/Input"; | |
| import Button from "@/shared/components/Button"; | |
| import Badge from "@/shared/components/Badge"; | |
| import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers"; | |
| export default function EditConnectionModal({ isOpen, connection, proxyPools, onSave, onClose }) { | |
| const [formData, setFormData] = useState({ | |
| name: "", | |
| priority: 1, | |
| apiKey: "", | |
| }); | |
| const [azureData, setAzureData] = useState({ | |
| azureEndpoint: "", | |
| apiVersion: "2024-10-01-preview", | |
| deployment: "", | |
| organization: "", | |
| }); | |
| const [cloudflareData, setCloudflareData] = useState({ accountId: "" }); | |
| const [testing, setTesting] = useState(false); | |
| const [testResult, setTestResult] = useState(null); | |
| const [validating, setValidating] = useState(false); | |
| const [validationResult, setValidationResult] = useState(null); | |
| const [saving, setSaving] = useState(false); | |
| useEffect(() => { | |
| if (connection) { | |
| setFormData({ | |
| name: connection.name || "", | |
| priority: connection.priority || 1, | |
| apiKey: "", | |
| }); | |
| // Load Azure-specific data if present | |
| if (connection.provider === "azure" && connection.providerSpecificData) { | |
| setAzureData({ | |
| azureEndpoint: connection.providerSpecificData.azureEndpoint || "", | |
| apiVersion: connection.providerSpecificData.apiVersion || "2024-10-01-preview", | |
| deployment: connection.providerSpecificData.deployment || "", | |
| organization: connection.providerSpecificData.organization || "", | |
| }); | |
| } | |
| if (connection.provider === "cloudflare-ai" && connection.providerSpecificData) { | |
| setCloudflareData({ accountId: connection.providerSpecificData.accountId || "" }); | |
| } | |
| setTestResult(null); | |
| setValidationResult(null); | |
| } | |
| }, [connection]); | |
| const isOAuth = connection?.authType === "oauth"; | |
| const isAzure = connection?.provider === "azure"; | |
| const isCloudflareAi = connection?.provider === "cloudflare-ai"; | |
| const isCompatible = connection | |
| ? (isOpenAICompatibleProvider(connection.provider) || isAnthropicCompatibleProvider(connection.provider)) | |
| : false; | |
| const handleTest = async () => { | |
| if (!connection?.provider) return; | |
| setTesting(true); | |
| setTestResult(null); | |
| try { | |
| const res = await fetch(`/api/providers/${connection.id}/test`, { method: "POST" }); | |
| const data = await res.json(); | |
| setTestResult(data.valid ? "success" : "failed"); | |
| } catch { | |
| setTestResult("failed"); | |
| } finally { | |
| setTesting(false); | |
| } | |
| }; | |
| const handleValidate = async () => { | |
| if (!connection?.provider || !formData.apiKey) return; | |
| setValidating(true); | |
| setValidationResult(null); | |
| try { | |
| const res = await fetch("/api/providers/validate", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| provider: connection.provider, | |
| apiKey: formData.apiKey, | |
| ...(isAzure ? { providerSpecificData: azureData } : {}), | |
| ...(isCloudflareAi ? { providerSpecificData: cloudflareData } : {}), | |
| }), | |
| }); | |
| const data = await res.json(); | |
| setValidationResult(data.valid ? "success" : "failed"); | |
| } catch { | |
| setValidationResult("failed"); | |
| } finally { | |
| setValidating(false); | |
| } | |
| }; | |
| const handleSubmit = async () => { | |
| if (!connection) return; | |
| setSaving(true); | |
| try { | |
| const updates = { | |
| name: formData.name, | |
| priority: formData.priority, | |
| }; | |
| if (!isOAuth && formData.apiKey) { | |
| updates.apiKey = formData.apiKey; | |
| let isValid = validationResult === "success"; | |
| if (!isValid) { | |
| try { | |
| setValidating(true); | |
| setValidationResult(null); | |
| const res = await fetch("/api/providers/validate", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| provider: connection.provider, | |
| apiKey: formData.apiKey, | |
| ...(isAzure ? { providerSpecificData: azureData } : {}), | |
| ...(isCloudflareAi ? { providerSpecificData: cloudflareData } : {}), | |
| }), | |
| }); | |
| const data = await res.json(); | |
| isValid = !!data.valid; | |
| setValidationResult(isValid ? "success" : "failed"); | |
| } catch { | |
| setValidationResult("failed"); | |
| } finally { | |
| setValidating(false); | |
| } | |
| } | |
| if (isValid) { | |
| updates.testStatus = "active"; | |
| updates.lastError = null; | |
| updates.lastErrorAt = null; | |
| } | |
| } | |
| // Add Azure-specific data if this is an Azure connection | |
| if (isAzure) { | |
| updates.providerSpecificData = { | |
| azureEndpoint: azureData.azureEndpoint, | |
| apiVersion: azureData.apiVersion, | |
| deployment: azureData.deployment, | |
| organization: azureData.organization, | |
| }; | |
| } | |
| if (isCloudflareAi) { | |
| updates.providerSpecificData = { accountId: cloudflareData.accountId }; | |
| } | |
| await onSave(updates); | |
| } finally { | |
| setSaving(false); | |
| } | |
| }; | |
| if (!connection) return null; | |
| return ( | |
| <Modal isOpen={isOpen} title="Edit Connection" onClose={onClose}> | |
| <div className="flex flex-col gap-4"> | |
| <Input | |
| label="Name" | |
| value={formData.name} | |
| onChange={(e) => setFormData({ ...formData, name: e.target.value })} | |
| placeholder={isOAuth ? "Account name" : "Production Key"} | |
| /> | |
| {isOAuth && connection.email && ( | |
| <div className="bg-sidebar/50 p-3 rounded-lg"> | |
| <p className="text-sm text-text-muted mb-1">Email</p> | |
| <p className="font-medium">{connection.email}</p> | |
| </div> | |
| )} | |
| <Input | |
| label="Priority" | |
| type="number" | |
| value={formData.priority} | |
| onChange={(e) => setFormData({ ...formData, priority: Number.parseInt(e.target.value, 10) || 1 })} | |
| /> | |
| {!isOAuth && ( | |
| <> | |
| <div className="flex gap-2"> | |
| <Input | |
| label="API Key" | |
| type="password" | |
| value={formData.apiKey} | |
| onChange={(e) => setFormData({ ...formData, apiKey: e.target.value })} | |
| placeholder="Enter new API key" | |
| hint="Leave blank to keep the current API key." | |
| className="flex-1" | |
| /> | |
| <div className="pt-6"> | |
| <Button onClick={handleValidate} disabled={!formData.apiKey || validating || saving} variant="secondary"> | |
| {validating ? "Checking..." : "Check"} | |
| </Button> | |
| </div> | |
| </div> | |
| {validationResult && ( | |
| <Badge variant={validationResult === "success" ? "success" : "error"}> | |
| {validationResult === "success" ? "Valid" : "Invalid"} | |
| </Badge> | |
| )} | |
| </> | |
| )} | |
| {isAzure && ( | |
| <div className="bg-sidebar/50 p-4 rounded-lg border border-accent/20"> | |
| <h3 className="font-semibold mb-3 text-sm">Azure OpenAI Configuration</h3> | |
| <div className="flex flex-col gap-3"> | |
| <Input | |
| label="Azure Endpoint" | |
| value={azureData.azureEndpoint} | |
| onChange={(e) => setAzureData({ ...azureData, azureEndpoint: e.target.value })} | |
| placeholder="https://your-resource.openai.azure.com" | |
| hint="Your Azure OpenAI resource endpoint URL" | |
| /> | |
| <Input | |
| label="Deployment Name" | |
| value={azureData.deployment} | |
| onChange={(e) => setAzureData({ ...azureData, deployment: e.target.value })} | |
| placeholder="gpt-4" | |
| hint="The deployment name in your Azure resource" | |
| /> | |
| <Input | |
| label="API Version" | |
| value={azureData.apiVersion} | |
| onChange={(e) => setAzureData({ ...azureData, apiVersion: e.target.value })} | |
| placeholder="2024-10-01-preview" | |
| hint="Azure OpenAI API version to use" | |
| /> | |
| <Input | |
| label="Organization" | |
| value={azureData.organization} | |
| onChange={(e) => setAzureData({ ...azureData, organization: e.target.value })} | |
| placeholder="Organization ID" | |
| hint="Required for billing" | |
| /> | |
| </div> | |
| </div> | |
| )} | |
| {!isCompatible && !isAzure && !isCloudflareAi && ( | |
| <div className="flex items-center gap-3"> | |
| <Button onClick={handleTest} variant="secondary" disabled={testing}> | |
| {testing ? "Testing..." : "Test Connection"} | |
| </Button> | |
| {testResult && ( | |
| <Badge variant={testResult === "success" ? "success" : "error"}> | |
| {testResult === "success" ? "Valid" : "Failed"} | |
| </Badge> | |
| )} | |
| </div> | |
| )} | |
| <div className="flex gap-2"> | |
| <Button onClick={handleSubmit} fullWidth disabled={saving}>{saving ? "Saving..." : "Save"}</Button> | |
| <Button onClick={onClose} variant="ghost" fullWidth>Cancel</Button> | |
| </div> | |
| </div> | |
| </Modal> | |
| ); | |
| } | |
| EditConnectionModal.propTypes = { | |
| isOpen: PropTypes.bool.isRequired, | |
| connection: PropTypes.shape({ | |
| id: PropTypes.string, | |
| name: PropTypes.string, | |
| email: PropTypes.string, | |
| priority: PropTypes.number, | |
| authType: PropTypes.string, | |
| provider: PropTypes.string, | |
| providerSpecificData: PropTypes.object, | |
| }), | |
| proxyPools: PropTypes.arrayOf(PropTypes.shape({ | |
| id: PropTypes.string, | |
| name: PropTypes.string, | |
| })), | |
| onSave: PropTypes.func.isRequired, | |
| onClose: PropTypes.func.isRequired, | |
| }; | |