mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-01 17:59:09 +08:00
feat: error boundary (#9462)
* build: 添加 react-error-boundary 依赖 添加 react-error-boundary 包以增强 React 应用的错误处理能力 * feat(组件): 添加ErrorBoundary组件用于错误边界处理 * feat(home): 为HomeTabs和Chat组件添加错误边界处理 * refactor(ErrorBoundary): 移除多余的ErrorContainer包装并优化结构 * feat(ErrorBoundary): 添加重新加载按钮并优化错误边界样式 添加重新加载功能按钮,方便用户快速恢复应用 调整错误边界容器的布局样式,使其居中显示 * style(ErrorBoundary): 移除ErrorContainer的固定高度以改善布局灵活性 * test(ErrorBoundary): 添加测试错误边界组件的功能按钮 添加一个用于测试错误边界组件功能的按钮组件,该按钮点击后会抛出错误以验证错误边界是否正常工作。此组件仅用于测试,合并前需要删除。 * feat(路由): 为路由组件添加错误边界处理 在Router组件中包裹ErrorBoundary以捕获并处理子组件中的错误 * fix(ErrorBoundary): 修复错误边界中翻译键的拼写错误 * feat(i18n): 添加边界错误处理和主题不存在错误的多语言支持 * refactor(ErrorBoundary): 移除用于测试的ThrowError组件
This commit is contained in:
parent
6d102ccef8
commit
107c01913d
@ -227,6 +227,7 @@
|
||||
"proxy-agent": "^6.5.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
|
||||
@ -4,6 +4,7 @@ import { FC, useMemo } from 'react'
|
||||
import { HashRouter, Route, Routes } from 'react-router-dom'
|
||||
|
||||
import Sidebar from './components/app/Sidebar'
|
||||
import { ErrorBoundary } from './components/ErrorBoundary'
|
||||
import TabsContainer from './components/Tab/TabContainer'
|
||||
import NavigationHandler from './handler/NavigationHandler'
|
||||
import { useNavbarPosition } from './hooks/useSettings'
|
||||
@ -23,18 +24,20 @@ const Router: FC = () => {
|
||||
|
||||
const routes = useMemo(() => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/agents" element={<AgentsPage />} />
|
||||
<Route path="/paintings/*" element={<PaintingsRoutePage />} />
|
||||
<Route path="/translate" element={<TranslatePage />} />
|
||||
<Route path="/files" element={<FilesPage />} />
|
||||
<Route path="/knowledge" element={<KnowledgePage />} />
|
||||
<Route path="/apps" element={<MinAppsPage />} />
|
||||
<Route path="/code" element={<CodeToolsPage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
<Route path="/launchpad" element={<LaunchpadPage />} />
|
||||
</Routes>
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/agents" element={<AgentsPage />} />
|
||||
<Route path="/paintings/*" element={<PaintingsRoutePage />} />
|
||||
<Route path="/translate" element={<TranslatePage />} />
|
||||
<Route path="/files" element={<FilesPage />} />
|
||||
<Route path="/knowledge" element={<KnowledgePage />} />
|
||||
<Route path="/apps" element={<MinAppsPage />} />
|
||||
<Route path="/code" element={<CodeToolsPage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
<Route path="/launchpad" element={<LaunchpadPage />} />
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
57
src/renderer/src/components/ErrorBoundary.tsx
Normal file
57
src/renderer/src/components/ErrorBoundary.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { formatErrorMessage } from '@renderer/utils/error'
|
||||
import { Alert, Button, Space } from 'antd'
|
||||
import { ComponentType, ReactNode } from 'react'
|
||||
import { ErrorBoundary, FallbackProps } from 'react-error-boundary'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const DefaultFallback: ComponentType<FallbackProps> = (props: FallbackProps): ReactNode => {
|
||||
const { t } = useTranslation()
|
||||
const { error } = props
|
||||
const debug = async () => {
|
||||
await window.api.devTools.toggle()
|
||||
}
|
||||
const reload = async () => {
|
||||
await window.api.reload()
|
||||
}
|
||||
return (
|
||||
<ErrorContainer>
|
||||
<Alert
|
||||
message={t('error.boundary.default.message')}
|
||||
showIcon
|
||||
description={formatErrorMessage(error)}
|
||||
type="error"
|
||||
action={
|
||||
<Space>
|
||||
<Button size="small" danger onClick={debug}>
|
||||
{t('error.boundary.default.devtools')}
|
||||
</Button>
|
||||
<Button size="small" danger onClick={reload}>
|
||||
{t('error.boundary.default.reload')}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
</ErrorContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const ErrorBoundaryCustomized = ({
|
||||
children,
|
||||
fallbackComponent
|
||||
}: {
|
||||
children: ReactNode
|
||||
fallbackComponent?: ComponentType<FallbackProps>
|
||||
}) => {
|
||||
return <ErrorBoundary FallbackComponent={fallbackComponent ?? DefaultFallback}>{children}</ErrorBoundary>
|
||||
}
|
||||
|
||||
const ErrorContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
`
|
||||
|
||||
export { ErrorBoundaryCustomized as ErrorBoundary }
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Backup file format error"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Open debug panel",
|
||||
"message": "It seems that something went wrong...",
|
||||
"reload": "Reload"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Returned an invalid data format"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "バックアップファイルの形式エラー"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "デバッグパネルを開く",
|
||||
"message": "何か問題が発生したようです...",
|
||||
"reload": "再読み込み"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "無効なデータ形式が返されました"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Ошибка формата файла резервной копии"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Открыть панель отладки",
|
||||
"message": "Похоже, возникла какая-то проблема...",
|
||||
"reload": "Перезагрузить"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Вернулся недопустимый формат данных"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "备份文件格式错误"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "打开调试面板",
|
||||
"message": "似乎出现了一些问题...",
|
||||
"reload": "重新加载"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "返回了无效的数据格式"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "備份檔案格式錯誤"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "打開除錯面板",
|
||||
"message": "似乎出現了一些問題...",
|
||||
"reload": "重新載入"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "返回了無效的資料格式"
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Λάθος μορφή αρχείου που επιστρέφεται"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Άνοιγμα πίνακα αποσφαλμάτωσης",
|
||||
"message": "Φαίνεται ότι προέκυψε κάποιο πρόβλημα...",
|
||||
"reload": "Επαναφόρτωση"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Επέστρεψε μη έγκυρη μορφή δεδομένων"
|
||||
@ -888,6 +895,9 @@
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Συνεχίστε το συνομιλημένο",
|
||||
"error": {
|
||||
"topic_not_found": "Το θέμα δεν υπάρχει"
|
||||
},
|
||||
"locate": {
|
||||
"message": "Εφαρμογή στο μήνυμα"
|
||||
},
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Formato de archivo de copia de seguridad incorrecto"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Abrir el panel de depuración",
|
||||
"message": "Parece que ha surgido un problema...",
|
||||
"reload": "Recargar"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Devuelve un formato de datos no válido"
|
||||
@ -888,6 +895,9 @@
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Continuar chat",
|
||||
"error": {
|
||||
"topic_not_found": "El tema no existe"
|
||||
},
|
||||
"locate": {
|
||||
"message": "Localizar mensaje"
|
||||
},
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Le format du fichier de sauvegarde est incorrect"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Ouvrir le panneau de débogage",
|
||||
"message": "Il semble que quelques problèmes soient survenus...",
|
||||
"reload": "Recharger"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "a renvoyé un format de données invalide"
|
||||
@ -888,6 +895,9 @@
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Continuer la conversation",
|
||||
"error": {
|
||||
"topic_not_found": "Le sujet n'existe pas"
|
||||
},
|
||||
"locate": {
|
||||
"message": "Localiser le message"
|
||||
},
|
||||
|
||||
@ -808,6 +808,13 @@
|
||||
"backup": {
|
||||
"file_format": "Formato do arquivo de backup está incorreto"
|
||||
},
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Abrir o painel de depuração",
|
||||
"message": "Parece que ocorreu um problema...",
|
||||
"reload": "Recarregar"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"chunk": {
|
||||
"non_json": "Devolveu um formato de dados inválido"
|
||||
@ -888,6 +895,9 @@
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Continuar conversando",
|
||||
"error": {
|
||||
"topic_not_found": "Tópico inexistente"
|
||||
},
|
||||
"locate": {
|
||||
"message": "Localizar mensagem"
|
||||
},
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useActiveTopic } from '@renderer/hooks/useTopic'
|
||||
@ -100,20 +101,24 @@ const HomePage: FC = () => {
|
||||
)}
|
||||
<ContentContainer id={isLeftNavbar ? 'content-container' : undefined}>
|
||||
{showAssistants && (
|
||||
<HomeTabs
|
||||
activeAssistant={activeAssistant}
|
||||
activeTopic={activeTopic}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
setActiveTopic={setActiveTopic}
|
||||
position="left"
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
<HomeTabs
|
||||
activeAssistant={activeAssistant}
|
||||
activeTopic={activeTopic}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
setActiveTopic={setActiveTopic}
|
||||
position="left"
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
<Chat
|
||||
assistant={activeAssistant}
|
||||
activeTopic={activeTopic}
|
||||
setActiveTopic={setActiveTopic}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
<Chat
|
||||
assistant={activeAssistant}
|
||||
activeTopic={activeTopic}
|
||||
setActiveTopic={setActiveTopic}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@ -8538,6 +8538,7 @@ __metadata:
|
||||
proxy-agent: "npm:^6.5.0"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
react-error-boundary: "npm:^6.0.0"
|
||||
react-hotkeys-hook: "npm:^4.6.1"
|
||||
react-i18next: "npm:^14.1.2"
|
||||
react-infinite-scroll-component: "npm:^6.1.0"
|
||||
@ -19026,6 +19027,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-error-boundary@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "react-error-boundary@npm:6.0.0"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.12.5"
|
||||
peerDependencies:
|
||||
react: ">=16.13.1"
|
||||
checksum: 10c0/1914d600dee95a14f14af4afe9867b0d35c26c4f7826d23208800ba2a99728659029aad60a6ef95e13430b4d79c2c4c9b3585f50bf508450478760d2e4e732d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-hotkeys-hook@npm:^4.6.1":
|
||||
version: 4.6.2
|
||||
resolution: "react-hotkeys-hook@npm:4.6.2"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user