add error catching feature

This commit is contained in:
Zhang Minghan 2023-11-23 18:09:51 +08:00
parent a8b3f6cc7c
commit c65051b1d9
5 changed files with 127 additions and 4 deletions

View File

@ -117,3 +117,37 @@ strong {
.cent {
font-weight: normal !important;
}
.error-boundary {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: max-content;
min-height: calc(100vh - 56px);
overflow: hidden;
background: hsl(var(--background-container));
padding: 2.5rem 5rem;
@media (max-width: 720px) {
& {
padding: 2.5rem 1rem;
}
}
.error-provider {
text-align: center;
margin: 0.5rem auto;
p {
margin: 0.35rem;
}
}
.error-tips {
text-align: center;
padding: 1rem 2rem;
color: hsl(var(--text-secondary));
}
}

View File

@ -0,0 +1,71 @@
import React from "react";
import { AlertCircle, Download } from "lucide-react";
import { withTranslation, WithTranslation } from "react-i18next";
import { version } from "@/conf.ts";
import { getMemoryPerformance } from "@/utils/app.ts";
import { Button } from "@/components/ui/button.tsx";
import { saveAsFile } from "@/utils/dom.ts";
type ErrorBoundaryProps = { children: React.ReactNode } & WithTranslation;
class ErrorBoundary extends React.Component<
ErrorBoundaryProps,
{ errorCaught: Error | null }
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { errorCaught: null };
}
static getDerivedStateFromError(error: Error) {
return { errorCaught: error };
}
render() {
const { t } = this.props;
const ua = navigator.userAgent || "unknown";
const memory = getMemoryPerformance();
const time = new Date().toLocaleString();
const stamp = new Date().getTime();
const path = window.location.pathname;
const message = `Raised-Path: ${path}\nApp-Version: ${version}\nMemory-Usage: ${
!isNaN(memory) ? memory.toFixed(2) + " MB" : "unknown"
}\nLocale-Time: ${time}\nError-Message: ${
this.state.errorCaught?.message || "unknown"
}\nUser-Agent: ${ua}\nStack-Trace: ${
this.state.errorCaught?.stack || "unknown"
}`;
return this.state.errorCaught ? (
<div className={`error-boundary`}>
<AlertCircle className={`h-12 w-12 mt-4 mb-6`} />
<p className={`select-none text-2xl mb-4`}>{t("fatal")}</p>
<div className={`error-provider`}>
<p>Raised-Path: {path}</p>
<p>App-Version: {version}</p>
<p>
Memory-Usage:{" "}
{!isNaN(memory) ? memory.toFixed(2) + " MB" : "unknown"}
</p>
<p>Locale-Time: {time}</p>
<p>Error-Message: {this.state.errorCaught.message}</p>
<p>User-Agent: {ua}</p>
</div>
<div className={`error-action mt-4 mb-4`}>
<Button onClick={() => saveAsFile(`error-${stamp}.log`, message)}>
<Download className={`h-4 w-4 mr-2`} />
{t("download-fatal-log")}
</Button>
</div>
<div className={`error-tips select-none align-center`}>
<p>{t("fatal-tips")}</p>
</div>
</div>
) : (
this.props.children
);
}
}
export default withTranslation()(ErrorBoundary);

View File

@ -1,6 +1,7 @@
import { Message } from "@/api/types.ts";
import Markdown from "@/components/Markdown.tsx";
import {
CircleSlash,
Cloud,
CloudFog,
Copy,
@ -77,6 +78,8 @@ function MessageContent({ message, end, onEvent }: MessageProps) {
<div className={`message-content`}>
{message.content.length ? (
<Markdown children={message.content} />
) : message.end === true ? (
<CircleSlash className={`h-5 w-5 m-1`} />
) : (
<Loader2 className={`h-5 w-5 m-1 animate-spin`} />
)}

View File

@ -34,6 +34,10 @@ const resources = {
unknown: "未知",
"scroll-down": "滚至最新",
broadcast: "公告",
fatal: "应用崩溃",
"download-fatal-log": "下载错误日志",
"fatal-tips":
"请您先检查您的网络,浏览器兼容性,尝试清除浏览器缓存并刷新页面。如果问题仍然存在,请将日志提供给开发者以便我们排查问题。",
tag: {
free: "免费",
official: "官方",
@ -370,6 +374,10 @@ const resources = {
unknown: "Unknown",
"scroll-down": "Scroll to latest",
broadcast: "Broadcast",
fatal: "App crashed",
"download-fatal-log": "Download error log",
"fatal-tips":
"Please try to check your network, browser compatibility, try to clear the browser cache and refresh the page. If the problem still exists, please provide the log to the developer so that we can troubleshoot the problem.",
tag: {
free: "Free",
official: "Official",
@ -722,6 +730,10 @@ const resources = {
unknown: "Неизвестный",
"scroll-down": "Прокрутите вниз",
broadcast: "Объявление",
fatal: "Приложение вылетело",
"download-fatal-log": "Скачать журнал ошибок",
"fatal-tips":
"Пожалуйста, попробуйте проверить свою сеть, совместимость браузера, попробуйте очистить кэш браузера и обновить страницу. Если проблема все еще существует, пожалуйста, предоставьте журнал разработчику, чтобы мы могли устранить проблему.",
tag: {
free: "Бесплатно",
official: "Официальный",

View File

@ -5,15 +5,18 @@ import SideBar from "@/components/home/SideBar.tsx";
import { useSelector } from "react-redux";
import { selectMarket } from "@/store/chat.ts";
import ModelMarket from "@/components/home/ModelMarket.tsx";
import ErrorBoundary from "@/components/ErrorBoundary.tsx";
function Home() {
const market = useSelector(selectMarket);
return (
<div className={`main`}>
<SideBar />
{market ? <ModelMarket /> : <ChatWrapper />}
</div>
<ErrorBoundary>
<div className={`main`}>
<SideBar />
{market ? <ModelMarket /> : <ChatWrapper />}
</div>
</ErrorBoundary>
);
}