@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 +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/model-runtime/src/providers/google/createImage.test.ts +6 -5
- package/packages/model-runtime/src/providers/google/createImage.ts +12 -8
- package/packages/model-runtime/src/types/error.ts +11 -8
- package/packages/model-runtime/src/utils/googleErrorParser.ts +5 -0
- package/src/server/routers/async/image.ts +20 -2
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
|
+
[](#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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
83
|
-
|
|
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 =
|
|
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(
|
|
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),
|