mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-05 04:19:02 +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 (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
description={getHttpMessageLabel(block.error.status)}
|
description={getHttpMessageLabel(block.error.status)}
|
||||||
message={block.error?.message}
|
message={block.error.message}
|
||||||
type="error"
|
type="error"
|
||||||
closable
|
closable
|
||||||
onClose={onRemoveBlock}
|
onClose={onRemoveBlock}
|
||||||
|
|||||||
@ -15,10 +15,11 @@ import {
|
|||||||
PlaceholderMessageBlock
|
PlaceholderMessageBlock
|
||||||
} from '@renderer/types/newMessage'
|
} from '@renderer/types/newMessage'
|
||||||
import { uuid } from '@renderer/utils'
|
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 { createBaseMessageBlock, createErrorBlock } from '@renderer/utils/messageUtils/create'
|
||||||
import { findAllBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
import { findAllBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||||
import { isFocused, isOnHomePage } from '@renderer/utils/window'
|
import { isFocused, isOnHomePage } from '@renderer/utils/window'
|
||||||
|
import { AISDKError } from 'ai'
|
||||||
|
|
||||||
import { BlockManager } from '../BlockManager'
|
import { BlockManager } from '../BlockManager'
|
||||||
|
|
||||||
@ -79,21 +80,12 @@ export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => {
|
|||||||
// await blockManager.handleBlockTransition(baseBlock as PlaceholderMessageBlock, MessageBlockType.UNKNOWN)
|
// await blockManager.handleBlockTransition(baseBlock as PlaceholderMessageBlock, MessageBlockType.UNKNOWN)
|
||||||
// },
|
// },
|
||||||
|
|
||||||
onError: async (error: any) => {
|
onError: async (error: AISDKError) => {
|
||||||
logger.debug('onError', error)
|
logger.debug('onError', error)
|
||||||
const isErrorTypeAbort = isAbortError(error)
|
const isErrorTypeAbort = isAbortError(error)
|
||||||
let pauseErrorLanguagePlaceholder = ''
|
const serializableError = serializeError(error)
|
||||||
if (isErrorTypeAbort) {
|
if (isErrorTypeAbort) {
|
||||||
pauseErrorLanguagePlaceholder = 'pause_placeholder'
|
serializableError.message = '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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export interface BaseMessageBlock {
|
|||||||
status: MessageBlockStatus // 块状态
|
status: MessageBlockStatus // 块状态
|
||||||
model?: Model // 使用的模型
|
model?: Model // 使用的模型
|
||||||
metadata?: Record<string, any> // 通用元数据
|
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 {
|
export interface PlaceholderMessageBlock extends BaseMessageBlock {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { formatErrorMessage, formatMessageError, getErrorDetails, getErrorMessage, isAbortError } from '../error'
|
import { formatErrorMessage, getErrorDetails, isAbortError } from '../error'
|
||||||
|
|
||||||
describe('error', () => {
|
describe('error', () => {
|
||||||
describe('getErrorDetails', () => {
|
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', () => {
|
describe('isAbortError', () => {
|
||||||
it('should identify OpenAI abort errors by message', () => {
|
it('should identify OpenAI abort errors by message', () => {
|
||||||
const openaiError = { message: 'Request was aborted.' }
|
const openaiError = { message: 'Request was aborted.' }
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { AISDKError, APICallError } from 'ai'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
|
|
||||||
const logger = loggerService.withContext('Utils:error')
|
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 => {
|
export const isAbortError = (error: any): boolean => {
|
||||||
// Convert message to string for consistent checking
|
// Convert message to string for consistent checking
|
||||||
const errorMessage = String(error?.message || '')
|
const errorMessage = String(error?.message || '')
|
||||||
@ -106,3 +87,23 @@ export const formatMcpError = (error: any) => {
|
|||||||
}
|
}
|
||||||
return error.message
|
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