@lobehub/lobehub 2.0.0-next.106 → 2.0.0-next.107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.107](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.106...v2.0.0-next.107)
6
+
7
+ <sup>Released on **2025-11-23**</sup>
8
+
9
+ #### 💄 Styles
10
+
11
+ - **misc**: Optimize nana banana pro error message.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **misc**: Optimize nana banana pro error message, closes [#10378](https://github.com/lobehub/lobe-chat/issues/10378) ([cb34757](https://github.com/lobehub/lobe-chat/commit/cb34757))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.106](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.105...v2.0.0-next.106)
6
31
 
7
32
  <sup>Released on **2025-11-23**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Optimize nana banana pro error message."
6
+ ]
7
+ },
8
+ "date": "2025-11-23",
9
+ "version": "2.0.0-next.107"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "features": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.106",
3
+ "version": "2.0.0-next.107",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -8,6 +8,7 @@ import { createGoogleImage } from './createImage';
8
8
 
9
9
  const provider = 'google';
10
10
  const bizErrorType = 'ProviderBizError';
11
+ const noImageErrorType = 'ProviderNoImageGenerated';
11
12
  const invalidErrorType = 'InvalidProviderAPIKey';
12
13
 
13
14
  // Mock the console.error to avoid polluting test output
@@ -201,7 +202,7 @@ describe('createGoogleImage', () => {
201
202
  // Act & Assert - Test error behavior rather than specific text
202
203
  await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
203
204
  expect.objectContaining({
204
- errorType: bizErrorType,
205
+ errorType: noImageErrorType,
205
206
  provider,
206
207
  }),
207
208
  );
@@ -224,7 +225,7 @@ describe('createGoogleImage', () => {
224
225
  // Act & Assert
225
226
  await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
226
227
  expect.objectContaining({
227
- errorType: bizErrorType,
228
+ errorType: noImageErrorType,
228
229
  provider,
229
230
  }),
230
231
  );
@@ -251,7 +252,7 @@ describe('createGoogleImage', () => {
251
252
  // Act & Assert
252
253
  await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
253
254
  expect.objectContaining({
254
- errorType: bizErrorType,
255
+ errorType: noImageErrorType,
255
256
  provider,
256
257
  }),
257
258
  );
@@ -602,7 +603,7 @@ describe('createGoogleImage', () => {
602
603
  // Act & Assert
603
604
  await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
604
605
  expect.objectContaining({
605
- errorType: bizErrorType,
606
+ errorType: noImageErrorType,
606
607
  provider,
607
608
  }),
608
609
  );
@@ -627,7 +628,7 @@ describe('createGoogleImage', () => {
627
628
  // Act & Assert
628
629
  await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
629
630
  expect.objectContaining({
630
- errorType: bizErrorType,
631
+ errorType: noImageErrorType,
631
632
  provider,
632
633
  }),
633
634
  );
@@ -47,7 +47,11 @@ async function processImageForParts(imageUrl: string): Promise<Part> {
47
47
  */
48
48
  function extractImageFromResponse(response: any): CreateImageResponse {
49
49
  const candidate = response.candidates?.[0];
50
+ if (candidate?.finishReason === 'NO_IMAGE') {
51
+ throw new Error('No image generated');
52
+ }
50
53
  if (!candidate?.content?.parts) {
54
+ // Handle cases where Google returns 200 but omits image parts (often moderation)
51
55
  throw new Error('No image generated');
52
56
  }
53
57
 
@@ -58,6 +62,7 @@ function extractImageFromResponse(response: any): CreateImageResponse {
58
62
  }
59
63
  }
60
64
 
65
+ // Fallback when no inlineData is present (commonly moderation or policy blocks)
61
66
  throw new Error('No image data found in response');
62
67
  }
63
68
 
@@ -79,16 +84,11 @@ async function generateByImageModel(
79
84
  prompt: params.prompt,
80
85
  });
81
86
 
82
- if (!response.generatedImages || response.generatedImages.length === 0) {
83
- throw new Error('No images generated');
84
- }
85
-
86
- const generatedImage = response.generatedImages[0];
87
- if (!generatedImage.image || !generatedImage.image.imageBytes) {
88
- throw new Error('Invalid image data');
87
+ const imageBytes = response.generatedImages?.[0]?.image?.imageBytes;
88
+ if (!imageBytes) {
89
+ throw new Error('No image generated');
89
90
  }
90
91
 
91
- const { imageBytes } = generatedImage.image;
92
92
  // 1. official doc use png as example
93
93
  // 2. no responseType param support like openai now.
94
94
  // I think we can just hard code png now
@@ -189,6 +189,10 @@ export async function createGoogleImage(
189
189
  } catch (error) {
190
190
  const err = error as Error;
191
191
 
192
+ if ((err as any)?.errorType) {
193
+ throw err;
194
+ }
195
+
192
196
  const { errorType, error: parsedError } = parseGoogleErrorMessage(err.message);
193
197
  throw AgentRuntimeError.createImage({
194
198
  error: parsedError,
@@ -19,14 +19,6 @@ export const AgentRuntimeErrorType = {
19
19
  OllamaBizError: 'OllamaBizError',
20
20
  OllamaServiceUnavailable: 'OllamaServiceUnavailable',
21
21
 
22
- InvalidComfyUIArgs: 'InvalidComfyUIArgs',
23
- ComfyUIBizError: 'ComfyUIBizError',
24
- ComfyUIServiceUnavailable: 'ComfyUIServiceUnavailable',
25
- ComfyUIEmptyResult: 'ComfyUIEmptyResult',
26
- ComfyUIUploadFailed: 'ComfyUIUploadFailed',
27
- ComfyUIWorkflowError: 'ComfyUIWorkflowError',
28
- ComfyUIModelError: 'ComfyUIModelError',
29
-
30
22
  InvalidBedrockCredentials: 'InvalidBedrockCredentials',
31
23
  InvalidVertexCredentials: 'InvalidVertexCredentials',
32
24
  StreamChunkError: 'StreamChunkError',
@@ -35,6 +27,17 @@ export const AgentRuntimeErrorType = {
35
27
 
36
28
  ConnectionCheckFailed: 'ConnectionCheckFailed',
37
29
 
30
+ // ******* Image Generation Error ******* //
31
+ ProviderNoImageGenerated: 'ProviderNoImageGenerated',
32
+
33
+ InvalidComfyUIArgs: 'InvalidComfyUIArgs',
34
+ ComfyUIBizError: 'ComfyUIBizError',
35
+ ComfyUIServiceUnavailable: 'ComfyUIServiceUnavailable',
36
+ ComfyUIEmptyResult: 'ComfyUIEmptyResult',
37
+ ComfyUIUploadFailed: 'ComfyUIUploadFailed',
38
+ ComfyUIWorkflowError: 'ComfyUIWorkflowError',
39
+ ComfyUIModelError: 'ComfyUIModelError',
40
+
38
41
  /**
39
42
  * @deprecated
40
43
  */
@@ -104,6 +104,11 @@ export function parseGoogleErrorMessage(message: string): ParsedError {
104
104
  return { error: { message }, errorType: AgentRuntimeErrorType.LocationNotSupportError };
105
105
  }
106
106
 
107
+ const lowerMessage = message.toLowerCase();
108
+ if (lowerMessage.includes('no image generated') || lowerMessage.includes('no image data')) {
109
+ return { error: { message }, errorType: AgentRuntimeErrorType.ProviderNoImageGenerated };
110
+ }
111
+
107
112
  // Unified error type determination function
108
113
  const getErrorType = (code: number | null, message: string): ILobeAgentRuntimeErrorType => {
109
114
  if (code === 400 && message.includes('API key not valid')) {
@@ -65,6 +65,7 @@ const checkAbortSignal = (signal: AbortSignal) => {
65
65
  const categorizeError = (
66
66
  error: any,
67
67
  isAborted: boolean,
68
+ isEditingImage: boolean,
68
69
  ): { errorMessage: string; errorType: AsyncTaskErrorType } => {
69
70
  log('🔥🔥🔥 [ASYNC] categorizeError called:', {
70
71
  errorMessage: error?.message,
@@ -73,6 +74,7 @@ const categorizeError = (
73
74
  errorType: error?.errorType,
74
75
  fullError: JSON.stringify(error, null, 2),
75
76
  isAborted,
77
+ isEditingImage,
76
78
  });
77
79
  // Handle Comfy UI errors
78
80
  if (error.errorType === AgentRuntimeErrorType.ComfyUIServiceUnavailable) {
@@ -127,6 +129,15 @@ const categorizeError = (
127
129
  };
128
130
  }
129
131
 
132
+ if (error.errorType === AgentRuntimeErrorType.ProviderNoImageGenerated) {
133
+ return {
134
+ errorMessage: isEditingImage
135
+ ? 'Provider returned no image (maybe content review). Try a safer source image or milder prompt.'
136
+ : 'Provider returned no image (maybe content review). Try a milder prompt or another model.',
137
+ errorType: AsyncTaskErrorType.ServerError,
138
+ };
139
+ }
140
+
130
141
  // FIXME: 401 的问题应该放到 agentRuntime 中处理会更好
131
142
  if (error.errorType === AgentRuntimeErrorType.InvalidProviderAPIKey || error?.status === 401) {
132
143
  return {
@@ -195,11 +206,14 @@ export const imageRouter = router({
195
206
  const abortController = new AbortController();
196
207
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
197
208
 
209
+ const isEditingImage =
210
+ Boolean((params as any).imageUrl) || Boolean(params.imageUrls && params.imageUrls.length > 0);
211
+
198
212
  try {
199
213
  const imageGenerationPromise = async (signal: AbortSignal) => {
200
214
  log('Initializing agent runtime for provider: %s', provider);
201
215
 
202
- const agentRuntime = await initModelRuntimeWithUserPayload(provider, ctx.jwtPayload);
216
+ const agentRuntime = initModelRuntimeWithUserPayload(provider, ctx.jwtPayload);
203
217
 
204
218
  // Check if operation has been cancelled
205
219
  checkAbortSignal(signal);
@@ -328,7 +342,11 @@ export const imageRouter = router({
328
342
  });
329
343
 
330
344
  // Improved error categorization logic
331
- const { errorType, errorMessage } = categorizeError(error, abortController.signal.aborted);
345
+ const { errorType, errorMessage } = categorizeError(
346
+ error,
347
+ abortController.signal.aborted,
348
+ isEditingImage,
349
+ );
332
350
 
333
351
  await ctx.asyncTaskModel.update(taskId, {
334
352
  error: new AsyncTaskError(errorType, errorMessage),