diff --git a/src/components/JsonEditor.tsx b/src/components/JsonEditor.tsx index 66eb156..2213599 100644 --- a/src/components/JsonEditor.tsx +++ b/src/components/JsonEditor.tsx @@ -12,6 +12,7 @@ import { toast } from "sonner"; import { formatJSON } from "@/utils/formatters"; interface JsonEditorProps { + id?: string; value: string; onChange: (value: string) => void; placeholder?: string; @@ -19,7 +20,8 @@ interface JsonEditorProps { rows?: number; showValidation?: boolean; language?: "json" | "javascript"; - height?: string; + height?: string | number; + showMinimap?: boolean; // 添加此属性以防未来使用 } const JsonEditor: React.FC = ({ @@ -116,8 +118,15 @@ const JsonEditor: React.FC = ({ }); // 使用 theme 定义尺寸和字体样式 + const heightValue = height + ? typeof height === "number" + ? `${height}px` + : height + : undefined; const sizingTheme = EditorView.theme({ - "&": height ? { height } : { minHeight: `${minHeightPx}px` }, + "&": heightValue + ? { height: heightValue } + : { minHeight: `${minHeightPx}px` }, ".cm-scroller": { overflow: "auto" }, ".cm-content": { fontFamily: diff --git a/src/components/UsageFooter.tsx b/src/components/UsageFooter.tsx index 6d3039f..1909901 100644 --- a/src/components/UsageFooter.tsx +++ b/src/components/UsageFooter.tsx @@ -57,7 +57,7 @@ const UsageFooter: React.FC = ({ if (!usageEnabled || !usage) return null; // 错误状态 - if (!usage.success) { + if (!usage.success) { if (inline) { return (
@@ -132,7 +132,9 @@ const UsageFooter: React.FC = ({ {/* 剩余 */} {firstUsage.remaining !== undefined && ( - {t("usage.remaining")} + + {t("usage.remaining")} + = ({ {/* 单位 */} {firstUsage.unit && ( - {firstUsage.unit} + + {firstUsage.unit} + )} {/* 刷新按钮 */} diff --git a/src/components/UsageScriptModal.tsx b/src/components/UsageScriptModal.tsx index e679202..7118799 100644 --- a/src/components/UsageScriptModal.tsx +++ b/src/components/UsageScriptModal.tsx @@ -16,25 +16,25 @@ import { FullScreenPanel } from "@/components/common/FullScreenPanel"; import { cn } from "@/lib/utils"; interface UsageScriptModalProps { - provider: Provider; - appId: AppId; - isOpen: boolean; - onClose: () => void; - onSave: (script: UsageScript) => void; + provider: Provider; + appId: AppId; + isOpen: boolean; + onClose: () => void; + onSave: (script: UsageScript) => void; } // 预设模板键名(用于国际化) const TEMPLATE_KEYS = { - CUSTOM: "custom", - GENERAL: "general", - NEW_API: "newapi", + CUSTOM: "custom", + GENERAL: "general", + NEW_API: "newapi", } as const; // 生成预设模板的函数(支持国际化) const generatePresetTemplates = ( - t: (key: string) => string, + t: (key: string) => string, ): Record => ({ - [TEMPLATE_KEYS.CUSTOM]: `({ + [TEMPLATE_KEYS.CUSTOM]: `({ request: { url: "", method: "GET", @@ -48,7 +48,7 @@ const generatePresetTemplates = ( } })`, - [TEMPLATE_KEYS.GENERAL]: `({ + [TEMPLATE_KEYS.GENERAL]: `({ request: { url: "{{baseUrl}}/user/balance", method: "GET", @@ -66,7 +66,7 @@ const generatePresetTemplates = ( } })`, - [TEMPLATE_KEYS.NEW_API]: `({ + [TEMPLATE_KEYS.NEW_API]: `({ request: { url: "{{baseUrl}}/api/user/self", method: "GET", @@ -96,606 +96,653 @@ const generatePresetTemplates = ( // 模板名称国际化键映射 const TEMPLATE_NAME_KEYS: Record = { - [TEMPLATE_KEYS.CUSTOM]: "usageScript.templateCustom", - [TEMPLATE_KEYS.GENERAL]: "usageScript.templateGeneral", - [TEMPLATE_KEYS.NEW_API]: "usageScript.templateNewAPI", + [TEMPLATE_KEYS.CUSTOM]: "usageScript.templateCustom", + [TEMPLATE_KEYS.GENERAL]: "usageScript.templateGeneral", + [TEMPLATE_KEYS.NEW_API]: "usageScript.templateNewAPI", }; const UsageScriptModal: React.FC = ({ - provider, - appId, - isOpen, - onClose, - onSave, + provider, + appId, + isOpen, + onClose, + onSave, }) => { - const { t } = useTranslation(); + const { t } = useTranslation(); - // 生成带国际化的预设模板 - const PRESET_TEMPLATES = generatePresetTemplates(t); - - const [script, setScript] = useState(() => { - return ( - provider.meta?.usage_script || { - enabled: false, - language: "javascript", - code: PRESET_TEMPLATES[TEMPLATE_KEYS.GENERAL], - timeout: 10, - } - ); - }); - - const [testing, setTesting] = useState(false); - - // 🔧 失焦时的验证(严格)- 仅确保有效整数 - const validateTimeout = (value: string): number => { - const num = Number(value); - if (isNaN(num) || value.trim() === "") { - return 10; - } - if (!Number.isInteger(num)) { - toast.warning( - t("usageScript.timeoutMustBeInteger") || "超时时间必须为整数", - ); - } - if (num < 0) { - toast.error( - t("usageScript.timeoutCannotBeNegative") || "超时时间不能为负数", - ); - return 10; - } - return Math.floor(num); - }; - - // 🔧 失焦时的验证(严格)- 自动查询间隔 - const validateAndClampInterval = (value: string): number => { - const num = Number(value); - if (isNaN(num) || value.trim() === "") { - return 0; - } - if (!Number.isInteger(num)) { - toast.warning( - t("usageScript.intervalMustBeInteger") || "自动查询间隔必须为整数", - ); - } - if (num < 0) { - toast.error( - t("usageScript.intervalCannotBeNegative") || "自动查询间隔不能为负数", - ); - return 0; - } - const clamped = Math.max(0, Math.min(1440, Math.floor(num))); - if (clamped !== num && num > 0) { - toast.info( - t("usageScript.intervalAdjusted", { value: clamped }) || - `自动查询间隔已调整为 ${clamped} 分钟`, - ); - } - return clamped; - }; - - const [selectedTemplate, setSelectedTemplate] = useState( - () => { - const existingScript = provider.meta?.usage_script; - if (existingScript?.accessToken || existingScript?.userId) { - return TEMPLATE_KEYS.NEW_API; - } - return null; - }, - ); - - const [showApiKey, setShowApiKey] = useState(false); - const [showAccessToken, setShowAccessToken] = useState(false); - - const handleSave = () => { - if (script.enabled && !script.code.trim()) { - toast.error(t("usageScript.scriptEmpty")); - return; - } - if (script.enabled && !script.code.includes("return")) { - toast.error(t("usageScript.mustHaveReturn"), { duration: 5000 }); - return; - } - onSave(script); - onClose(); - }; - - const handleTest = async () => { - setTesting(true); - try { - const result = await usageApi.testScript( - provider.id, - appId, - script.code, - script.timeout, - script.apiKey, - script.baseUrl, - script.accessToken, - script.userId, - ); - if (result.success && result.data && result.data.length > 0) { - const summary = result.data - .map((plan) => { - const planInfo = plan.planName ? `[${plan.planName}]` : ""; - return `${planInfo} ${t("usage.remaining")} ${plan.remaining} ${plan.unit}`; - }) - .join(", "); - toast.success(`${t("usageScript.testSuccess")}${summary}`, { - duration: 3000, - }); - } else { - toast.error( - `${t("usageScript.testFailed")}: ${result.error || t("endpointTest.noResult")}`, - { - duration: 5000, - }, - ); - } - } catch (error: any) { - toast.error( - `${t("usageScript.testFailed")}: ${error?.message || t("common.unknown")}`, - { - duration: 5000, - }, - ); - } finally { - setTesting(false); - } - }; - - const handleFormat = async () => { - try { - const formatted = await prettier.format(script.code, { - parser: "babel", - plugins: [parserBabel as any, pluginEstree as any], - semi: true, - singleQuote: false, - tabWidth: 2, - printWidth: 80, - }); - setScript({ ...script, code: formatted.trim() }); - toast.success(t("usageScript.formatSuccess"), { duration: 1000 }); - } catch (error: any) { - toast.error( - `${t("usageScript.formatFailed")}: ${error?.message || t("jsonEditor.invalidJson")}`, - { - duration: 3000, - }, - ); - } - }; - - const handleUsePreset = (presetName: string) => { - const preset = PRESET_TEMPLATES[presetName]; - if (preset) { - if (presetName === TEMPLATE_KEYS.CUSTOM) { - setScript({ - ...script, - code: preset, - apiKey: undefined, - baseUrl: undefined, - accessToken: undefined, - userId: undefined, - }); - } else if (presetName === TEMPLATE_KEYS.GENERAL) { - setScript({ - ...script, - code: preset, - accessToken: undefined, - userId: undefined, - }); - } else if (presetName === TEMPLATE_KEYS.NEW_API) { - setScript({ - ...script, - code: preset, - apiKey: undefined, - }); - } - setSelectedTemplate(presetName); - } - }; - - const shouldShowCredentialsConfig = - selectedTemplate === TEMPLATE_KEYS.GENERAL || - selectedTemplate === TEMPLATE_KEYS.NEW_API; - - const footer = ( - <> -
- - -
- -
- - -
- - ); + // 生成带国际化的预设模板 + const PRESET_TEMPLATES = generatePresetTemplates(t); + const [script, setScript] = useState(() => { return ( - { + const num = Number(value); + if (isNaN(num) || value.trim() === "") { + return 10; + } + if (!Number.isInteger(num)) { + toast.warning( + t("usageScript.timeoutMustBeInteger") || "超时时间必须为整数", + ); + } + if (num < 0) { + toast.error( + t("usageScript.timeoutCannotBeNegative") || "超时时间不能为负数", + ); + return 10; + } + return Math.floor(num); + }; + + // 🔧 失焦时的验证(严格)- 自动查询间隔 + const validateAndClampInterval = (value: string): number => { + const num = Number(value); + if (isNaN(num) || value.trim() === "") { + return 0; + } + if (!Number.isInteger(num)) { + toast.warning( + t("usageScript.intervalMustBeInteger") || "自动查询间隔必须为整数", + ); + } + if (num < 0) { + toast.error( + t("usageScript.intervalCannotBeNegative") || "自动查询间隔不能为负数", + ); + return 0; + } + const clamped = Math.max(0, Math.min(1440, Math.floor(num))); + if (clamped !== num && num > 0) { + toast.info( + t("usageScript.intervalAdjusted", { value: clamped }) || + `自动查询间隔已调整为 ${clamped} 分钟`, + ); + } + return clamped; + }; + + const [selectedTemplate, setSelectedTemplate] = useState( + () => { + const existingScript = provider.meta?.usage_script; + if (existingScript?.accessToken || existingScript?.userId) { + return TEMPLATE_KEYS.NEW_API; + } + return null; + }, + ); + + const [showApiKey, setShowApiKey] = useState(false); + const [showAccessToken, setShowAccessToken] = useState(false); + + const handleSave = () => { + if (script.enabled && !script.code.trim()) { + toast.error(t("usageScript.scriptEmpty")); + return; + } + if (script.enabled && !script.code.includes("return")) { + toast.error(t("usageScript.mustHaveReturn"), { duration: 5000 }); + return; + } + onSave(script); + onClose(); + }; + + const handleTest = async () => { + setTesting(true); + try { + const result = await usageApi.testScript( + provider.id, + appId, + script.code, + script.timeout, + script.apiKey, + script.baseUrl, + script.accessToken, + script.userId, + ); + if (result.success && result.data && result.data.length > 0) { + const summary = result.data + .map((plan) => { + const planInfo = plan.planName ? `[${plan.planName}]` : ""; + return `${planInfo} ${t("usage.remaining")} ${plan.remaining} ${plan.unit}`; + }) + .join(", "); + toast.success(`${t("usageScript.testSuccess")}${summary}`, { + duration: 3000, + }); + } else { + toast.error( + `${t("usageScript.testFailed")}: ${result.error || t("endpointTest.noResult")}`, + { + duration: 5000, + }, + ); + } + } catch (error: any) { + toast.error( + `${t("usageScript.testFailed")}: ${error?.message || t("common.unknown")}`, + { + duration: 5000, + }, + ); + } finally { + setTesting(false); + } + }; + + const handleFormat = async () => { + try { + const formatted = await prettier.format(script.code, { + parser: "babel", + plugins: [parserBabel as any, pluginEstree as any], + semi: true, + singleQuote: false, + tabWidth: 2, + printWidth: 80, + }); + setScript({ ...script, code: formatted.trim() }); + toast.success(t("usageScript.formatSuccess"), { duration: 1000 }); + } catch (error: any) { + toast.error( + `${t("usageScript.formatFailed")}: ${error?.message || t("jsonEditor.invalidJson")}`, + { + duration: 3000, + }, + ); + } + }; + + const handleUsePreset = (presetName: string) => { + const preset = PRESET_TEMPLATES[presetName]; + if (preset) { + if (presetName === TEMPLATE_KEYS.CUSTOM) { + setScript({ + ...script, + code: preset, + apiKey: undefined, + baseUrl: undefined, + accessToken: undefined, + userId: undefined, + }); + } else if (presetName === TEMPLATE_KEYS.GENERAL) { + setScript({ + ...script, + code: preset, + accessToken: undefined, + userId: undefined, + }); + } else if (presetName === TEMPLATE_KEYS.NEW_API) { + setScript({ + ...script, + code: preset, + apiKey: undefined, + }); + } + setSelectedTemplate(presetName); + } + }; + + const shouldShowCredentialsConfig = + selectedTemplate === TEMPLATE_KEYS.GENERAL || + selectedTemplate === TEMPLATE_KEYS.NEW_API; + + const footer = ( + <> +
+ + +
+ +
+ + +
+ + ); + + return ( + +
+
+

+ {t("usageScript.enableUsageQuery")} +

+

+ {t("usageScript.autoQueryIntervalHint")} +

+
+ + setScript({ ...script, enabled: checked }) + } + aria-label={t("usageScript.enableUsageQuery")} + /> +
+ + {script.enabled && ( +
+ {/* 预设模板选择 */} +
+
+ + + {t("usageScript.variablesHint")} + +
+
+ {Object.keys(PRESET_TEMPLATES).map((name) => { + const isSelected = selectedTemplate === name; + return ( + + ); + })}
- {script.enabled && ( -
- {/* 预设模板选择 */} -
-
- - - {t("usageScript.variablesHint")} - -
-
- {Object.keys(PRESET_TEMPLATES).map((name) => { - const isSelected = selectedTemplate === name; - return ( - - ); - })} + {/* 凭证配置 */} + {shouldShowCredentialsConfig && ( +
+

+ {t("usageScript.credentialsConfig")} +

+ +
+ {selectedTemplate === TEMPLATE_KEYS.GENERAL && ( + <> +
+ +
+ + setScript({ ...script, apiKey: e.target.value }) + } + placeholder="sk-xxxxx" + autoComplete="off" + className="bg-card border-border-default" + /> + {script.apiKey && ( + + )}
+
- {/* 凭证配置 */} - {shouldShowCredentialsConfig && ( -
-

- {t("usageScript.credentialsConfig")} -

- -
- {selectedTemplate === TEMPLATE_KEYS.GENERAL && ( - <> -
- -
- - setScript({ ...script, apiKey: e.target.value }) - } - placeholder="sk-xxxxx" - autoComplete="off" - className="bg-card border-border-default" - /> - {script.apiKey && ( - - )} -
-
- -
- - - setScript({ ...script, baseUrl: e.target.value }) - } - placeholder="https://api.example.com" - autoComplete="off" - className="bg-card border-border-default" - /> -
- - )} - - {selectedTemplate === TEMPLATE_KEYS.NEW_API && ( - <> -
- - - setScript({ ...script, baseUrl: e.target.value }) - } - placeholder="https://api.newapi.com" - autoComplete="off" - className="bg-card border-border-default" - /> -
- -
- -
- - setScript({ ...script, accessToken: e.target.value }) - } - placeholder={t("usageScript.accessTokenPlaceholder")} - autoComplete="off" - className="bg-card border-border-default" - /> - {script.accessToken && ( - - )} -
-
- -
- - - setScript({ ...script, userId: e.target.value }) - } - placeholder={t("usageScript.userIdPlaceholder")} - autoComplete="off" - className="bg-card border-border-default" - /> -
- - )} -
-
- )} -
- - {/* 脚本配置 */} -
-
-

- {t("usageScript.scriptConfig")} -

-

- {t("usageScript.variablesHint")} -

-
- -
-
- - { - setScript({ - ...script, - request: { ...script.request, url: e.target.value }, - }); - }} - placeholder={t("usageScript.requestUrlPlaceholder")} - className="bg-card border-border-default" - /> -
- -
-
- - { - setScript({ - ...script, - request: { - ...script.request, - method: e.target.value.toUpperCase(), - }, - }); - }} - placeholder="GET / POST" - className="bg-card border-border-default" - /> -
- -
- - - setScript({ - ...script, - timeout: validateTimeout(e.target.value), - }) - } - onBlur={(e) => - setScript({ - ...script, - timeout: validateTimeout(e.target.value), - }) - } - className="bg-card border-border-default" - /> -
-
- -
- - { - try { - const parsed = JSON.parse(value || "{}"); - setScript({ - ...script, - request: { ...script.request, headers: parsed }, - }); - } catch (error) { - console.error("Invalid headers JSON", error); - } - }} - height={180} - /> -
- -
- - { - try { - const parsed = value?.trim() === "" ? undefined : JSON.parse(value); - setScript({ - ...script, - request: { ...script.request, body: parsed }, - }); - } catch (error) { - toast.error( - t("usageScript.invalidJson") || "Body 必须是合法 JSON", - ); - } - }} - height={220} - /> -
- -
- - - setScript({ - ...script, - autoIntervalMinutes: validateAndClampInterval(e.target.value), - }) - } - onBlur={(e) => - setScript({ - ...script, - autoIntervalMinutes: validateAndClampInterval(e.target.value), - }) - } - className="bg-card border-border-default" - /> -

- {t("usageScript.autoQueryIntervalHint")} -

-
-
-
- - {/* 提取器代码 */} -
-
- -
- {t("usageScript.extractorHint")} -
-
- setScript({ ...script, code: value })} - height={480} - language="javascript" - showMinimap={false} +
+ + + setScript({ ...script, baseUrl: e.target.value }) + } + placeholder="https://api.example.com" + autoComplete="off" + className="bg-card border-border-default" /> -
+
+ + )} - {/* 帮助信息 */} -
-

{t("usageScript.scriptHelp")}

-
-
- {t("usageScript.configFormat")} -
-                                    {`({
+                  {selectedTemplate === TEMPLATE_KEYS.NEW_API && (
+                    <>
+                      
+ + + setScript({ ...script, baseUrl: e.target.value }) + } + placeholder="https://api.newapi.com" + autoComplete="off" + className="bg-card border-border-default" + /> +
+ +
+ +
+ + setScript({ + ...script, + accessToken: e.target.value, + }) + } + placeholder={t( + "usageScript.accessTokenPlaceholder", + )} + autoComplete="off" + className="bg-card border-border-default" + /> + {script.accessToken && ( + + )} +
+
+ +
+ + + setScript({ ...script, userId: e.target.value }) + } + placeholder={t("usageScript.userIdPlaceholder")} + autoComplete="off" + className="bg-card border-border-default" + /> +
+ + )} +
+
+ )} +
+ + {/* 脚本配置 */} +
+
+

+ {t("usageScript.scriptConfig")} +

+

+ {t("usageScript.variablesHint")} +

+
+ +
+
+ + { + setScript({ + ...script, + request: { ...script.request, url: e.target.value }, + }); + }} + placeholder={t("usageScript.requestUrlPlaceholder")} + className="bg-card border-border-default" + /> +
+ +
+
+ + { + setScript({ + ...script, + request: { + ...script.request, + method: e.target.value.toUpperCase(), + }, + }); + }} + placeholder="GET / POST" + className="bg-card border-border-default" + /> +
+ +
+ + + setScript({ + ...script, + timeout: validateTimeout(e.target.value), + }) + } + onBlur={(e) => + setScript({ + ...script, + timeout: validateTimeout(e.target.value), + }) + } + className="bg-card border-border-default" + /> +
+
+ +
+ + { + try { + const parsed = JSON.parse(value || "{}"); + setScript({ + ...script, + request: { ...script.request, headers: parsed }, + }); + } catch (error) { + console.error("Invalid headers JSON", error); + } + }} + height={180} + /> +
+ +
+ + { + try { + const parsed = + value?.trim() === "" ? undefined : JSON.parse(value); + setScript({ + ...script, + request: { ...script.request, body: parsed }, + }); + } catch (error) { + toast.error( + t("usageScript.invalidJson") || "Body 必须是合法 JSON", + ); + } + }} + height={220} + /> +
+ +
+ + + setScript({ + ...script, + autoIntervalMinutes: validateAndClampInterval( + e.target.value, + ), + }) + } + onBlur={(e) => + setScript({ + ...script, + autoIntervalMinutes: validateAndClampInterval( + e.target.value, + ), + }) + } + className="bg-card border-border-default" + /> +

+ {t("usageScript.autoQueryIntervalHint")} +

+
+
+
+ + {/* 提取器代码 */} +
+
+ +
+ {t("usageScript.extractorHint")} +
+
+ setScript({ ...script, code: value })} + height={480} + language="javascript" + showMinimap={false} + /> +
+ + {/* 帮助信息 */} +
+

{t("usageScript.scriptHelp")}

+
+
+ {t("usageScript.configFormat")} +
+                  {`({
   request: {
     url: "{{baseUrl}}/api/usage",
     method: "POST",
@@ -712,39 +759,42 @@ const UsageScriptModal: React.FC = ({
     };
   }
 })`}
-                                
-
+ +
-
- {t("usageScript.extractorFormat")} -
    -
  • {t("usageScript.fieldIsValid")}
  • -
  • {t("usageScript.fieldInvalidMessage")}
  • -
  • {t("usageScript.fieldRemaining")}
  • -
  • {t("usageScript.fieldUnit")}
  • -
  • {t("usageScript.fieldPlanName")}
  • -
  • {t("usageScript.fieldTotal")}
  • -
  • {t("usageScript.fieldUsed")}
  • -
  • {t("usageScript.fieldExtra")}
  • -
-
+
+ {t("usageScript.extractorFormat")} +
    +
  • {t("usageScript.fieldIsValid")}
  • +
  • {t("usageScript.fieldInvalidMessage")}
  • +
  • {t("usageScript.fieldRemaining")}
  • +
  • {t("usageScript.fieldUnit")}
  • +
  • {t("usageScript.fieldPlanName")}
  • +
  • {t("usageScript.fieldTotal")}
  • +
  • {t("usageScript.fieldUsed")}
  • +
  • {t("usageScript.fieldExtra")}
  • +
+
-
- {t("usageScript.tips")} -
    -
  • - {t("usageScript.tip1", { apiKey: "{{apiKey}}", baseUrl: "{{baseUrl}}" })} -
  • -
  • {t("usageScript.tip2")}
  • -
  • {t("usageScript.tip3")}
  • -
-
-
-
-
- )} - - ); +
+ {t("usageScript.tips")} +
    +
  • + {t("usageScript.tip1", { + apiKey: "{{apiKey}}", + baseUrl: "{{baseUrl}}", + })} +
  • +
  • {t("usageScript.tip2")}
  • +
  • {t("usageScript.tip3")}
  • +
+
+
+
+
+ )} + + ); }; export default UsageScriptModal; diff --git a/src/components/env/EnvWarningBanner.tsx b/src/components/env/EnvWarningBanner.tsx index 3a06db9..7a2ec5d 100644 --- a/src/components/env/EnvWarningBanner.tsx +++ b/src/components/env/EnvWarningBanner.tsx @@ -229,8 +229,8 @@ export function EnvWarningBanner({ {isDeleting ? t("env.actions.deleting") : t("env.actions.deleteSelected", { - count: selectedConflicts.size, - })} + count: selectedConflicts.size, + })}
diff --git a/src/components/mcp/McpFormModal.tsx b/src/components/mcp/McpFormModal.tsx index 513c9de..43d1421 100644 --- a/src/components/mcp/McpFormModal.tsx +++ b/src/components/mcp/McpFormModal.tsx @@ -1,13 +1,7 @@ import React, { useMemo, useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; -import { - Save, - Plus, - AlertCircle, - ChevronDown, - ChevronUp, -} from "lucide-react"; +import { Save, Plus, AlertCircle, ChevronDown, ChevronUp } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; @@ -415,11 +409,7 @@ const McpFormModal: React.FC = ({ return ( <> - + {/* 预设选择(仅新增时展示) */} {!isEditing && (
@@ -430,10 +420,11 @@ const McpFormModal: React.FC = ({ @@ -444,10 +435,11 @@ const McpFormModal: React.FC = ({ key={preset.id} type="button" onClick={() => applyPreset(idx)} - className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${selectedPreset === idx - ? "bg-emerald-500 text-white dark:bg-emerald-600" - : "bg-accent text-muted-foreground hover:bg-accent/80" - }`} + className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${ + selectedPreset === idx + ? "bg-emerald-500 text-white dark:bg-emerald-600" + : "bg-accent text-muted-foreground hover:bg-accent/80" + }`} title={t(descriptionKey)} > {preset.id} @@ -555,11 +547,7 @@ const McpFormModal: React.FC = ({ onClick={() => setShowMetadata(!showMetadata)} className="flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors" > - {showMetadata ? ( - - ) : ( - - )} + {showMetadata ? : } {t("mcp.form.additionalInfo")}
@@ -621,9 +609,7 @@ const McpFormModal: React.FC = ({
{(isEditing || selectedPreset === -1) && (