From d4f33224c6e82db7c1c7d6a4dceee7c4b685940d Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 16 Dec 2025 10:56:39 +0800 Subject: [PATCH] fix(ui): add close button to all success toasts --- src/App.tsx | 3 ++- src/components/DeepLinkImportDialog.tsx | 21 ++++++++++------ src/components/JsonEditor.tsx | 4 ++- src/components/UsageScriptModal.tsx | 6 ++++- src/components/env/EnvWarningBanner.tsx | 1 + src/components/mcp/McpFormModal.tsx | 2 +- src/components/mcp/UnifiedMcpPanel.tsx | 2 +- src/components/providers/ProviderActions.tsx | 4 +-- src/components/providers/ProviderCard.tsx | 21 ++++++++++------ .../proxy/AutoFailoverConfigPanel.tsx | 1 + .../proxy/CircuitBreakerConfigPanel.tsx | 2 +- src/components/proxy/FailoverQueueManager.tsx | 25 ++++++++++--------- src/components/proxy/ProxyPanel.tsx | 2 +- src/components/settings/AboutSection.tsx | 2 +- src/components/settings/SettingsPage.tsx | 2 +- src/components/skills/SkillsPage.tsx | 13 +++++++--- src/components/usage/ModelTestConfigPanel.tsx | 4 ++- src/components/usage/PricingEditModal.tsx | 1 + src/hooks/useDragSort.ts | 1 + src/hooks/useImportExport.ts | 2 ++ src/hooks/usePromptActions.ts | 12 ++++----- src/hooks/useProviderActions.ts | 1 + src/hooks/useProxyConfig.ts | 2 +- src/hooks/useProxyStatus.ts | 2 ++ src/hooks/useSettings.ts | 1 + src/hooks/useStreamCheck.ts | 1 + src/lib/api/deeplink.ts | 4 +-- src/lib/api/failover.ts | 11 ++++---- src/lib/query/mutations.ts | 1 + 29 files changed, 98 insertions(+), 56 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 7d4a84d55..41efe2640 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -142,6 +142,7 @@ function App() { if (migrated) { toast.success( t("migration.success", { defaultValue: "配置迁移成功" }), + { closeButton: true }, ); } } catch (error) { @@ -422,7 +423,7 @@ function App() { "text-xl font-semibold transition-colors", isProxyRunning && isTakeoverActive ? "text-emerald-500 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-300" - : "text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300" + : "text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300", )} > CC Switch diff --git a/src/components/DeepLinkImportDialog.tsx b/src/components/DeepLinkImportDialog.tsx index 5f6f9cc66..86e6b0e6f 100644 --- a/src/components/DeepLinkImportDialog.tsx +++ b/src/components/DeepLinkImportDialog.tsx @@ -32,7 +32,7 @@ export function DeepLinkImportDialog() { // 容错判断:MCP 导入结果可能缺少 type 字段 const isMcpImportResult = ( - value: unknown + value: unknown, ): value is { importedCount: number; importedIds: string[]; @@ -59,7 +59,7 @@ export function DeepLinkImportDialog() { if (event.payload.config || event.payload.configUrl) { try { const mergedRequest = await deeplinkApi.mergeDeeplinkConfig( - event.payload + event.payload, ); console.log("Config merged successfully:", mergedRequest); setRequest(mergedRequest); @@ -77,7 +77,7 @@ export function DeepLinkImportDialog() { } setIsOpen(true); - } + }, ); // Listen for deep link error events @@ -128,6 +128,7 @@ export function DeepLinkImportDialog() { description: t("deeplink.mcpImportSuccessDescription", { count: summary.importedCount, }), + closeButton: true, }); } }; @@ -142,18 +143,20 @@ export function DeepLinkImportDialog() { description: t("deeplink.importSuccessDescription", { name: request.name, }), + closeButton: true, }); } else if (result.type === "prompt") { // Prompts don't use React Query, trigger a custom event for refresh window.dispatchEvent( new CustomEvent("prompt-imported", { detail: { app: request.app }, - }) + }), ); toast.success(t("deeplink.promptImportSuccess"), { description: t("deeplink.promptImportSuccessDescription", { name: request.name, }), + closeButton: true, }); } else if (result.type === "mcp") { await refreshMcp(result); @@ -171,6 +174,7 @@ export function DeepLinkImportDialog() { description: t("deeplink.skillImportSuccessDescription", { repo: request.repo, }), + closeButton: true, }); } } else if (isMcpImportResult(result)) { @@ -185,6 +189,7 @@ export function DeepLinkImportDialog() { description: t("deeplink.importSuccessDescription", { name: request.name, }), + closeButton: true, }); } @@ -279,7 +284,7 @@ export function DeepLinkImportDialog() { const maskValue = (key: string, value: string): string => { const sensitiveKeys = ["TOKEN", "KEY", "SECRET", "PASSWORD"]; const isSensitive = sensitiveKeys.some((k) => - key.toUpperCase().includes(k) + key.toUpperCase().includes(k), ); if (isSensitive && value.length > 8) { return `${value.substring(0, 8)}${"*".repeat(12)}`; @@ -521,7 +526,7 @@ export function DeepLinkImportDialog() { {maskValue(key, String(value))} - ) + ), )} )} @@ -548,7 +553,7 @@ export function DeepLinkImportDialog() { {maskValue(key, String(value))} - ) + ), )} )} @@ -584,7 +589,7 @@ export function DeepLinkImportDialog() { {maskValue(key, String(value))} - ) + ), )} )} diff --git a/src/components/JsonEditor.tsx b/src/components/JsonEditor.tsx index 72972e578..860660252 100644 --- a/src/components/JsonEditor.tsx +++ b/src/components/JsonEditor.tsx @@ -234,7 +234,9 @@ const JsonEditor: React.FC = ({ try { const formatted = formatJSON(currentValue); onChange(formatted); - toast.success(t("common.formatSuccess", { defaultValue: "格式化成功" })); + toast.success(t("common.formatSuccess", { defaultValue: "格式化成功" }), { + closeButton: true, + }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); diff --git a/src/components/UsageScriptModal.tsx b/src/components/UsageScriptModal.tsx index b7ca6a107..c23dbb552 100644 --- a/src/components/UsageScriptModal.tsx +++ b/src/components/UsageScriptModal.tsx @@ -227,6 +227,7 @@ const UsageScriptModal: React.FC = ({ .join(", "); toast.success(`${t("usageScript.testSuccess")}${summary}`, { duration: 3000, + closeButton: true, }); } else { toast.error( @@ -259,7 +260,10 @@ const UsageScriptModal: React.FC = ({ printWidth: 80, }); setScript({ ...script, code: formatted.trim() }); - toast.success(t("usageScript.formatSuccess"), { duration: 1000 }); + toast.success(t("usageScript.formatSuccess"), { + duration: 1000, + closeButton: true, + }); } catch (error: any) { toast.error( `${t("usageScript.formatFailed")}: ${error?.message || t("jsonEditor.invalidJson")}`, diff --git a/src/components/env/EnvWarningBanner.tsx b/src/components/env/EnvWarningBanner.tsx index 7a2ec5d06..55c5621fa 100644 --- a/src/components/env/EnvWarningBanner.tsx +++ b/src/components/env/EnvWarningBanner.tsx @@ -79,6 +79,7 @@ export function EnvWarningBanner({ path: backupInfo.backupPath, }), duration: 5000, + closeButton: true, }); // 清空选择并通知父组件 diff --git a/src/components/mcp/McpFormModal.tsx b/src/components/mcp/McpFormModal.tsx index d75e5b301..5c608a4ff 100644 --- a/src/components/mcp/McpFormModal.tsx +++ b/src/components/mcp/McpFormModal.tsx @@ -391,7 +391,7 @@ const McpFormModal: React.FC = ({ } await upsertMutation.mutateAsync(entry); - toast.success(t("common.success")); + toast.success(t("common.success"), { closeButton: true }); await onSave(); } catch (error: any) { const detail = extractErrorMessage(error); diff --git a/src/components/mcp/UnifiedMcpPanel.tsx b/src/components/mcp/UnifiedMcpPanel.tsx index 0e5448cea..08f6c340b 100644 --- a/src/components/mcp/UnifiedMcpPanel.tsx +++ b/src/components/mcp/UnifiedMcpPanel.tsx @@ -99,7 +99,7 @@ const UnifiedMcpPanel = React.forwardRef< try { await deleteServerMutation.mutateAsync(id); setConfirmDialog(null); - toast.success(t("common.success")); + toast.success(t("common.success"), { closeButton: true }); } catch (error) { toast.error(t("common.error"), { description: String(error), diff --git a/src/components/providers/ProviderActions.tsx b/src/components/providers/ProviderActions.tsx index c649053cb..706582ccb 100644 --- a/src/components/providers/ProviderActions.tsx +++ b/src/components/providers/ProviderActions.tsx @@ -59,7 +59,7 @@ export function ProviderActions({ // 代理接管模式下启用按钮使用绿色 !isCurrent && isProxyTakeover && - "bg-emerald-500 hover:bg-emerald-600 dark:bg-emerald-600 dark:hover:bg-emerald-700" + "bg-emerald-500 hover:bg-emerald-600 dark:bg-emerald-600 dark:hover:bg-emerald-700", )} > {isCurrent ? ( @@ -158,7 +158,7 @@ export function ProviderActions({ className={cn( iconButtonClass, !isCurrent && "hover:text-red-500 dark:hover:text-red-400", - isCurrent && "opacity-40 cursor-not-allowed text-muted-foreground" + isCurrent && "opacity-40 cursor-not-allowed text-muted-foreground", )} > diff --git a/src/components/providers/ProviderCard.tsx b/src/components/providers/ProviderCard.tsx index c4145d607..81317139e 100644 --- a/src/components/providers/ProviderCard.tsx +++ b/src/components/providers/ProviderCard.tsx @@ -111,6 +111,7 @@ export function ProviderCard({ t("provider.circuitBreakerReset", { defaultValue: "熔断器已重置", }), + { closeButton: true }, ); } catch (error) { toast.error( @@ -183,7 +184,9 @@ export function ProviderCard({ "relative overflow-hidden rounded-xl border border-border p-4 transition-all duration-300", "bg-card text-card-foreground group", // 代理接管模式下 hover 使用绿色边框,否则使用蓝色 - isProxyTakeover ? "hover:border-emerald-500/50" : "hover:border-border-active", + isProxyTakeover + ? "hover:border-emerald-500/50" + : "hover:border-border-active", // 代理接管模式下当前供应商使用绿色边框 isProxyTakeover && isCurrent ? "border-emerald-500/60 shadow-sm shadow-emerald-500/10" @@ -194,12 +197,16 @@ export function ProviderCard({ "cursor-grabbing border-primary shadow-lg scale-105 z-10", )} > -
+