From 5bace38e4767f83e17a5a5c8ca2fe08ef3c39ff3 Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Thu, 26 Feb 2026 10:34:42 +0800 Subject: [PATCH] fix: improve ErrorBoundary i18n and update provider preset descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ErrorBoundary (PR #49 follow-up): - Replace inline SVG with lucide-react CircleAlert icon for consistency - Add i18n support: extract fallback UI into functional ErrorFallback component so it can use useTranslation hook - Remove unused withErrorBoundary HOC - Add error.* translation keys to en.ts and zh.ts ProviderManager: - Add Claude Code default config entry in connected providers section with cc switch hint explaining configurations may not be readable - Update GLM preset descriptions: "GLM" → "GLM Code Plan / 编程套餐" - Update MiniMax preset descriptions: "MiniMax API" → "MiniMax Code Plan / 编程套餐" - Add provider.ccSwitchHint translation key to en.ts and zh.ts Co-Authored-By: Claude Opus 4.6 --- src/components/layout/ErrorBoundary.tsx | 158 +++++++++----------- src/components/settings/ProviderManager.tsx | 42 ++++-- src/i18n/en.ts | 9 ++ src/i18n/zh.ts | 9 ++ 4 files changed, 112 insertions(+), 106 deletions(-) diff --git a/src/components/layout/ErrorBoundary.tsx b/src/components/layout/ErrorBoundary.tsx index 651a837..c280aeb 100644 --- a/src/components/layout/ErrorBoundary.tsx +++ b/src/components/layout/ErrorBoundary.tsx @@ -1,6 +1,8 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; +import { CircleAlert } from "lucide-react"; +import { useTranslation } from "@/hooks/useTranslation"; interface ErrorBoundaryProps { children: React.ReactNode; @@ -10,16 +12,77 @@ interface ErrorBoundaryProps { interface ErrorBoundaryState { hasError: boolean; error: Error | null; - showDetails: boolean; } +/* ── Fallback UI (functional, so it can use hooks) ──────────── */ + +function ErrorFallback({ + error, + onReset, +}: { + error: Error | null; + onReset: () => void; +}) { + const { t } = useTranslation(); + const [showDetails, setShowDetails] = useState(false); + + return ( +
+
+
+ +
+ +

+ {t("error.title")} +

+

+ {t("error.description")} +

+ + {error && ( + + )} + {showDetails && error && ( +
+            {error.message}
+            {error.stack && `\n\n${error.stack}`}
+          
+ )} + +
+ + +
+
+
+ ); +} + +/* ── Error Boundary (class component, required by React) ────── */ + export class ErrorBoundary extends React.Component< ErrorBoundaryProps, ErrorBoundaryState > { constructor(props: ErrorBoundaryProps) { super(props); - this.state = { hasError: false, error: null, showDetails: false }; + this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error): Partial { @@ -32,11 +95,7 @@ export class ErrorBoundary extends React.Component< } handleReset = () => { - this.setState({ hasError: false, error: null, showDetails: false }); - }; - - handleReload = () => { - window.location.reload(); + this.setState({ hasError: false, error: null }); }; render() { @@ -44,91 +103,10 @@ export class ErrorBoundary extends React.Component< if (this.props.fallback) { return this.props.fallback; } - return ( -
-
- {/* Error icon */} -
- - - - - -
- -

- Something went wrong -

-

- An unexpected error occurred. You can try again or reload the app. -

- - {/* Expandable error details */} - {this.state.error && ( - - )} - {this.state.showDetails && this.state.error && ( -
-                {this.state.error.message}
-                {this.state.error.stack && `\n\n${this.state.error.stack}`}
-              
- )} - - {/* Action buttons */} -
- - -
-
-
+ ); } - return this.props.children; } } - -/** Convenience HOC to wrap any component with an ErrorBoundary */ -export function withErrorBoundary

( - Component: React.ComponentType

, - fallback?: React.ReactNode -) { - const displayName = Component.displayName || Component.name || "Component"; - - const Wrapped = (props: P) => ( - - - - ); - - Wrapped.displayName = `withErrorBoundary(${displayName})`; - return Wrapped; -} diff --git a/src/components/settings/ProviderManager.tsx b/src/components/settings/ProviderManager.tsx index 7a15173..3aa8778 100644 --- a/src/components/settings/ProviderManager.tsx +++ b/src/components/settings/ProviderManager.tsx @@ -146,8 +146,8 @@ const QUICK_PRESETS: QuickPreset[] = [ { key: "glm-cn", name: "GLM (CN)", - description: "Zhipu GLM — China region", - descriptionZh: "智谱 GLM — 中国区", + description: "Zhipu GLM Code Plan — China region", + descriptionZh: "智谱 GLM 编程套餐 — 中国区", icon: , provider_type: "custom", base_url: "https://open.bigmodel.cn/api/anthropic", @@ -157,8 +157,8 @@ const QUICK_PRESETS: QuickPreset[] = [ { key: "glm-global", name: "GLM (Global)", - description: "Zhipu GLM — Global region", - descriptionZh: "智谱 GLM — 国际区", + description: "Zhipu GLM Code Plan — Global region", + descriptionZh: "智谱 GLM 编程套餐 — 国际区", icon: , provider_type: "custom", base_url: "https://api.z.ai/api/anthropic", @@ -190,8 +190,8 @@ const QUICK_PRESETS: QuickPreset[] = [ { key: "minimax-cn", name: "MiniMax (CN)", - description: "MiniMax API — China region", - descriptionZh: "MiniMax API — 中国区", + description: "MiniMax Code Plan — China region", + descriptionZh: "MiniMax 编程套餐 — 中国区", icon: , provider_type: "custom", base_url: "https://api.minimaxi.com/anthropic", @@ -201,8 +201,8 @@ const QUICK_PRESETS: QuickPreset[] = [ { key: "minimax-global", name: "MiniMax (Global)", - description: "MiniMax API — Global region", - descriptionZh: "MiniMax API — 国际区", + description: "MiniMax Code Plan — Global region", + descriptionZh: "MiniMax 编程套餐 — 国际区", icon: , provider_type: "custom", base_url: "https://api.minimax.io/anthropic", @@ -563,20 +563,30 @@ export function ProviderManager() {

{t('provider.connectedProviders')}

- {/* Environment variable detection */} - {Object.keys(envDetected).length > 0 && ( -
+ {/* Claude Code default config */} +
+
- {t('provider.environment')} - - ENV - +
+ Claude Code + + {t('provider.default')} + + {Object.keys(envDetected).length > 0 && ( + + ENV + + )} +
- )} +

+ {t('provider.ccSwitchHint')} +

+
{/* Connected provider list */} {sorted.length > 0 ? ( diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 0bb3392..a921863 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -145,6 +145,7 @@ const en = { 'provider.disconnecting': 'Disconnecting...', 'provider.disconnectProvider': 'Disconnect Provider', 'provider.disconnectConfirm': 'Are you sure you want to disconnect "{name}"? This action cannot be undone.', + 'provider.ccSwitchHint': 'Claude Code configurations added via tools like cc switch may not be readable by CodePilot. We recommend re-adding your provider here.', 'provider.addProviderSection': 'Add Provider', 'provider.addProviderDesc': 'Select a provider to connect. Most presets only require an API key.', @@ -290,6 +291,14 @@ const en = { 'common.enabled': 'Enabled', 'common.disabled': 'Disabled', + // ── Error boundary ──────────────────────────────────────── + 'error.title': 'Something went wrong', + 'error.description': 'An unexpected error occurred. You can try again or reload the app.', + 'error.showDetails': 'Show details', + 'error.hideDetails': 'Hide details', + 'error.tryAgain': 'Try Again', + 'error.reloadApp': 'Reload App', + // ── CLI dynamic field labels ────────────────────────────── 'cli.loadingSettings': 'Loading settings...', 'cli.field.skipDangerousModePermissionPrompt': 'Skip Dangerous Mode Permission Prompt', diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index 89a0a96..7e2433d 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -142,6 +142,7 @@ const zh: Record = { 'provider.disconnecting': '断开中...', 'provider.disconnectProvider': '断开提供商', 'provider.disconnectConfirm': '确定要断开"{name}"吗?此操作无法撤销。', + 'provider.ccSwitchHint': '通过类似 cc switch 等工具添加的 Claude Code 配置可能无法被 CodePilot 读取,建议在此处重新添加。', 'provider.addProviderSection': '添加提供商', 'provider.addProviderDesc': '选择要连接的提供商。大多数预设只需填写 API 密钥。', @@ -287,6 +288,14 @@ const zh: Record = { 'common.enabled': '已启用', 'common.disabled': '已禁用', + // ── Error boundary ──────────────────────────────────────── + 'error.title': '出错了', + 'error.description': '发生了意外错误。您可以重试或重新加载应用。', + 'error.showDetails': '显示详情', + 'error.hideDetails': '隐藏详情', + 'error.tryAgain': '重试', + 'error.reloadApp': '重新加载', + // ── CLI dynamic field labels ────────────────────────────── 'cli.loadingSettings': '加载设置中...', 'cli.field.skipDangerousModePermissionPrompt': '跳过危险模式权限提示',