From 914f77ff3714f28e49b5c6527c27037e1162d0e0 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 3 Jan 2026 00:56:25 +0800 Subject: [PATCH] refactor: standardize error handling across components by utilizing CustomApiError for improved error messaging --- .../home/bots/components/bot-form/BotForm.tsx | 11 +- web/src/app/home/bots/page.tsx | 6 +- .../dynamic-form/DynamicFormItemComponent.tsx | 6 +- .../components/models-dialog/ModelsDialog.tsx | 4 +- .../component/provider-form/ProviderForm.tsx | 3 +- .../external-kb-form/ExternalKBForm.tsx | 6 +- .../pipeline-form/PipelineFormComponent.tsx | 8 +- .../plugin-sort/PluginSortDialog.tsx | 214 ------------------ .../mcp-server/mcp-card/MCPCardComponent.tsx | 4 +- .../mcp-server/mcp-form/MCPFormDialog.tsx | 4 +- web/src/app/home/plugins/page.tsx | 4 +- web/src/app/infra/entities/common.ts | 4 + web/src/app/infra/http/BaseHttpClient.ts | 2 +- web/src/app/register/page.tsx | 3 +- 14 files changed, 38 insertions(+), 241 deletions(-) delete mode 100644 web/src/app/home/plugins/components/plugin-sort/PluginSortDialog.tsx diff --git a/web/src/app/home/bots/components/bot-form/BotForm.tsx b/web/src/app/home/bots/components/bot-form/BotForm.tsx index 5e030021..2c76c5fc 100644 --- a/web/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web/src/app/home/bots/components/bot-form/BotForm.tsx @@ -49,6 +49,7 @@ import { } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { extractI18nObject } from '@/i18n/I18nProvider'; +import { CustomApiError } from '@/app/infra/entities/common'; const getFormSchema = (t: (key: string) => string) => z.object({ @@ -241,7 +242,9 @@ export default function BotForm({ } }) .catch((err) => { - toast.error(t('bots.getBotConfigError') + err.message); + toast.error( + t('bots.getBotConfigError') + (err as CustomApiError).msg, + ); }); } else { form.reset(); @@ -384,7 +387,7 @@ export default function BotForm({ toast.success(t('bots.saveSuccess')); }) .catch((err) => { - toast.error(t('bots.saveError') + err.message); + toast.error(t('bots.saveError') + err.msg); }) .finally(() => { setIsLoading(false); @@ -410,7 +413,7 @@ export default function BotForm({ onNewBotCreated(res.uuid); }) .catch((err) => { - toast.error(t('bots.createError') + err.message); + toast.error(t('bots.createError') + err.msg); }) .finally(() => { setIsLoading(false); @@ -429,7 +432,7 @@ export default function BotForm({ toast.success(t('bots.deleteSuccess')); }) .catch((err) => { - toast.error(t('bots.deleteError') + err.message); + toast.error(t('bots.deleteError') + err.msg); }); } } diff --git a/web/src/app/home/bots/page.tsx b/web/src/app/home/bots/page.tsx index b3c0dd25..1550af53 100644 --- a/web/src/app/home/bots/page.tsx +++ b/web/src/app/home/bots/page.tsx @@ -11,6 +11,7 @@ import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import { extractI18nObject } from '@/i18n/I18nProvider'; import BotDetailDialog from '@/app/home/bots/BotDetailDialog'; +import { CustomApiError } from '@/app/infra/entities/common'; export default function BotConfigPage() { const { t } = useTranslation(); @@ -54,10 +55,7 @@ export default function BotConfigPage() { }) .catch((err) => { console.error('get bot list error', err); - toast.error(t('bots.getBotListError') + err.message); - }) - .finally(() => { - // setIsLoading(false); + toast.error(t('bots.getBotListError') + (err as CustomApiError).msg); }); } diff --git a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx index 16642e84..a1285f12 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx +++ b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx @@ -108,7 +108,7 @@ export default function DynamicFormItemComponent({ setLlmModels(models); }) .catch((err) => { - toast.error('Failed to get LLM model list: ' + err.message); + toast.error('Failed to get LLM model list: ' + err.msg); }); } }, [config.type]); @@ -124,7 +124,7 @@ export default function DynamicFormItemComponent({ setKnowledgeBases(resp.bases); }) .catch((err) => { - toast.error('Failed to get knowledge base list: ' + err.message); + toast.error('Failed to get knowledge base list: ' + err.msg); }); // Fetch plugin system status @@ -165,7 +165,7 @@ export default function DynamicFormItemComponent({ setBots(resp.bots); }) .catch((err) => { - toast.error('Failed to get bot list: ' + err.message); + toast.error('Failed to get bot list: ' + err.msg); }); } }, [config.type]); diff --git a/web/src/app/home/components/models-dialog/ModelsDialog.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx index 201e2570..9f030cd2 100644 --- a/web/src/app/home/components/models-dialog/ModelsDialog.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -22,6 +22,7 @@ import { ProviderModels, LANGBOT_MODELS_PROVIDER_REQUESTER, } from './types'; +import { CustomApiError } from '@/app/infra/entities/common'; interface ModelsDialogProps { open: boolean; @@ -349,7 +350,8 @@ export default function ModelsDialog({ const duration = Date.now() - startTime; setTestResult({ success: true, duration }); } catch (err) { - toast.error(t('models.testError') + ': ' + (err as Error).message); + console.error('Failed to test model', err); + toast.error(t('models.testError') + ': ' + (err as CustomApiError).msg); setTestResult(null); } finally { setIsTesting(false); diff --git a/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx b/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx index 209f23cc..94e4b0f9 100644 --- a/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx +++ b/web/src/app/home/components/models-dialog/component/provider-form/ProviderForm.tsx @@ -28,6 +28,7 @@ import { import { DialogFooter } from '@/components/ui/dialog'; import { toast } from 'sonner'; import { extractI18nObject } from '@/i18n/I18nProvider'; +import { CustomApiError } from '@/app/infra/entities/common'; const getFormSchema = (t: (key: string) => string) => z.object({ @@ -124,7 +125,7 @@ export default function ProviderForm({ } onFormSubmit(); } catch (err) { - toast.error(t('models.providerSaveError') + (err as Error).message); + toast.error(t('models.providerSaveError') + (err as CustomApiError).msg); } } diff --git a/web/src/app/home/knowledge/components/external-kb-form/ExternalKBForm.tsx b/web/src/app/home/knowledge/components/external-kb-form/ExternalKBForm.tsx index f371f860..e5bf9d24 100644 --- a/web/src/app/home/knowledge/components/external-kb-form/ExternalKBForm.tsx +++ b/web/src/app/home/knowledge/components/external-kb-form/ExternalKBForm.tsx @@ -291,7 +291,7 @@ export default function ExternalKBForm({ toast.success(t('knowledge.updateExternalSuccess')); }) .catch((err) => { - toast.error('Failed to update KB: ' + err.message); + toast.error('Failed to update KB: ' + err.msg); }); } else { // Create new KB @@ -303,7 +303,7 @@ export default function ExternalKBForm({ form.reset(); }) .catch((err) => { - toast.error('Failed to create KB: ' + err.message); + toast.error('Failed to create KB: ' + err.msg); }); } } @@ -321,7 +321,7 @@ export default function ExternalKBForm({ toast.success(t('knowledge.deleteExternalSuccess')); }) .catch((err) => { - toast.error('Failed to delete KB: ' + err.message); + toast.error('Failed to delete KB: ' + err.msg); }); } diff --git a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx index 6f86d6ef..65bbc526 100644 --- a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx +++ b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx @@ -181,7 +181,7 @@ export default function PipelineFormComponent({ toast.success(t('pipelines.createSuccess')); }) .catch((err) => { - toast.error(t('pipelines.createError') + err.message); + toast.error(t('pipelines.createError') + err.msg); }); } @@ -211,7 +211,7 @@ export default function PipelineFormComponent({ toast.success(t('pipelines.saveSuccess')); }) .catch((err) => { - toast.error(t('pipelines.saveError') + err.message); + toast.error(t('pipelines.saveError') + err.msg); }); } @@ -340,7 +340,7 @@ export default function PipelineFormComponent({ toast.success(t('pipelines.deleteSuccess')); }) .catch((err) => { - toast.error(t('pipelines.deleteError') + err.message); + toast.error(t('pipelines.deleteError') + err.msg); }); } }; @@ -360,7 +360,7 @@ export default function PipelineFormComponent({ onCancel(); }) .catch((err) => { - toast.error(t('pipelines.createError') + err.message); + toast.error(t('pipelines.createError') + err.msg); }); } }; diff --git a/web/src/app/home/plugins/components/plugin-sort/PluginSortDialog.tsx b/web/src/app/home/plugins/components/plugin-sort/PluginSortDialog.tsx deleted file mode 100644 index 1c601e77..00000000 --- a/web/src/app/home/plugins/components/plugin-sort/PluginSortDialog.tsx +++ /dev/null @@ -1,214 +0,0 @@ -// 'use client'; - -// import * as React from 'react'; -// import { useState, useEffect } from 'react'; -// import { PluginCardVO } from '@/app/home/plugins/plugin-installed/PluginCardVO'; -// import { httpClient } from '@/app/infra/http/HttpClient'; -// import { PluginReorderElement } from '@/app/infra/entities/api'; -// import { toast } from 'sonner'; -// import { -// Dialog, -// DialogContent, -// DialogHeader, -// DialogTitle, -// DialogFooter, -// } from '@/components/ui/dialog'; -// import { Button } from '@/components/ui/button'; -// import { -// DndContext, -// closestCenter, -// KeyboardSensor, -// PointerSensor, -// useSensor, -// useSensors, -// DragEndEvent, -// } from '@dnd-kit/core'; -// import { -// arrayMove, -// SortableContext, -// sortableKeyboardCoordinates, -// useSortable, -// verticalListSortingStrategy, -// } from '@dnd-kit/sortable'; -// import { CSS } from '@dnd-kit/utilities'; -// import { useTranslation } from 'react-i18next'; -// import { extractI18nObject } from '@/i18n/I18nProvider'; - -// interface PluginSortDialogProps { -// open: boolean; -// onOpenChange: (open: boolean) => void; -// onSortComplete: () => void; -// } - -// function SortablePluginItem({ plugin }: { plugin: PluginCardVO }) { -// const { attributes, listeners, setNodeRef, transform, transition } = -// useSortable({ -// id: `${plugin.author}-${plugin.name}`, -// }); - -// const style = { -// transform: CSS.Transform.toString(transform), -// transition, -// }; - -// return ( -//
-//
-//
-// {plugin.author} -//
-//
{plugin.name}
-//
-// {plugin.description} -//
-//
-//
-// ); -// } - -// export default function PluginSortDialog({ -// open, -// onOpenChange, -// onSortComplete, -// }: PluginSortDialogProps) { -// const { t } = useTranslation(); -// const [sortedPlugins, setSortedPlugins] = useState([]); -// const [isLoading, setIsLoading] = useState(false); - -// function getPluginList() { -// httpClient.getPlugins().then((value) => { -// setSortedPlugins( -// value.plugins.map((plugin) => { -// return new PluginCardVO({ -// author: plugin.manifest.manifest.metadata.author ?? '', -// description: extractI18nObject( -// plugin.manifest.manifest.metadata.description ?? { -// en_US: '', -// zh_Hans: '', -// }, -// ), -// enabled: plugin.enabled, -// name: plugin.manifest.manifest.metadata.name, -// version: plugin.manifest.manifest.metadata.version ?? '', -// status: plugin.status, -// components: plugin.components, -// install_source: plugin.install_source, -// install_info: plugin.install_info, -// priority: plugin.priority, -// debug: plugin.debug, -// }); -// }), -// ); -// }); -// } - -// useEffect(() => { -// if (open) { -// getPluginList(); -// } -// }, [open]); - -// const sensors = useSensors( -// useSensor(PointerSensor), -// useSensor(KeyboardSensor, { -// coordinateGetter: sortableKeyboardCoordinates, -// }), -// ); - -// function handleDragEnd(event: DragEndEvent) { -// const { active, over } = event; - -// if (over && active.id !== over.id) { -// setSortedPlugins((items) => { -// const oldIndex = items.findIndex( -// (item) => `${item.author}-${item.name}` === active.id, -// ); -// const newIndex = items.findIndex( -// (item) => `${item.author}-${item.name}` === over.id, -// ); - -// const newItems = arrayMove(items, oldIndex, newIndex); - -// return newItems; -// }); -// } -// } - -// function handleSave() { -// setIsLoading(true); - -// const reorderElements: PluginReorderElement[] = sortedPlugins.map( -// (plugin, index) => ({ -// author: plugin.author, -// name: plugin.name, -// priority: index, -// }), -// ); - -// httpClient -// .reorderPlugins(reorderElements) -// .then(() => { -// toast.success(t('plugins.pluginSortSuccess')); -// onSortComplete(); -// onOpenChange(false); -// }) -// .catch((err) => { -// toast.error(t('plugins.pluginSortError') + err.message); -// }) -// .finally(() => { -// setIsLoading(false); -// }); -// } - -// return ( -// -// -// -// {t('plugins.pluginSort')} -// -//
-//

-// {t('plugins.pluginSortDescription')} -//

-// -// `${plugin.author}-${plugin.name}`, -// )} -// strategy={verticalListSortingStrategy} -// > -// {sortedPlugins.map((plugin) => ( -// -// ))} -// -// -//
-// -// -// -// -//
-//
-// ); -// } diff --git a/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx b/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx index 937da834..2ae43a9c 100644 --- a/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx +++ b/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx @@ -41,7 +41,7 @@ export default function MCPCardComponent({ setSwitchEnable(true); }) .catch((err) => { - toast.error(t('mcp.modifyFailed') + err.message); + toast.error(t('mcp.modifyFailed') + err.msg); setSwitchEnable(true); }); } @@ -76,7 +76,7 @@ export default function MCPCardComponent({ }, 1000); }) .catch((err) => { - toast.error(t('mcp.refreshFailed') + err.message); + toast.error(t('mcp.refreshFailed') + err.msg); setTesting(false); }); } diff --git a/web/src/app/home/plugins/mcp-server/mcp-form/MCPFormDialog.tsx b/web/src/app/home/plugins/mcp-server/mcp-form/MCPFormDialog.tsx index 2a0bf35a..72901d87 100644 --- a/web/src/app/home/plugins/mcp-server/mcp-form/MCPFormDialog.tsx +++ b/web/src/app/home/plugins/mcp-server/mcp-form/MCPFormDialog.tsx @@ -44,6 +44,7 @@ import { MCPServer, MCPSessionStatus, } from '@/app/infra/entities/api'; +import { CustomApiError } from '@/app/infra/entities/common'; // Status Display Component - 在测试中、连接中或连接失败时使用 function StatusDisplay({ @@ -409,7 +410,8 @@ export default function MCPFormDialog({ } catch (err) { clearInterval(interval); setMcpTesting(false); - const errorMsg = (err as Error).message || t('mcp.getTaskFailed'); + const errorMsg = + (err as CustomApiError).msg || t('mcp.getTaskFailed'); toast.error(`${t('mcp.testError')}: ${errorMsg}`); } }, 1000); diff --git a/web/src/app/home/plugins/page.tsx b/web/src/app/home/plugins/page.tsx index 822364d6..594f3829 100644 --- a/web/src/app/home/plugins/page.tsx +++ b/web/src/app/home/plugins/page.tsx @@ -282,7 +282,7 @@ export default function PluginConfigPage() { watchTask(taskId); }) .catch((err) => { - setInstallError(err.message); + setInstallError(err.msg); setPluginInstallStatus(PluginInstallStatus.ERROR); }); } else if (installSource === 'local') { @@ -293,7 +293,7 @@ export default function PluginConfigPage() { watchTask(taskId); }) .catch((err) => { - setInstallError(err.message); + setInstallError(err.msg); setPluginInstallStatus(PluginInstallStatus.ERROR); }); } else if (installSource === 'marketplace') { diff --git a/web/src/app/infra/entities/common.ts b/web/src/app/infra/entities/common.ts index 64331738..729aa77a 100644 --- a/web/src/app/infra/entities/common.ts +++ b/web/src/app/infra/entities/common.ts @@ -19,3 +19,7 @@ export interface ComponentManifest { }; spec: Record; // eslint-disable-line @typescript-eslint/no-explicit-any } + +export interface CustomApiError { + msg?: string; +} diff --git a/web/src/app/infra/http/BaseHttpClient.ts b/web/src/app/infra/http/BaseHttpClient.ts index 1198c647..c90e95d6 100644 --- a/web/src/app/infra/http/BaseHttpClient.ts +++ b/web/src/app/infra/http/BaseHttpClient.ts @@ -147,7 +147,7 @@ export abstract class BaseHttpClient { // 错误处理 protected handleError(error: object): never { if (axios.isCancel(error)) { - throw { code: -2, message: 'Request canceled', data: null }; + throw { code: -2, msg: 'Request canceled', data: null }; } throw error; } diff --git a/web/src/app/register/page.tsx b/web/src/app/register/page.tsx index 5ec3b249..3932e509 100644 --- a/web/src/app/register/page.tsx +++ b/web/src/app/register/page.tsx @@ -28,6 +28,7 @@ import langbotIcon from '@/app/assets/langbot-logo.webp'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import { ThemeToggle } from '@/components/ui/theme-toggle'; +import { CustomApiError } from '@/app/infra/entities/common'; const formSchema = (t: (key: string) => string) => z.object({ @@ -75,7 +76,7 @@ export default function Register() { router.push('/login'); }) .catch((err: Error) => { - toast.error(t('register.initFailed') + err.message); + toast.error(t('register.initFailed') + (err as CustomApiError).msg); }); }