mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-05 04:19:02 +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: 'reasoning' | 'text-delta'; textDelta: string }
|
||||||
| { type: 'tool-calls'; delta: any }
|
| { type: 'tool-calls'; delta: any }
|
||||||
| { type: 'finish'; finishReason: any; usage: any; delta: any; chunk: any }
|
| { type: 'finish'; finishReason: any; usage: any; delta: any; chunk: any }
|
||||||
|
| { type: 'unknown'; chunk: any }
|
||||||
|
|
||||||
export default class OpenAIProvider extends BaseOpenAIProvider {
|
export default class OpenAIProvider extends BaseOpenAIProvider {
|
||||||
constructor(provider: Provider) {
|
constructor(provider: Provider) {
|
||||||
@ -631,21 +632,25 @@ export default class OpenAIProvider extends BaseOpenAIProvider {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const delta = chunk.choices[0]?.delta
|
if (chunk.choices && chunk.choices.length > 0) {
|
||||||
if (delta?.reasoning_content || delta?.reasoning) {
|
const delta = chunk.choices[0]?.delta
|
||||||
yield { type: 'reasoning', textDelta: delta.reasoning_content || delta.reasoning }
|
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?.content) {
|
||||||
}
|
yield { type: 'text-delta', textDelta: delta.content }
|
||||||
if (delta?.tool_calls) {
|
}
|
||||||
yield { type: 'tool-calls', delta: delta }
|
if (delta?.tool_calls) {
|
||||||
}
|
yield { type: 'tool-calls', delta: delta }
|
||||||
|
}
|
||||||
|
|
||||||
const finishReason = chunk.choices[0]?.finish_reason
|
const finishReason = chunk?.choices[0]?.finish_reason
|
||||||
if (!isEmpty(finishReason)) {
|
if (!isEmpty(finishReason)) {
|
||||||
yield { type: 'finish', finishReason, usage: chunk.usage, delta, chunk }
|
yield { type: 'finish', finishReason, usage: chunk.usage, delta, chunk }
|
||||||
break
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield { type: 'unknown', chunk }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -832,6 +837,12 @@ export default class OpenAIProvider extends BaseOpenAIProvider {
|
|||||||
}
|
}
|
||||||
break
|
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) {
|
if (data.type === ChunkType.IMAGE_COMPLETE && callbacks.onImageGenerated) {
|
||||||
callbacks.onImageGenerated(data.image)
|
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,
|
// 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.
|
// so direct callbacks might not be the best fit here. They are often part of the final message state.
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import type {
|
|||||||
import { AssistantMessageStatus, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
import { AssistantMessageStatus, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
||||||
import { Response } from '@renderer/types/newMessage'
|
import { Response } from '@renderer/types/newMessage'
|
||||||
import { uuid } from '@renderer/utils'
|
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 { extractUrlsFromMarkdown } from '@renderer/utils/linkConverter'
|
||||||
import {
|
import {
|
||||||
createAssistantMessage,
|
createAssistantMessage,
|
||||||
@ -587,7 +587,7 @@ const fetchAndProcessAssistantResponseImpl = async (
|
|||||||
|
|
||||||
const serializableError = {
|
const serializableError = {
|
||||||
name: error.name,
|
name: error.name,
|
||||||
message: pauseErrorLanguagePlaceholder || error.message || 'Stream processing error',
|
message: pauseErrorLanguagePlaceholder || error.message || formatErrorMessage(error),
|
||||||
originalMessage: error.message,
|
originalMessage: error.message,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
status: error.status || error.code,
|
status: error.status || error.code,
|
||||||
|
|||||||
@ -50,19 +50,21 @@ describe('error', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('formatErrorMessage', () => {
|
describe('formatErrorMessage', () => {
|
||||||
it('should format error as JSON string', () => {
|
it('should format error with indentation and header', () => {
|
||||||
console.error = vi.fn() // Mock console.error
|
console.error = vi.fn()
|
||||||
|
|
||||||
const error = new Error('Test error')
|
const error = new Error('Test error')
|
||||||
const result = formatErrorMessage(error)
|
const result = formatErrorMessage(error)
|
||||||
|
|
||||||
expect(console.error).toHaveBeenCalled()
|
expect(console.error).toHaveBeenCalled()
|
||||||
expect(result).toContain('```json')
|
expect(result).toContain('Error Details:')
|
||||||
expect(result).toContain('"message": "Test error"')
|
expect(result).toContain(' {')
|
||||||
|
expect(result).toContain(' "message": "Test error"')
|
||||||
|
expect(result).toContain(' }')
|
||||||
expect(result).not.toContain('"stack":')
|
expect(result).not.toContain('"stack":')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should remove sensitive information', () => {
|
it('should remove sensitive information and format with proper indentation', () => {
|
||||||
console.error = vi.fn()
|
console.error = vi.fn()
|
||||||
|
|
||||||
const error = {
|
const error = {
|
||||||
@ -74,27 +76,30 @@ describe('error', () => {
|
|||||||
|
|
||||||
const result = formatErrorMessage(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('Authorization')
|
||||||
expect(result).not.toContain('stack')
|
expect(result).not.toContain('stack')
|
||||||
expect(result).not.toContain('request_id')
|
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()
|
console.error = vi.fn()
|
||||||
|
|
||||||
const problematicError = {
|
const problematicError = {
|
||||||
get message() {
|
get message() {
|
||||||
throw new Error('Cannot access message')
|
throw new Error('Cannot access')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = formatErrorMessage(problematicError)
|
const result = formatErrorMessage(problematicError)
|
||||||
expect(result).toContain('```')
|
expect(result).toContain('Error Details:')
|
||||||
expect(result).toContain('Unable')
|
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()
|
console.error = vi.fn()
|
||||||
|
|
||||||
const nonSerializableError = {
|
const nonSerializableError = {
|
||||||
@ -114,7 +119,8 @@ describe('error', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = formatErrorMessage(nonSerializableError)
|
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?.headers
|
||||||
delete detailedError?.stack
|
delete detailedError?.stack
|
||||||
delete detailedError?.request_id
|
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) {
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
return '```\n' + String(error) + '\n```'
|
return `Error: ${String(error)}`
|
||||||
} catch {
|
} catch {
|
||||||
return 'Error: Unable to format error message'
|
return 'Error: Unable to format error message'
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user