diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json index 9732fde1a..a4e141600 100644 --- a/messages/en/dashboard.json +++ b/messages/en/dashboard.json @@ -130,7 +130,8 @@ "view": "View" }, "error": { - "loadFailed": "Load Failed" + "loadFailed": "Load Failed", + "loadKeysFailed": "Failed to load keys" }, "details": { "title": "Request Details", diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json index ddd6a7545..bf5fddc11 100644 --- a/messages/ja/dashboard.json +++ b/messages/ja/dashboard.json @@ -130,7 +130,8 @@ "view": "表示" }, "error": { - "loadFailed": "読み込み失敗" + "loadFailed": "読み込み失敗", + "loadKeysFailed": "キーリストの読み込みに失敗しました" }, "details": { "title": "リクエスト詳細", diff --git a/messages/ru/dashboard.json b/messages/ru/dashboard.json index 0db08c676..ca7fdad9a 100644 --- a/messages/ru/dashboard.json +++ b/messages/ru/dashboard.json @@ -130,7 +130,8 @@ "view": "Просмотр" }, "error": { - "loadFailed": "Ошибка загрузки" + "loadFailed": "Ошибка загрузки", + "loadKeysFailed": "Не удалось загрузить список ключей" }, "details": { "title": "Детали запроса", diff --git a/messages/zh-CN/dashboard.json b/messages/zh-CN/dashboard.json index bc9131bcf..27e62c789 100644 --- a/messages/zh-CN/dashboard.json +++ b/messages/zh-CN/dashboard.json @@ -130,7 +130,8 @@ "view": "查看" }, "error": { - "loadFailed": "加载失败" + "loadFailed": "加载失败", + "loadKeysFailed": "加载密钥列表失败" }, "details": { "title": "请求详情", diff --git a/messages/zh-TW/dashboard.json b/messages/zh-TW/dashboard.json index e2b486d57..cd8f16209 100644 --- a/messages/zh-TW/dashboard.json +++ b/messages/zh-TW/dashboard.json @@ -130,7 +130,8 @@ "view": "檢視" }, "error": { - "loadFailed": "載入失敗" + "loadFailed": "載入失敗", + "loadKeysFailed": "載入密鑰列表失敗" }, "details": { "title": "請求詳情", diff --git a/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx b/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx index 42db1cf16..229b9e603 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx @@ -37,11 +37,13 @@ export function AddKeyForm({ userId, user, onSuccess }: AddKeyFormProps) { // Load provider group suggestions useEffect(() => { - if (user?.id) { - getAvailableProviderGroups(user.id).then(setProviderGroupSuggestions); - } else { - getAvailableProviderGroups().then(setProviderGroupSuggestions); - } + const loadGroups = user?.id + ? getAvailableProviderGroups(user.id) + : getAvailableProviderGroups(); + + loadGroups.then(setProviderGroupSuggestions).catch((err) => { + console.error("[AddKeyForm] Failed to load provider groups:", err); + }); }, [user?.id]); const form = useZodForm({ diff --git a/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx b/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx index 6f4b34dcf..ac5480748 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx @@ -52,11 +52,13 @@ export function EditKeyForm({ keyData, user, onSuccess }: EditKeyFormProps) { // Load provider group suggestions useEffect(() => { - if (user?.id) { - getAvailableProviderGroups(user.id).then(setProviderGroupSuggestions); - } else { - getAvailableProviderGroups().then(setProviderGroupSuggestions); - } + const loadGroups = user?.id + ? getAvailableProviderGroups(user.id) + : getAvailableProviderGroups(); + + loadGroups.then(setProviderGroupSuggestions).catch((err) => { + console.error("[EditKeyForm] Failed to load provider groups:", err); + }); }, [user?.id]); const formatExpiresAt = (expiresAt: string) => { diff --git a/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx b/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx index 6ac03076c..017d113d2 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx @@ -76,7 +76,11 @@ export function UserForm({ user, onSuccess, currentUser }: UserFormProps) { // 加载供应商分组建议 useEffect(() => { - getAvailableProviderGroups().then(setProviderGroupSuggestions); + getAvailableProviderGroups() + .then(setProviderGroupSuggestions) + .catch((err) => { + console.error("[UserForm] Failed to load provider groups:", err); + }); }, []); const form = useZodForm({ diff --git a/src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx b/src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx index 18e51cada..138f9cbad 100644 --- a/src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx +++ b/src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx @@ -112,9 +112,14 @@ export function UsageLogsFilters({ // 加载该用户的 keys if (newUserId) { - const keysResult = await getKeys(newUserId); - if (keysResult.ok && keysResult.data) { - setKeys(keysResult.data); + try { + const keysResult = await getKeys(newUserId); + if (keysResult.ok && keysResult.data) { + setKeys(keysResult.data); + } + } catch (error) { + console.error("Failed to load keys:", error); + toast.error(t("logs.error.loadKeysFailed")); } } else { setKeys([]); diff --git a/src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx b/src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx index 152eb48cb..c879523ed 100644 --- a/src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx +++ b/src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx @@ -146,18 +146,26 @@ export function ApiTestButton({ const currentProviderType = apiFormatToProviderType[apiFormat]; if (!currentProviderType) return; - getProviderTestPresets(currentProviderType).then((result) => { - if (result.ok && result.data) { - setPresets(result.data); - // Auto-select first preset if available - if (result.data.length > 0 && !selectedPreset) { - setSelectedPreset(result.data[0].id); - setSuccessContains(result.data[0].defaultSuccessContains); + getProviderTestPresets(currentProviderType) + .then((result) => { + if (result.ok && result.data) { + setPresets(result.data); + // Auto-select first preset if available + if (result.data.length > 0 && !selectedPreset) { + setSelectedPreset(result.data[0].id); + setSuccessContains(result.data[0].defaultSuccessContains); + } + } else { + if (!result.ok) { + console.error("[ApiTestButton] Failed to load presets:", result.error); + } + setPresets([]); } - } else { + }) + .catch((err) => { + console.error("[ApiTestButton] Failed to load presets:", err); setPresets([]); - } - }); + }); }, [apiFormat, apiFormatToProviderType, selectedPreset]); useEffect(() => { diff --git a/src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx b/src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx index 5125584e3..ff100eb7c 100644 --- a/src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx +++ b/src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx @@ -152,12 +152,20 @@ export function ProviderRichListItem({ // 处理查看密钥 const handleShowKey = async () => { setShowKeyDialog(true); - const result = await getUnmaskedProviderKey(provider.id); - if (result.ok) { - setUnmaskedKey(result.data.key); - } else { + try { + const result = await getUnmaskedProviderKey(provider.id); + if (result.ok) { + setUnmaskedKey(result.data.key); + } else { + toast.error(tList("getKeyFailed"), { + description: result.error || tList("unknownError"), + }); + setShowKeyDialog(false); + } + } catch (error) { + console.error("Failed to get provider key:", error); toast.error(tList("getKeyFailed"), { - description: result.error || tList("unknownError"), + description: tList("unknownError"), }); setShowKeyDialog(false); } diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index d0ff39014..b2aaaf137 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -1,10 +1,18 @@ "use client"; +import { isNetworkError } from "@/lib/utils/error-detection"; + /** - * 全局错误边界组件 + * Global error boundary component + * + * Must be a Client Component with html and body tags + * Displayed when root layout throws an error * - * 必须是 Client Component,且包含 html 和 body 标签 - * 当 root layout 抛出错误时显示 + * Note: Most errors should be caught by component-level error boundaries + * or try-catch in event handlers. This is the last resort fallback. + * + * Security: Never display raw error.message to users as it may contain + * sensitive information (database strings, file paths, internal APIs, etc.) */ export default function GlobalError({ error, @@ -13,6 +21,13 @@ export default function GlobalError({ error: Error & { digest?: string }; reset: () => void; }) { + // Use shared network error detection + const isNetwork = isNetworkError(error); + + const handleGoHome = () => { + window.location.href = "/"; + }; + return (
@@ -26,33 +41,77 @@ export default function GlobalError({ fontFamily: "system-ui, sans-serif", backgroundColor: "#f8f9fa", padding: "20px", + textAlign: "center", }} > -- {error.message || "An unexpected error occurred"} -
+ + {isNetwork ? ( +Unable to connect to the server. Please check:
++ An unexpected error occurred. Please try again later. +
+ )} + {error.digest && (Error ID: {error.digest}
)} - + +