| "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: "", |
| }); |
| |
| 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; |
| } |
| } |
| |
| |
| 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, |
| }; |
|
|
|
|