mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-25 03:10:08 +08:00
fix: Optimize error message formatting (#5988)
* fix: Optimize error message formatting * fix: improve error unit test * refactor: simplify error handling in ErrorBlock component - Replaced custom StyledAlert with a more streamlined Alert component for error messages. - Reduced complexity by removing unnecessary JSX wrappers and improving readability. - Adjusted styling for the Alert component to maintain visual consistency. * fix: update error handling in ErrorBlock component - Removed unnecessary message prop from Alert component to simplify error display. - Maintained existing error handling logic while improving code clarity.
This commit is contained in:
parent
6e50030237
commit
aadcbc67cf
@ -84,6 +84,7 @@ export type OpenAIStreamChunk =
|
||||
| { type: 'reasoning' | 'text-delta'; textDelta: string }
|
||||
| { type: 'tool-calls'; delta: any }
|
||||
| { type: 'finish'; finishReason: any; usage: any; delta: any; chunk: any }
|
||||
| { type: 'unknown'; chunk: any }
|
||||
|
||||
export default class OpenAIProvider extends BaseOpenAIProvider {
|
||||
constructor(provider: Provider) {
|
||||
@ -631,21 +632,25 @@ export default class OpenAIProvider extends BaseOpenAIProvider {
|
||||
break
|
||||
}
|
||||
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if (delta?.reasoning_content || delta?.reasoning) {
|
||||
yield { type: 'reasoning', textDelta: delta.reasoning_content || delta.reasoning }
|
||||
}
|
||||
if (delta?.content) {
|
||||
yield { type: 'text-delta', textDelta: delta.content }
|
||||
}
|
||||
if (delta?.tool_calls) {
|
||||
yield { type: 'tool-calls', delta: delta }
|
||||
}
|
||||
if (chunk.choices && chunk.choices.length > 0) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if (delta?.reasoning_content || delta?.reasoning) {
|
||||
yield { type: 'reasoning', textDelta: delta.reasoning_content || delta.reasoning }
|
||||
}
|
||||
if (delta?.content) {
|
||||
yield { type: 'text-delta', textDelta: delta.content }
|
||||
}
|
||||
if (delta?.tool_calls) {
|
||||
yield { type: 'tool-calls', delta: delta }
|
||||
}
|
||||
|
||||
const finishReason = chunk.choices[0]?.finish_reason
|
||||
if (!isEmpty(finishReason)) {
|
||||
yield { type: 'finish', finishReason, usage: chunk.usage, delta, chunk }
|
||||
break
|
||||
const finishReason = chunk?.choices[0]?.finish_reason
|
||||
if (!isEmpty(finishReason)) {
|
||||
yield { type: 'finish', finishReason, usage: chunk.usage, delta, chunk }
|
||||
break
|
||||
}
|
||||
} else {
|
||||
yield { type: 'unknown', chunk }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -832,6 +837,12 @@ export default class OpenAIProvider extends BaseOpenAIProvider {
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'unknown': {
|
||||
onChunk({
|
||||
type: ChunkType.ERROR,
|
||||
error: chunk.chunk
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -88,6 +88,9 @@ export function createStreamProcessor(callbacks: StreamProcessorCallbacks = {})
|
||||
if (data.type === ChunkType.IMAGE_COMPLETE && callbacks.onImageGenerated) {
|
||||
callbacks.onImageGenerated(data.image)
|
||||
}
|
||||
if (data.type === ChunkType.ERROR && callbacks.onError) {
|
||||
callbacks.onError(data.error)
|
||||
}
|
||||
// Note: Usage and Metrics are usually handled at the end or accumulated differently,
|
||||
// so direct callbacks might not be the best fit here. They are often part of the final message state.
|
||||
} catch (error) {
|
||||
|
||||
@ -20,7 +20,7 @@ import type {
|
||||
import { AssistantMessageStatus, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
||||
import { Response } from '@renderer/types/newMessage'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { isAbortError } from '@renderer/utils/error'
|
||||
import { formatErrorMessage, isAbortError } from '@renderer/utils/error'
|
||||
import { extractUrlsFromMarkdown } from '@renderer/utils/linkConverter'
|
||||
import {
|
||||
createAssistantMessage,
|
||||
@ -587,7 +587,7 @@ const fetchAndProcessAssistantResponseImpl = async (
|
||||
|
||||
const serializableError = {
|
||||
name: error.name,
|
||||
message: pauseErrorLanguagePlaceholder || error.message || 'Stream processing error',
|
||||
message: pauseErrorLanguagePlaceholder || error.message || formatErrorMessage(error),
|
||||
originalMessage: error.message,
|
||||
stack: error.stack,
|
||||
status: error.status || error.code,
|
||||
|
||||
@ -50,19 +50,21 @@ describe('error', () => {
|
||||
})
|
||||
|
||||
describe('formatErrorMessage', () => {
|
||||
it('should format error as JSON string', () => {
|
||||
console.error = vi.fn() // Mock console.error
|
||||
it('should format error with indentation and header', () => {
|
||||
console.error = vi.fn()
|
||||
|
||||
const error = new Error('Test error')
|
||||
const result = formatErrorMessage(error)
|
||||
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
expect(result).toContain('```json')
|
||||
expect(result).toContain('"message": "Test error"')
|
||||
expect(result).toContain('Error Details:')
|
||||
expect(result).toContain(' {')
|
||||
expect(result).toContain(' "message": "Test error"')
|
||||
expect(result).toContain(' }')
|
||||
expect(result).not.toContain('"stack":')
|
||||
})
|
||||
|
||||
it('should remove sensitive information', () => {
|
||||
it('should remove sensitive information and format with proper indentation', () => {
|
||||
console.error = vi.fn()
|
||||
|
||||
const error = {
|
||||
@ -74,27 +76,30 @@ describe('error', () => {
|
||||
|
||||
const result = formatErrorMessage(error)
|
||||
|
||||
expect(result).toContain('"message": "API error"')
|
||||
expect(result).toContain('Error Details:')
|
||||
expect(result).toContain(' {')
|
||||
expect(result).toContain(' "message": "API error"')
|
||||
expect(result).toContain(' }')
|
||||
expect(result).not.toContain('Authorization')
|
||||
expect(result).not.toContain('stack')
|
||||
expect(result).not.toContain('request_id')
|
||||
})
|
||||
|
||||
it('should handle errors during formatting', () => {
|
||||
it('should handle errors during formatting with simple error message', () => {
|
||||
console.error = vi.fn()
|
||||
|
||||
const problematicError = {
|
||||
get message() {
|
||||
throw new Error('Cannot access message')
|
||||
throw new Error('Cannot access')
|
||||
}
|
||||
}
|
||||
|
||||
const result = formatErrorMessage(problematicError)
|
||||
expect(result).toContain('```')
|
||||
expect(result).toContain('Unable')
|
||||
expect(result).toContain('Error Details:')
|
||||
expect(result).toContain('"message": "<Unable to access property>"')
|
||||
})
|
||||
|
||||
it('should handle non-serializable errors', () => {
|
||||
it('should handle non-serializable errors with simple error message', () => {
|
||||
console.error = vi.fn()
|
||||
|
||||
const nonSerializableError = {
|
||||
@ -114,7 +119,8 @@ describe('error', () => {
|
||||
}
|
||||
|
||||
const result = formatErrorMessage(nonSerializableError)
|
||||
expect(result).toBeTruthy()
|
||||
expect(result).toContain('Error Details:')
|
||||
expect(result).toContain('"toString": "<Unable to access property>"')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -35,10 +35,15 @@ export function formatErrorMessage(error: any): string {
|
||||
delete detailedError?.headers
|
||||
delete detailedError?.stack
|
||||
delete detailedError?.request_id
|
||||
return '```json\n' + JSON.stringify(detailedError, null, 2) + '\n```'
|
||||
|
||||
const formattedJson = JSON.stringify(detailedError, null, 2)
|
||||
.split('\n')
|
||||
.map((line) => ` ${line}`)
|
||||
.join('\n')
|
||||
return `Error Details:\n${formattedJson}`
|
||||
} catch (e) {
|
||||
try {
|
||||
return '```\n' + String(error) + '\n```'
|
||||
return `Error: ${String(error)}`
|
||||
} catch {
|
||||
return 'Error: Unable to format error message'
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user