diff --git a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx index 68b2987cc9..146e6d5c95 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx @@ -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 ( = ({ block }) => { return t(errorKey) } - if (HTTP_ERROR_CODES.includes(errorStatus)) { + if (typeof errorStatus === 'number' && HTTP_ERROR_CODES.includes(errorStatus)) { return (
- {getHttpMessageLabel(errorStatus)} {block.error?.message} + {getHttpMessageLabel(errorStatus.toString())} {block.error?.message}
) } @@ -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 } @@ -125,7 +134,7 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }> interface ErrorDetailModalProps { open: boolean onClose: () => void - error?: Record + error?: SerializedError } const ErrorDetailModal: React.FC = ({ open, onClose, error }) => { @@ -146,28 +155,17 @@ const ErrorDetailModal: React.FC = ({ open, onClose, erro navigator.clipboard.writeText(errorText) } - const renderErrorDetails = (error?: Record) => { + const renderErrorDetails = (error?: SerializedError) => { if (!error) return
{t('error.unknown')}
- if (APICallError.isInstance(error)) { + if (isSerializedAiSdkAPICallError(error)) { return } - if (AISDKError.isInstance(error)) { + if (isSerializedAiSdkError(error)) { return } - if (error instanceof Error) { - return ( - - - - ) - } - - // default return ( - - {t('error.unknown')}: - + ) } @@ -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 类型,暂且不清楚都可能是什么类型,总之先覆盖下大部分场景 diff --git a/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts index 2c7ac234a8..a561ba937e 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts @@ -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' diff --git a/src/renderer/src/types/error.ts b/src/renderer/src/types/error.ts new file mode 100644 index 0000000000..de4aadbaf5 --- /dev/null +++ b/src/renderer/src/types/error.ts @@ -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 + 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 +} diff --git a/src/renderer/src/types/newMessage.ts b/src/renderer/src/types/newMessage.ts index 7a33c00b81..f0b49ce336 100644 --- a/src/renderer/src/types/newMessage.ts +++ b/src/renderer/src/types/newMessage.ts @@ -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 // 通用元数据 - error?: Record // Serializable error object instead of AISDKError + error?: SerializedError // Serializable error object instead of AISDKError } export interface PlaceholderMessageBlock extends BaseMessageBlock { diff --git a/src/renderer/src/utils/error.ts b/src/renderer/src/utils/error.ts index 5c9279c827..55cb6936f4 100644 --- a/src/renderer/src/utils/error.ts +++ b/src/renderer/src/utils/error.ts @@ -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,