mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 05:11:24 +08:00
refactor(errorHandling): improve error serialization and update error handling in callbacks
- Updated the error handling in the `onError` callback to use `AISDKError` type for better type safety. - Introduced a new `serializeError` function to standardize error serialization. - Modified the `ErrorBlock` component to directly access the error message. - Removed deprecated error formatting functions to streamline the error utility module.
This commit is contained in:
parent
a30df46c40
commit
fee6ad58d1
@ -32,7 +32,7 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }>
|
||||
return (
|
||||
<Alert
|
||||
description={getHttpMessageLabel(block.error.status)}
|
||||
message={block.error?.message}
|
||||
message={block.error.message}
|
||||
type="error"
|
||||
closable
|
||||
onClose={onRemoveBlock}
|
||||
|
||||
@ -15,10 +15,11 @@ import {
|
||||
PlaceholderMessageBlock
|
||||
} from '@renderer/types/newMessage'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { formatErrorMessage, isAbortError } from '@renderer/utils/error'
|
||||
import { isAbortError, serializeError } from '@renderer/utils/error'
|
||||
import { createBaseMessageBlock, createErrorBlock } from '@renderer/utils/messageUtils/create'
|
||||
import { findAllBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||
import { isFocused, isOnHomePage } from '@renderer/utils/window'
|
||||
import { AISDKError } from 'ai'
|
||||
|
||||
import { BlockManager } from '../BlockManager'
|
||||
|
||||
@ -79,21 +80,12 @@ export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => {
|
||||
// await blockManager.handleBlockTransition(baseBlock as PlaceholderMessageBlock, MessageBlockType.UNKNOWN)
|
||||
// },
|
||||
|
||||
onError: async (error: any) => {
|
||||
onError: async (error: AISDKError) => {
|
||||
logger.debug('onError', error)
|
||||
const isErrorTypeAbort = isAbortError(error)
|
||||
let pauseErrorLanguagePlaceholder = ''
|
||||
const serializableError = serializeError(error)
|
||||
if (isErrorTypeAbort) {
|
||||
pauseErrorLanguagePlaceholder = 'pause_placeholder'
|
||||
}
|
||||
|
||||
const serializableError = {
|
||||
name: error.name,
|
||||
message: pauseErrorLanguagePlaceholder || error.message || formatErrorMessage(error),
|
||||
originalMessage: error.message,
|
||||
stack: error.stack,
|
||||
status: error.status || error.code,
|
||||
requestId: error.request_id
|
||||
serializableError.message = 'pause_placeholder'
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime
|
||||
|
||||
@ -50,7 +50,7 @@ export interface BaseMessageBlock {
|
||||
status: MessageBlockStatus // 块状态
|
||||
model?: Model // 使用的模型
|
||||
metadata?: Record<string, any> // 通用元数据
|
||||
error?: Record<string, any> // Added optional error field to base
|
||||
error?: Record<string, any> // Serializable error object instead of AISDKError
|
||||
}
|
||||
|
||||
export interface PlaceholderMessageBlock extends BaseMessageBlock {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { formatErrorMessage, formatMessageError, getErrorDetails, getErrorMessage, isAbortError } from '../error'
|
||||
import { formatErrorMessage, getErrorDetails, isAbortError } from '../error'
|
||||
|
||||
describe('error', () => {
|
||||
describe('getErrorDetails', () => {
|
||||
@ -124,79 +124,6 @@ describe('error', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatMessageError', () => {
|
||||
it('should return error details as an object', () => {
|
||||
const error = new Error('Test error')
|
||||
const result = formatMessageError(error)
|
||||
|
||||
expect(result.message).toBe('Test error')
|
||||
expect(result.stack).toBeUndefined()
|
||||
expect(result.headers).toBeUndefined()
|
||||
expect(result.request_id).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle string errors', () => {
|
||||
const result = formatMessageError('String error')
|
||||
expect(typeof result).toBe('string')
|
||||
expect(result).toBe('String error')
|
||||
})
|
||||
|
||||
it('should handle formatting errors', () => {
|
||||
const problematicError = {
|
||||
get message() {
|
||||
throw new Error('Cannot access')
|
||||
},
|
||||
toString: () => 'Error object'
|
||||
}
|
||||
|
||||
const result = formatMessageError(problematicError)
|
||||
expect(result).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should handle completely invalid errors', () => {
|
||||
let invalidError: any
|
||||
try {
|
||||
invalidError = Object.create(null)
|
||||
Object.defineProperty(invalidError, 'toString', {
|
||||
get: () => {
|
||||
throw new Error()
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
invalidError = {
|
||||
toString() {
|
||||
throw new Error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = formatMessageError(invalidError)
|
||||
expect(result).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getErrorMessage', () => {
|
||||
it('should extract message from Error objects', () => {
|
||||
const error = new Error('Test message')
|
||||
expect(getErrorMessage(error)).toBe('Test message')
|
||||
})
|
||||
|
||||
it('should handle objects with message property', () => {
|
||||
const errorObj = { message: 'Object message' }
|
||||
expect(getErrorMessage(errorObj)).toBe('Object message')
|
||||
})
|
||||
|
||||
it('should convert non-Error objects to string', () => {
|
||||
const obj = { toString: () => 'Custom toString' }
|
||||
expect(getErrorMessage(obj)).toBe('Custom toString')
|
||||
})
|
||||
|
||||
it('should return empty string for undefined or null', () => {
|
||||
expect(getErrorMessage(undefined)).toBe('')
|
||||
expect(getErrorMessage(null)).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isAbortError', () => {
|
||||
it('should identify OpenAI abort errors by message', () => {
|
||||
const openaiError = { message: 'Request was aborted.' }
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { AISDKError, APICallError } from 'ai'
|
||||
import { t } from 'i18next'
|
||||
|
||||
const logger = loggerService.withContext('Utils:error')
|
||||
@ -53,26 +54,6 @@ export function formatErrorMessage(error: any): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function formatMessageError(error: any): Record<string, any> {
|
||||
try {
|
||||
const detailedError = getErrorDetails(error)
|
||||
delete detailedError?.headers
|
||||
delete detailedError?.stack
|
||||
delete detailedError?.request_id
|
||||
return detailedError
|
||||
} catch (e) {
|
||||
try {
|
||||
return { message: String(error) }
|
||||
} catch {
|
||||
return { message: 'Error: Unable to format error message' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getErrorMessage(error: any): string {
|
||||
return error?.message || error?.toString() || ''
|
||||
}
|
||||
|
||||
export const isAbortError = (error: any): boolean => {
|
||||
// Convert message to string for consistent checking
|
||||
const errorMessage = String(error?.message || '')
|
||||
@ -106,3 +87,23 @@ export const formatMcpError = (error: any) => {
|
||||
}
|
||||
return error.message
|
||||
}
|
||||
|
||||
export const serializeError = (error: AISDKError) => {
|
||||
const baseError = {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
cause: error.cause ? String(error.cause) : undefined
|
||||
}
|
||||
if (APICallError.isInstance(error)) {
|
||||
let content = error.message === '' ? error.responseBody || 'Unknown error' : error.message
|
||||
try {
|
||||
const obj = JSON.parse(content)
|
||||
content = obj.error.message
|
||||
} catch (e: any) {
|
||||
logger.warn('Error parsing error response body:', e)
|
||||
}
|
||||
return { ...baseError, status: error.statusCode, url: error.url, message: content }
|
||||
}
|
||||
return baseError
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user