fix: check provider name to prevent duplicate name with different config

This commit is contained in:
HynoR
2025-08-07 00:56:30 +08:00
parent adfae3263a
commit 38bc747261
3 changed files with 51 additions and 5 deletions

View File

@@ -37,6 +37,7 @@ export function Providers() {
const [providerTemplates, setProviderTemplates] = useState<ProviderType[]>([]); const [providerTemplates, setProviderTemplates] = useState<ProviderType[]>([]);
const [showApiKey, setShowApiKey] = useState<Record<number, boolean>>({}); const [showApiKey, setShowApiKey] = useState<Record<number, boolean>>({});
const [apiKeyError, setApiKeyError] = useState<string | null>(null); const [apiKeyError, setApiKeyError] = useState<string | null>(null);
const [nameError, setNameError] = useState<string | null>(null);
const comboInputRef = useRef<HTMLInputElement>(null); const comboInputRef = useRef<HTMLInputElement>(null);
useEffect(() => { useEffect(() => {
@@ -100,6 +101,7 @@ export function Providers() {
[config.Providers.length]: false [config.Providers.length]: false
})); }));
setApiKeyError(null); setApiKeyError(null);
setNameError(null);
}; };
const handleEditProvider = (index: number) => { const handleEditProvider = (index: number) => {
@@ -113,17 +115,42 @@ export function Providers() {
[index]: false [index]: false
})); }));
setApiKeyError(null); setApiKeyError(null);
setNameError(null);
}; };
const handleSaveProvider = () => { const handleSaveProvider = () => {
if (!editingProviderData) return;
// Validate name
if (!editingProviderData.name || editingProviderData.name.trim() === '') {
setNameError(t("providers.name_required"));
return;
}
// Check for duplicate names (case-insensitive)
const trimmedName = editingProviderData.name.trim();
const isDuplicate = config.Providers.some((provider, index) => {
// For edit mode, skip checking the current provider being edited
if (!isNewProvider && index === editingProviderIndex) {
return false;
}
return provider.name.toLowerCase() === trimmedName.toLowerCase();
});
if (isDuplicate) {
setNameError(t("providers.name_duplicate"));
return;
}
// Validate API key // Validate API key
if (!editingProviderData || !editingProviderData.api_key || editingProviderData.api_key.trim() === '') { if (!editingProviderData.api_key || editingProviderData.api_key.trim() === '') {
setApiKeyError(t("providers.api_key_required")); setApiKeyError(t("providers.api_key_required"));
return; return;
} }
// Clear error if validation passes // Clear errors if validation passes
setApiKeyError(null); setApiKeyError(null);
setNameError(null);
if (editingProviderIndex !== null && editingProviderData) { if (editingProviderIndex !== null && editingProviderData) {
const newProviders = [...config.Providers]; const newProviders = [...config.Providers];
@@ -166,6 +193,7 @@ export function Providers() {
setEditingProviderData(null); setEditingProviderData(null);
setIsNewProvider(false); setIsNewProvider(false);
setApiKeyError(null); setApiKeyError(null);
setNameError(null);
}; };
const handleRemoveProvider = (index: number) => { const handleRemoveProvider = (index: number) => {
@@ -499,7 +527,21 @@ export function Providers() {
)} )}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="name">{t("providers.name")}</Label> <Label htmlFor="name">{t("providers.name")}</Label>
<Input id="name" value={editingProvider.name || ''} onChange={(e) => handleProviderChange(editingProviderIndex, 'name', e.target.value)} /> <Input
id="name"
value={editingProvider.name || ''}
onChange={(e) => {
handleProviderChange(editingProviderIndex, 'name', e.target.value);
// Clear name error when user starts typing
if (nameError) {
setNameError(null);
}
}}
className={nameError ? "border-red-500" : ""}
/>
{nameError && (
<p className="text-sm text-red-500">{nameError}</p>
)}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="api_base_url">{t("providers.api_base_url")}</Label> <Label htmlFor="api_base_url">{t("providers.api_base_url")}</Label>

View File

@@ -81,7 +81,9 @@
"import_from_template": "Import from template", "import_from_template": "Import from template",
"no_templates_found": "No templates found", "no_templates_found": "No templates found",
"select_template": "Select a template...", "select_template": "Select a template...",
"api_key_required": "API Key is required" "api_key_required": "API Key is required",
"name_required": "Name is required",
"name_duplicate": "A provider with this name already exists"
}, },
"router": { "router": {

View File

@@ -81,7 +81,9 @@
"import_from_template": "从模板导入", "import_from_template": "从模板导入",
"no_templates_found": "未找到模板", "no_templates_found": "未找到模板",
"select_template": "选择一个模板...", "select_template": "选择一个模板...",
"api_key_required": "API 密钥为必填项" "api_key_required": "API 密钥为必填项",
"name_required": "名称为必填项",
"name_duplicate": "已存在同名供应商"
}, },
"router": { "router": {