277 lines
9.3 KiB
JavaScript
277 lines
9.3 KiB
JavaScript
"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 [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 || "",
|
|
});
|
|
}
|
|
setTestResult(null);
|
|
setValidationResult(null);
|
|
}
|
|
}, [connection]);
|
|
|
|
const isOAuth = connection?.authType === "oauth";
|
|
const isAzure = connection?.provider === "azure";
|
|
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 } : {}),
|
|
}),
|
|
});
|
|
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 } : {}),
|
|
}),
|
|
});
|
|
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,
|
|
};
|
|
}
|
|
|
|
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 && (
|
|
<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,
|
|
};
|
|
|