feat(mcp): show inline duplicate ID error and block submit in add mode

- Display red hint next to title when ID exists

- Disable Add/Save button and prevent submit on duplicate

- Accept existing IDs via prop for real-time validation

- Remove overwrite confirmation dialog on add

- i18n: add duplicate-ID error strings and remove unused overwrite prompt

- files:

  - src/components/mcp/McpFormModal.tsx

  - src/components/mcp/McpPanel.tsx

  - src/i18n/locales/en.json

  - src/i18n/locales/zh.json
This commit is contained in:
Jason
2025-10-10 11:17:40 +08:00
parent eb8d9352c8
commit 7493f3f9dd
4 changed files with 32 additions and 5 deletions

View File

@@ -11,6 +11,7 @@ interface McpFormModalProps {
initialData?: McpServer;
onSave: (id: string, server: McpServer) => Promise<void>;
onClose: () => void;
existingIds?: string[];
}
/**
@@ -38,6 +39,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
initialData,
onSave,
onClose,
existingIds = [],
}) => {
const { t } = useTranslation();
const [formId, setFormId] = useState(editingId || "");
@@ -50,10 +52,19 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
const [jsonError, setJsonError] = useState("");
const [saving, setSaving] = useState(false);
const [isWizardOpen, setIsWizardOpen] = useState(false);
const [idError, setIdError] = useState("");
// 编辑模式下禁止修改 ID
const isEditing = !!editingId;
const handleIdChange = (value: string) => {
setFormId(value);
if (!isEditing) {
const exists = existingIds.includes(value.trim());
setIdError(exists ? t("mcp.error.idExists") : "");
}
};
const handleJsonChange = (value: string) => {
setFormJson(value);
@@ -104,6 +115,12 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
return;
}
// 新增模式:阻止提交重名 ID
if (!isEditing && existingIds.includes(formId.trim())) {
setIdError(t("mcp.error.idExists"));
return;
}
// 验证 JSON
const currentJsonError = validateJson(formJson);
setJsonError(currentJsonError);
@@ -186,14 +203,21 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
<div className="p-6 space-y-4">
{/* ID (标题) */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{t("mcp.form.title")} <span className="text-red-500">*</span>
</label>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{t("mcp.form.title")} <span className="text-red-500">*</span>
</label>
{!isEditing && idError && (
<span className="text-xs text-red-500 dark:text-red-400">
{idError}
</span>
)}
</div>
<input
className={inputStyles.text}
placeholder={t("mcp.form.titlePlaceholder")}
value={formId}
onChange={(e) => setFormId(e.target.value)}
onChange={(e) => handleIdChange(e.target.value)}
disabled={isEditing}
/>
</div>
@@ -247,7 +271,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
</button>
<button
onClick={handleSubmit}
disabled={saving}
disabled={saving || (!isEditing && !!idError)}
className={buttonStyles.primary}
>
<Save size={16} />

View File

@@ -292,6 +292,7 @@ const McpPanel: React.FC<McpPanelProps> = ({ onClose, onNotify }) => {
<McpFormModal
editingId={editingId || undefined}
initialData={editingId ? servers[editingId] : undefined}
existingIds={Object.keys(servers)}
onSave={handleSave}
onClose={handleCloseForm}
/>

View File

@@ -316,6 +316,7 @@
},
"error": {
"idRequired": "Please enter identifier",
"idExists": "Identifier already exists. Please choose another.",
"jsonInvalid": "Invalid JSON format",
"commandRequired": "Please enter command",
"singleServerObjectRequired": "Please paste a single MCP server object (do not include top-level mcpServers)",

View File

@@ -316,6 +316,7 @@
},
"error": {
"idRequired": "请填写标识",
"idExists": "该标识已存在,请更换",
"jsonInvalid": "JSON 格式错误,请检查",
"commandRequired": "请填写命令",
"singleServerObjectRequired": "此处只需单个服务器对象,请不要粘贴包含 mcpServers 的整份配置",