mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
💄 style: optimize nana banana pro error message (#10378)
This commit is contained in:
@@ -8,6 +8,7 @@ import { createGoogleImage } from './createImage';
|
||||
|
||||
const provider = 'google';
|
||||
const bizErrorType = 'ProviderBizError';
|
||||
const noImageErrorType = 'ProviderNoImageGenerated';
|
||||
const invalidErrorType = 'InvalidProviderAPIKey';
|
||||
|
||||
// Mock the console.error to avoid polluting test output
|
||||
@@ -201,7 +202,7 @@ describe('createGoogleImage', () => {
|
||||
// Act & Assert - Test error behavior rather than specific text
|
||||
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
|
||||
expect.objectContaining({
|
||||
errorType: bizErrorType,
|
||||
errorType: noImageErrorType,
|
||||
provider,
|
||||
}),
|
||||
);
|
||||
@@ -224,7 +225,7 @@ describe('createGoogleImage', () => {
|
||||
// Act & Assert
|
||||
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
|
||||
expect.objectContaining({
|
||||
errorType: bizErrorType,
|
||||
errorType: noImageErrorType,
|
||||
provider,
|
||||
}),
|
||||
);
|
||||
@@ -251,7 +252,7 @@ describe('createGoogleImage', () => {
|
||||
// Act & Assert
|
||||
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
|
||||
expect.objectContaining({
|
||||
errorType: bizErrorType,
|
||||
errorType: noImageErrorType,
|
||||
provider,
|
||||
}),
|
||||
);
|
||||
@@ -602,7 +603,7 @@ describe('createGoogleImage', () => {
|
||||
// Act & Assert
|
||||
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
|
||||
expect.objectContaining({
|
||||
errorType: bizErrorType,
|
||||
errorType: noImageErrorType,
|
||||
provider,
|
||||
}),
|
||||
);
|
||||
@@ -627,7 +628,7 @@ describe('createGoogleImage', () => {
|
||||
// Act & Assert
|
||||
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
|
||||
expect.objectContaining({
|
||||
errorType: bizErrorType,
|
||||
errorType: noImageErrorType,
|
||||
provider,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -47,7 +47,11 @@ async function processImageForParts(imageUrl: string): Promise<Part> {
|
||||
*/
|
||||
function extractImageFromResponse(response: any): CreateImageResponse {
|
||||
const candidate = response.candidates?.[0];
|
||||
if (candidate?.finishReason === 'NO_IMAGE') {
|
||||
throw new Error('No image generated');
|
||||
}
|
||||
if (!candidate?.content?.parts) {
|
||||
// Handle cases where Google returns 200 but omits image parts (often moderation)
|
||||
throw new Error('No image generated');
|
||||
}
|
||||
|
||||
@@ -58,6 +62,7 @@ function extractImageFromResponse(response: any): CreateImageResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback when no inlineData is present (commonly moderation or policy blocks)
|
||||
throw new Error('No image data found in response');
|
||||
}
|
||||
|
||||
@@ -79,16 +84,11 @@ async function generateByImageModel(
|
||||
prompt: params.prompt,
|
||||
});
|
||||
|
||||
if (!response.generatedImages || response.generatedImages.length === 0) {
|
||||
throw new Error('No images generated');
|
||||
const imageBytes = response.generatedImages?.[0]?.image?.imageBytes;
|
||||
if (!imageBytes) {
|
||||
throw new Error('No image generated');
|
||||
}
|
||||
|
||||
const generatedImage = response.generatedImages[0];
|
||||
if (!generatedImage.image || !generatedImage.image.imageBytes) {
|
||||
throw new Error('Invalid image data');
|
||||
}
|
||||
|
||||
const { imageBytes } = generatedImage.image;
|
||||
// 1. official doc use png as example
|
||||
// 2. no responseType param support like openai now.
|
||||
// I think we can just hard code png now
|
||||
@@ -189,6 +189,10 @@ export async function createGoogleImage(
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
|
||||
if ((err as any)?.errorType) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const { errorType, error: parsedError } = parseGoogleErrorMessage(err.message);
|
||||
throw AgentRuntimeError.createImage({
|
||||
error: parsedError,
|
||||
|
||||
@@ -19,14 +19,6 @@ export const AgentRuntimeErrorType = {
|
||||
OllamaBizError: 'OllamaBizError',
|
||||
OllamaServiceUnavailable: 'OllamaServiceUnavailable',
|
||||
|
||||
InvalidComfyUIArgs: 'InvalidComfyUIArgs',
|
||||
ComfyUIBizError: 'ComfyUIBizError',
|
||||
ComfyUIServiceUnavailable: 'ComfyUIServiceUnavailable',
|
||||
ComfyUIEmptyResult: 'ComfyUIEmptyResult',
|
||||
ComfyUIUploadFailed: 'ComfyUIUploadFailed',
|
||||
ComfyUIWorkflowError: 'ComfyUIWorkflowError',
|
||||
ComfyUIModelError: 'ComfyUIModelError',
|
||||
|
||||
InvalidBedrockCredentials: 'InvalidBedrockCredentials',
|
||||
InvalidVertexCredentials: 'InvalidVertexCredentials',
|
||||
StreamChunkError: 'StreamChunkError',
|
||||
@@ -35,6 +27,17 @@ export const AgentRuntimeErrorType = {
|
||||
|
||||
ConnectionCheckFailed: 'ConnectionCheckFailed',
|
||||
|
||||
// ******* Image Generation Error ******* //
|
||||
ProviderNoImageGenerated: 'ProviderNoImageGenerated',
|
||||
|
||||
InvalidComfyUIArgs: 'InvalidComfyUIArgs',
|
||||
ComfyUIBizError: 'ComfyUIBizError',
|
||||
ComfyUIServiceUnavailable: 'ComfyUIServiceUnavailable',
|
||||
ComfyUIEmptyResult: 'ComfyUIEmptyResult',
|
||||
ComfyUIUploadFailed: 'ComfyUIUploadFailed',
|
||||
ComfyUIWorkflowError: 'ComfyUIWorkflowError',
|
||||
ComfyUIModelError: 'ComfyUIModelError',
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@@ -104,6 +104,11 @@ export function parseGoogleErrorMessage(message: string): ParsedError {
|
||||
return { error: { message }, errorType: AgentRuntimeErrorType.LocationNotSupportError };
|
||||
}
|
||||
|
||||
const lowerMessage = message.toLowerCase();
|
||||
if (lowerMessage.includes('no image generated') || lowerMessage.includes('no image data')) {
|
||||
return { error: { message }, errorType: AgentRuntimeErrorType.ProviderNoImageGenerated };
|
||||
}
|
||||
|
||||
// Unified error type determination function
|
||||
const getErrorType = (code: number | null, message: string): ILobeAgentRuntimeErrorType => {
|
||||
if (code === 400 && message.includes('API key not valid')) {
|
||||
|
||||
@@ -65,6 +65,7 @@ const checkAbortSignal = (signal: AbortSignal) => {
|
||||
const categorizeError = (
|
||||
error: any,
|
||||
isAborted: boolean,
|
||||
isEditingImage: boolean,
|
||||
): { errorMessage: string; errorType: AsyncTaskErrorType } => {
|
||||
log('🔥🔥🔥 [ASYNC] categorizeError called:', {
|
||||
errorMessage: error?.message,
|
||||
@@ -73,6 +74,7 @@ const categorizeError = (
|
||||
errorType: error?.errorType,
|
||||
fullError: JSON.stringify(error, null, 2),
|
||||
isAborted,
|
||||
isEditingImage,
|
||||
});
|
||||
// Handle Comfy UI errors
|
||||
if (error.errorType === AgentRuntimeErrorType.ComfyUIServiceUnavailable) {
|
||||
@@ -127,6 +129,15 @@ const categorizeError = (
|
||||
};
|
||||
}
|
||||
|
||||
if (error.errorType === AgentRuntimeErrorType.ProviderNoImageGenerated) {
|
||||
return {
|
||||
errorMessage: isEditingImage
|
||||
? 'Provider returned no image (maybe content review). Try a safer source image or milder prompt.'
|
||||
: 'Provider returned no image (maybe content review). Try a milder prompt or another model.',
|
||||
errorType: AsyncTaskErrorType.ServerError,
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME: 401 的问题应该放到 agentRuntime 中处理会更好
|
||||
if (error.errorType === AgentRuntimeErrorType.InvalidProviderAPIKey || error?.status === 401) {
|
||||
return {
|
||||
@@ -195,11 +206,14 @@ export const imageRouter = router({
|
||||
const abortController = new AbortController();
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const isEditingImage =
|
||||
Boolean((params as any).imageUrl) || Boolean(params.imageUrls && params.imageUrls.length > 0);
|
||||
|
||||
try {
|
||||
const imageGenerationPromise = async (signal: AbortSignal) => {
|
||||
log('Initializing agent runtime for provider: %s', provider);
|
||||
|
||||
const agentRuntime = await initModelRuntimeWithUserPayload(provider, ctx.jwtPayload);
|
||||
const agentRuntime = initModelRuntimeWithUserPayload(provider, ctx.jwtPayload);
|
||||
|
||||
// Check if operation has been cancelled
|
||||
checkAbortSignal(signal);
|
||||
@@ -328,7 +342,11 @@ export const imageRouter = router({
|
||||
});
|
||||
|
||||
// Improved error categorization logic
|
||||
const { errorType, errorMessage } = categorizeError(error, abortController.signal.aborted);
|
||||
const { errorType, errorMessage } = categorizeError(
|
||||
error,
|
||||
abortController.signal.aborted,
|
||||
isEditingImage,
|
||||
);
|
||||
|
||||
await ctx.asyncTaskModel.update(taskId, {
|
||||
error: new AsyncTaskError(errorType, errorMessage),
|
||||
|
||||
Reference in New Issue
Block a user