mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-27 21:01:32 +08:00
feat(错误处理): 统一使用SerializedError类型处理错误并增强类型安全
重构错误处理逻辑,使用@reduxjs/toolkit的SerializedError类型统一处理错误 新增错误类型定义文件并扩展SerializedError接口 更新相关组件和工具函数以适配新的错误类型
This commit is contained in:
parent
d23983a5b2
commit
a681c28a0a
@ -1,12 +1,19 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit'
|
||||
import CodeViewer from '@renderer/components/CodeViewer'
|
||||
import { useTimer } from '@renderer/hooks/useTimer'
|
||||
import { getHttpMessageLabel, getProviderLabel } from '@renderer/i18n/label'
|
||||
import { getProviderById } from '@renderer/services/ProviderService'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { removeBlocksThunk } from '@renderer/store/thunk/messageThunk'
|
||||
import {
|
||||
isSerializedAiSdkAPICallError,
|
||||
isSerializedAiSdkError,
|
||||
SerializedAiSdkAPICallError,
|
||||
SerializedAiSdkError
|
||||
} from '@renderer/types/error'
|
||||
import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage'
|
||||
import { formatAiSdkError, formatError, safeToString } from '@renderer/utils/error'
|
||||
import { AISDKError, APICallError } from 'ai'
|
||||
import { AISDKError } from 'ai'
|
||||
import { Alert as AntdAlert, Button, Modal } from 'antd'
|
||||
import React, { useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
@ -33,7 +40,7 @@ const ErrorMessage: React.FC<{ block: ErrorMessageBlock }> = ({ block }) => {
|
||||
|
||||
if (i18n.exists(i18nKey)) {
|
||||
const providerId = block.error?.providerId
|
||||
if (providerId) {
|
||||
if (providerId && typeof providerId === 'string') {
|
||||
return (
|
||||
<Trans
|
||||
i18nKey={i18nKey}
|
||||
@ -56,10 +63,10 @@ const ErrorMessage: React.FC<{ block: ErrorMessageBlock }> = ({ block }) => {
|
||||
return t(errorKey)
|
||||
}
|
||||
|
||||
if (HTTP_ERROR_CODES.includes(errorStatus)) {
|
||||
if (typeof errorStatus === 'number' && HTTP_ERROR_CODES.includes(errorStatus)) {
|
||||
return (
|
||||
<h5>
|
||||
{getHttpMessageLabel(errorStatus)} {block.error?.message}
|
||||
{getHttpMessageLabel(errorStatus.toString())} {block.error?.message}
|
||||
</h5>
|
||||
)
|
||||
}
|
||||
@ -82,15 +89,17 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }>
|
||||
}
|
||||
|
||||
const getAlertMessage = () => {
|
||||
if (block.error && HTTP_ERROR_CODES.includes(block.error?.status)) {
|
||||
const status = block.error?.status
|
||||
if (block.error && typeof status === 'number' && HTTP_ERROR_CODES.includes(status)) {
|
||||
return block.error.message
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const getAlertDescription = () => {
|
||||
if (block.error && HTTP_ERROR_CODES.includes(block.error?.status)) {
|
||||
return getHttpMessageLabel(block.error.status)
|
||||
const status = block.error?.status
|
||||
if (block.error && typeof status === 'number' && HTTP_ERROR_CODES.includes(status)) {
|
||||
return getHttpMessageLabel(status.toString())
|
||||
}
|
||||
return <ErrorMessage block={block} />
|
||||
}
|
||||
@ -125,7 +134,7 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }>
|
||||
interface ErrorDetailModalProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
error?: Record<string, any>
|
||||
error?: SerializedError
|
||||
}
|
||||
|
||||
const ErrorDetailModal: React.FC<ErrorDetailModalProps> = ({ open, onClose, error }) => {
|
||||
@ -146,28 +155,17 @@ const ErrorDetailModal: React.FC<ErrorDetailModalProps> = ({ open, onClose, erro
|
||||
navigator.clipboard.writeText(errorText)
|
||||
}
|
||||
|
||||
const renderErrorDetails = (error?: Record<string, any>) => {
|
||||
const renderErrorDetails = (error?: SerializedError) => {
|
||||
if (!error) return <div>{t('error.unknown')}</div>
|
||||
if (APICallError.isInstance(error)) {
|
||||
if (isSerializedAiSdkAPICallError(error)) {
|
||||
return <AiApiCallError error={error} />
|
||||
}
|
||||
if (AISDKError.isInstance(error)) {
|
||||
if (isSerializedAiSdkError(error)) {
|
||||
return <AiSdkError error={error} />
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return (
|
||||
<ErrorDetailList>
|
||||
<BuiltinError error={error} />
|
||||
</ErrorDetailList>
|
||||
)
|
||||
}
|
||||
|
||||
// default
|
||||
return (
|
||||
<ErrorDetailList>
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.unknown')}:</ErrorDetailLabel>
|
||||
</ErrorDetailItem>
|
||||
<BuiltinError error={error} />
|
||||
</ErrorDetailList>
|
||||
)
|
||||
}
|
||||
@ -253,7 +251,7 @@ const Alert = styled(AntdAlert)`
|
||||
`
|
||||
|
||||
// 作为 base,渲染公共字段,应当在 ErrorDetailList 中渲染
|
||||
const BuiltinError = ({ error }: { error: Error }) => {
|
||||
const BuiltinError = ({ error }: { error: SerializedError }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<>
|
||||
@ -282,7 +280,7 @@ const BuiltinError = ({ error }: { error: Error }) => {
|
||||
}
|
||||
|
||||
// 作为 base,渲染公共字段,应当在 ErrorDetailList 中渲染
|
||||
const AiSdkError = ({ error }: { error: AISDKError }) => {
|
||||
const AiSdkError = ({ error }: { error: SerializedAiSdkError }) => {
|
||||
const { t } = useTranslation()
|
||||
const cause = safeToString(error.cause)
|
||||
return (
|
||||
@ -298,7 +296,7 @@ const AiSdkError = ({ error }: { error: AISDKError }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const AiApiCallError = ({ error }: { error: APICallError }) => {
|
||||
const AiApiCallError = ({ error }: { error: SerializedAiSdkAPICallError }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
// 这些字段是 unknown 类型,暂且不清楚都可能是什么类型,总之先覆盖下大部分场景
|
||||
|
||||
@ -100,7 +100,7 @@ export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => {
|
||||
id: uuid(),
|
||||
type: 'error',
|
||||
title: i18n.t('notification.assistant'),
|
||||
message: serializableError.message,
|
||||
message: serializableError.message ?? '',
|
||||
silent: false,
|
||||
timestamp: Date.now(),
|
||||
source: 'assistant'
|
||||
|
||||
30
src/renderer/src/types/error.ts
Normal file
30
src/renderer/src/types/error.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit'
|
||||
|
||||
// 定义模块增强以扩展 @reduxjs/toolkit 的 SerializedError
|
||||
declare module '@reduxjs/toolkit' {
|
||||
interface SerializedError {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export interface SerializedAiSdkError extends SerializedError {
|
||||
readonly cause?: unknown
|
||||
}
|
||||
|
||||
export const isSerializedAiSdkError = (error: SerializedError): error is SerializedAiSdkError => {
|
||||
return 'cause' in error
|
||||
}
|
||||
|
||||
export interface SerializedAiSdkAPICallError extends SerializedAiSdkError {
|
||||
readonly url: string
|
||||
readonly requestBodyValues: unknown
|
||||
readonly statusCode?: number
|
||||
readonly responseHeaders?: Record<string, string>
|
||||
readonly responseBody?: string
|
||||
readonly isRetryable: boolean
|
||||
readonly data?: unknown
|
||||
}
|
||||
|
||||
export const isSerializedAiSdkAPICallError = (error: SerializedError): error is SerializedAiSdkAPICallError => {
|
||||
return isSerializedAiSdkError(error) && 'url' in error && 'requestBodyValues' in error && 'isRetryable' in error
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit'
|
||||
import type { CompletionUsage } from 'openai/resources'
|
||||
|
||||
import type {
|
||||
@ -50,7 +51,7 @@ export interface BaseMessageBlock {
|
||||
status: MessageBlockStatus // 块状态
|
||||
model?: Model // 使用的模型
|
||||
metadata?: Record<string, any> // 通用元数据
|
||||
error?: Record<string, any> // Serializable error object instead of AISDKError
|
||||
error?: SerializedError // Serializable error object instead of AISDKError
|
||||
}
|
||||
|
||||
export interface PlaceholderMessageBlock extends BaseMessageBlock {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { SerializedError } from '@reduxjs/toolkit'
|
||||
import { AiSdkErrorUnion } from '@renderer/types/aiCoreTypes'
|
||||
import { AISDKError, APICallError } from 'ai'
|
||||
import { t } from 'i18next'
|
||||
@ -88,7 +89,7 @@ export const formatMcpError = (error: any) => {
|
||||
return error.message
|
||||
}
|
||||
|
||||
export const serializeError = (error: AISDKError) => {
|
||||
export const serializeError = (error: AISDKError): SerializedError => {
|
||||
const baseError = {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user