@lobehub/chat 1.137.4 → 1.137.5
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/context-engine/src/processors/MessageContent.ts +4 -2
- package/packages/context-engine/src/processors/__tests__/MessageContent.test.ts +11 -14
- package/packages/model-bank/src/aiModels/google.ts +7 -7
- package/packages/model-bank/src/aiModels/vertexai.ts +63 -2
- package/packages/utils/package.json +2 -1
- package/packages/utils/src/url.test.ts +19 -19
- package/packages/utils/src/url.ts +7 -11
- package/src/services/chat/chat.test.ts +38 -17
- package/src/services/chat/clientModelRuntime.test.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 1.137.5](https://github.com/lobehub/lobe-chat/compare/v1.137.4...v1.137.5)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-10-14**</sup>
|
|
8
|
+
|
|
9
|
+
#### 💄 Styles
|
|
10
|
+
|
|
11
|
+
- **misc**: Add imagen model to vertex ai.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Styles
|
|
19
|
+
|
|
20
|
+
- **misc**: Add imagen model to vertex ai, closes [#9699](https://github.com/lobehub/lobe-chat/issues/9699) ([3b2a2c1](https://github.com/lobehub/lobe-chat/commit/3b2a2c1))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
### [Version 1.137.4](https://github.com/lobehub/lobe-chat/compare/v1.137.3...v1.137.4)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2025-10-14**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "1.137.
|
|
3
|
+
"version": "1.137.5",
|
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot 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",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { filesPrompts } from '@lobechat/prompts';
|
|
2
|
-
import { imageUrlToBase64
|
|
2
|
+
import { imageUrlToBase64 } from '@lobechat/utils/imageToBase64';
|
|
3
|
+
import { parseDataUri } from '@lobechat/utils/uriParser';
|
|
4
|
+
import { isDesktopLocalStaticServerUrl } from '@lobechat/utils/url';
|
|
3
5
|
import debug from 'debug';
|
|
4
6
|
|
|
5
7
|
import { BaseProcessor } from '../base/BaseProcessor';
|
|
@@ -277,7 +279,7 @@ export class MessageContentProcessor extends BaseProcessor {
|
|
|
277
279
|
const { type } = parseDataUri(image.url);
|
|
278
280
|
|
|
279
281
|
let processedUrl = image.url;
|
|
280
|
-
if (type === 'url' &&
|
|
282
|
+
if (type === 'url' && isDesktopLocalStaticServerUrl(image.url)) {
|
|
281
283
|
const { base64, mimeType } = await imageUrlToBase64(image.url);
|
|
282
284
|
processedUrl = `data:${mimeType};base64,${base64}`;
|
|
283
285
|
}
|
|
@@ -4,19 +4,16 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
4
4
|
import type { PipelineContext } from '../../types';
|
|
5
5
|
import { MessageContentProcessor } from '../MessageContent';
|
|
6
6
|
|
|
7
|
-
vi.mock('@lobechat/utils', () =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return { type: 'url' };
|
|
18
|
-
}),
|
|
19
|
-
}));
|
|
7
|
+
vi.mock('@lobechat/utils/imageToBase64', async (importOriginal) => {
|
|
8
|
+
const actual = await importOriginal<typeof import('@lobechat/utils/imageToBase64')>();
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
imageUrlToBase64: vi.fn().mockResolvedValue({
|
|
12
|
+
base64: 'base64-data',
|
|
13
|
+
mimeType: 'image/png',
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
20
17
|
|
|
21
18
|
const createContext = (messages: ChatMessage[]): PipelineContext => ({
|
|
22
19
|
initialState: { messages: [] } as any,
|
|
@@ -138,7 +135,7 @@ describe('MessageContentProcessor', () => {
|
|
|
138
135
|
role: 'user',
|
|
139
136
|
content: 'Hello',
|
|
140
137
|
imageList: [
|
|
141
|
-
{ url: 'http://
|
|
138
|
+
{ url: 'http://127.0.0.1:3000/image.jpg', alt: '', id: 'test' } as ChatImageItem,
|
|
142
139
|
],
|
|
143
140
|
createdAt: Date.now(),
|
|
144
141
|
updatedAt: Date.now(),
|
|
@@ -820,7 +820,7 @@ const googleChatModels: AIChatModelCard[] = [
|
|
|
820
820
|
];
|
|
821
821
|
|
|
822
822
|
// Common parameters for Imagen models
|
|
823
|
-
const
|
|
823
|
+
export const imagenGenParameters: ModelParamsSchema = {
|
|
824
824
|
aspectRatio: {
|
|
825
825
|
default: '1:1',
|
|
826
826
|
enum: ['1:1', '16:9', '9:16', '3:4', '4:3'],
|
|
@@ -841,7 +841,7 @@ const NANO_BANANA_ASPECT_RATIOS = [
|
|
|
841
841
|
'21:9', // 1536x672
|
|
842
842
|
];
|
|
843
843
|
|
|
844
|
-
const nanoBananaParameters: ModelParamsSchema = {
|
|
844
|
+
export const nanoBananaParameters: ModelParamsSchema = {
|
|
845
845
|
aspectRatio: {
|
|
846
846
|
default: '1:1',
|
|
847
847
|
enum: NANO_BANANA_ASPECT_RATIOS,
|
|
@@ -895,7 +895,7 @@ const googleImageModels: AIImageModelCard[] = [
|
|
|
895
895
|
description: 'Imagen 4th generation text-to-image model series',
|
|
896
896
|
organization: 'Deepmind',
|
|
897
897
|
releasedAt: '2025-08-15',
|
|
898
|
-
parameters:
|
|
898
|
+
parameters: imagenGenParameters,
|
|
899
899
|
pricing: {
|
|
900
900
|
units: [{ name: 'imageGeneration', rate: 0.04, strategy: 'fixed', unit: 'image' }],
|
|
901
901
|
},
|
|
@@ -908,7 +908,7 @@ const googleImageModels: AIImageModelCard[] = [
|
|
|
908
908
|
description: 'Imagen 4th generation text-to-image model series Ultra version',
|
|
909
909
|
organization: 'Deepmind',
|
|
910
910
|
releasedAt: '2025-08-15',
|
|
911
|
-
parameters:
|
|
911
|
+
parameters: imagenGenParameters,
|
|
912
912
|
pricing: {
|
|
913
913
|
units: [{ name: 'imageGeneration', rate: 0.06, strategy: 'fixed', unit: 'image' }],
|
|
914
914
|
},
|
|
@@ -921,7 +921,7 @@ const googleImageModels: AIImageModelCard[] = [
|
|
|
921
921
|
description: 'Imagen 4th generation text-to-image model series Fast version',
|
|
922
922
|
organization: 'Deepmind',
|
|
923
923
|
releasedAt: '2025-08-15',
|
|
924
|
-
parameters:
|
|
924
|
+
parameters: imagenGenParameters,
|
|
925
925
|
pricing: {
|
|
926
926
|
units: [{ name: 'imageGeneration', rate: 0.02, strategy: 'fixed', unit: 'image' }],
|
|
927
927
|
},
|
|
@@ -933,7 +933,7 @@ const googleImageModels: AIImageModelCard[] = [
|
|
|
933
933
|
description: 'Imagen 4th generation text-to-image model series',
|
|
934
934
|
organization: 'Deepmind',
|
|
935
935
|
releasedAt: '2024-06-06',
|
|
936
|
-
parameters:
|
|
936
|
+
parameters: imagenGenParameters,
|
|
937
937
|
pricing: {
|
|
938
938
|
units: [{ name: 'imageGeneration', rate: 0.04, strategy: 'fixed', unit: 'image' }],
|
|
939
939
|
},
|
|
@@ -945,7 +945,7 @@ const googleImageModels: AIImageModelCard[] = [
|
|
|
945
945
|
description: 'Imagen 4th generation text-to-image model series Ultra version',
|
|
946
946
|
organization: 'Deepmind',
|
|
947
947
|
releasedAt: '2025-06-11',
|
|
948
|
-
parameters:
|
|
948
|
+
parameters: imagenGenParameters,
|
|
949
949
|
pricing: {
|
|
950
950
|
units: [{ name: 'imageGeneration', rate: 0.06, strategy: 'fixed', unit: 'image' }],
|
|
951
951
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AIChatModelCard } from '../types/aiModel';
|
|
1
|
+
import { AIChatModelCard, AIImageModelCard } from '../types/aiModel';
|
|
2
|
+
import { imagenGenParameters, nanoBananaParameters } from './google';
|
|
2
3
|
|
|
3
4
|
// ref: https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models
|
|
4
5
|
const vertexaiChatModels: AIChatModelCard[] = [
|
|
@@ -278,6 +279,66 @@ const vertexaiChatModels: AIChatModelCard[] = [
|
|
|
278
279
|
},
|
|
279
280
|
];
|
|
280
281
|
|
|
281
|
-
|
|
282
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
283
|
+
const vertexaiImageModels: AIImageModelCard[] = [
|
|
284
|
+
{
|
|
285
|
+
displayName: 'Nano Banana',
|
|
286
|
+
id: 'gemini-2.5-flash-image:image',
|
|
287
|
+
enabled: true,
|
|
288
|
+
type: 'image',
|
|
289
|
+
description:
|
|
290
|
+
'Nano Banana 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
|
|
291
|
+
releasedAt: '2025-08-26',
|
|
292
|
+
parameters: nanoBananaParameters,
|
|
293
|
+
pricing: {
|
|
294
|
+
units: [
|
|
295
|
+
{ name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
|
|
296
|
+
{ name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
|
|
297
|
+
{ name: 'imageOutput', rate: 30, strategy: 'fixed', unit: 'millionTokens' },
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
displayName: 'Imagen 4',
|
|
303
|
+
id: 'imagen-4.0-generate-001',
|
|
304
|
+
enabled: true,
|
|
305
|
+
type: 'image',
|
|
306
|
+
description: 'Imagen 4th generation text-to-image model series',
|
|
307
|
+
organization: 'Deepmind',
|
|
308
|
+
releasedAt: '2025-08-15',
|
|
309
|
+
parameters: imagenGenParameters,
|
|
310
|
+
pricing: {
|
|
311
|
+
units: [{ name: 'imageGeneration', rate: 0.04, strategy: 'fixed', unit: 'image' }],
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
displayName: 'Imagen 4 Ultra',
|
|
316
|
+
id: 'imagen-4.0-ultra-generate-001',
|
|
317
|
+
enabled: true,
|
|
318
|
+
type: 'image',
|
|
319
|
+
description: 'Imagen 4th generation text-to-image model series Ultra version',
|
|
320
|
+
organization: 'Deepmind',
|
|
321
|
+
releasedAt: '2025-08-15',
|
|
322
|
+
parameters: imagenGenParameters,
|
|
323
|
+
pricing: {
|
|
324
|
+
units: [{ name: 'imageGeneration', rate: 0.06, strategy: 'fixed', unit: 'image' }],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
displayName: 'Imagen 4 Fast',
|
|
329
|
+
id: 'imagen-4.0-fast-generate-001',
|
|
330
|
+
enabled: true,
|
|
331
|
+
type: 'image',
|
|
332
|
+
description: 'Imagen 4th generation text-to-image model series Fast version',
|
|
333
|
+
organization: 'Deepmind',
|
|
334
|
+
releasedAt: '2025-08-15',
|
|
335
|
+
parameters: imagenGenParameters,
|
|
336
|
+
pricing: {
|
|
337
|
+
units: [{ name: 'imageGeneration', rate: 0.02, strategy: 'fixed', unit: 'image' }],
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
export const allModels = [...vertexaiChatModels, ...vertexaiImageModels];
|
|
282
343
|
|
|
283
344
|
export default allModels;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { pathString } from './url';
|
|
4
3
|
import {
|
|
5
4
|
inferContentTypeFromImageUrl,
|
|
6
5
|
inferFileExtensionFromImageUrl,
|
|
6
|
+
isDesktopLocalStaticServerUrl,
|
|
7
7
|
isLocalOrPrivateUrl,
|
|
8
|
-
|
|
8
|
+
pathString,
|
|
9
9
|
} from './url';
|
|
10
10
|
|
|
11
11
|
describe('pathString', () => {
|
|
@@ -404,36 +404,36 @@ describe('inferFileExtensionFromImageUrl', () => {
|
|
|
404
404
|
});
|
|
405
405
|
});
|
|
406
406
|
|
|
407
|
-
describe('
|
|
407
|
+
describe('isDesktopLocalStaticServerUrl', () => {
|
|
408
408
|
it('should return true for 127.0.0.1', () => {
|
|
409
|
-
expect(
|
|
410
|
-
expect(
|
|
411
|
-
expect(
|
|
412
|
-
expect(
|
|
413
|
-
expect(
|
|
409
|
+
expect(isDesktopLocalStaticServerUrl('http://127.0.0.1')).toBe(true);
|
|
410
|
+
expect(isDesktopLocalStaticServerUrl('https://127.0.0.1')).toBe(true);
|
|
411
|
+
expect(isDesktopLocalStaticServerUrl('http://127.0.0.1:8080')).toBe(true);
|
|
412
|
+
expect(isDesktopLocalStaticServerUrl('http://127.0.0.1/path/to/resource')).toBe(true);
|
|
413
|
+
expect(isDesktopLocalStaticServerUrl('https://127.0.0.1/path?query=1#hash')).toBe(true);
|
|
414
414
|
});
|
|
415
415
|
|
|
416
416
|
it('should return false for other 127.x.x.x addresses', () => {
|
|
417
|
-
expect(
|
|
418
|
-
expect(
|
|
419
|
-
expect(
|
|
417
|
+
expect(isDesktopLocalStaticServerUrl('http://127.0.0.2')).toBe(false);
|
|
418
|
+
expect(isDesktopLocalStaticServerUrl('http://127.1.1.1')).toBe(false);
|
|
419
|
+
expect(isDesktopLocalStaticServerUrl('http://127.255.255.255')).toBe(false);
|
|
420
420
|
});
|
|
421
421
|
|
|
422
422
|
it('should return false for localhost', () => {
|
|
423
|
-
expect(
|
|
424
|
-
expect(
|
|
425
|
-
expect(
|
|
423
|
+
expect(isDesktopLocalStaticServerUrl('http://localhost')).toBe(false);
|
|
424
|
+
expect(isDesktopLocalStaticServerUrl('http://localhost:3000')).toBe(false);
|
|
425
|
+
expect(isDesktopLocalStaticServerUrl('https://localhost/api')).toBe(false);
|
|
426
426
|
});
|
|
427
427
|
|
|
428
428
|
it('should return false for domain names', () => {
|
|
429
|
-
expect(
|
|
430
|
-
expect(
|
|
429
|
+
expect(isDesktopLocalStaticServerUrl('https://example.com')).toBe(false);
|
|
430
|
+
expect(isDesktopLocalStaticServerUrl('http://www.google.com')).toBe(false);
|
|
431
431
|
});
|
|
432
432
|
|
|
433
433
|
it('should return false for malformed URLs', () => {
|
|
434
|
-
expect(
|
|
435
|
-
expect(
|
|
436
|
-
expect(
|
|
434
|
+
expect(isDesktopLocalStaticServerUrl('invalid-url')).toBe(false);
|
|
435
|
+
expect(isDesktopLocalStaticServerUrl('http://')).toBe(false);
|
|
436
|
+
expect(isDesktopLocalStaticServerUrl('')).toBe(false);
|
|
437
437
|
});
|
|
438
438
|
});
|
|
439
439
|
|
|
@@ -125,25 +125,21 @@ export function inferContentTypeFromImageUrl(url: string) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Check if a URL points to localhost (127.0.0.1)
|
|
129
128
|
*
|
|
130
|
-
*
|
|
131
|
-
* It handles malformed URLs gracefully by returning false instead of throwing errors.
|
|
132
|
-
*
|
|
133
|
-
* @param url - The URL string to check
|
|
134
|
-
* @returns true if the URL's hostname is '127.0.0.1', false otherwise (including for malformed URLs)
|
|
129
|
+
* Check if a URL points to desktop local static server
|
|
135
130
|
*
|
|
136
131
|
* @example
|
|
137
132
|
* ```typescript
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
133
|
+
* isDesktopLocalStaticServerUrl('http://127.0.0.1:8080/path') // true
|
|
134
|
+
* isDesktopLocalStaticServerUrl('http://localhost:8080/path') // false
|
|
135
|
+
* isDesktopLocalStaticServerUrl('https://example.com') // false
|
|
136
|
+
* isDesktopLocalStaticServerUrl('invalid-url') // false (instead of throwing)
|
|
137
|
+
* isDesktopLocalStaticServerUrl('') // false (instead of throwing)
|
|
142
138
|
* ```
|
|
143
139
|
*
|
|
144
140
|
* check: apps/desktop/src/main/core/StaticFileServerManager.ts
|
|
145
141
|
*/
|
|
146
|
-
export function
|
|
142
|
+
export function isDesktopLocalStaticServerUrl(url: string) {
|
|
147
143
|
try {
|
|
148
144
|
return new URL(url).hostname === '127.0.0.1';
|
|
149
145
|
} catch {
|
|
@@ -37,9 +37,13 @@ vi.mock('@/utils/fetch', async (importOriginal) => {
|
|
|
37
37
|
|
|
38
38
|
return { ...(module as any), getMessageError: vi.fn() };
|
|
39
39
|
});
|
|
40
|
-
vi.mock('@lobechat/utils', () => ({
|
|
41
|
-
|
|
40
|
+
vi.mock('@lobechat/utils/url', () => ({
|
|
41
|
+
isDesktopLocalStaticServerUrl: vi.fn(),
|
|
42
|
+
}));
|
|
43
|
+
vi.mock('@lobechat/utils/imageToBase64', () => ({
|
|
42
44
|
imageUrlToBase64: vi.fn(),
|
|
45
|
+
}));
|
|
46
|
+
vi.mock('@lobechat/utils/uriParser', () => ({
|
|
43
47
|
parseDataUri: vi.fn(),
|
|
44
48
|
}));
|
|
45
49
|
|
|
@@ -280,9 +284,10 @@ describe('ChatService', () => {
|
|
|
280
284
|
describe('should handle content correctly for vision models', () => {
|
|
281
285
|
it('should include image content when with vision model', async () => {
|
|
282
286
|
// Mock utility functions used in processImageList
|
|
283
|
-
const { parseDataUri
|
|
287
|
+
const { parseDataUri } = await import('@lobechat/utils/uriParser');
|
|
288
|
+
const { isDesktopLocalStaticServerUrl } = await import('@lobechat/utils/url');
|
|
284
289
|
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
285
|
-
vi.mocked(
|
|
290
|
+
vi.mocked(isDesktopLocalStaticServerUrl).mockReturnValue(false); // Not a local URL
|
|
286
291
|
|
|
287
292
|
const messages = [
|
|
288
293
|
{
|
|
@@ -357,11 +362,13 @@ describe('ChatService', () => {
|
|
|
357
362
|
|
|
358
363
|
describe('local image URL conversion', () => {
|
|
359
364
|
it('should convert local image URLs to base64 and call processImageList', async () => {
|
|
360
|
-
const { imageUrlToBase64
|
|
365
|
+
const { imageUrlToBase64 } = await import('@lobechat/utils/imageToBase64');
|
|
366
|
+
const { parseDataUri } = await import('@lobechat/utils/uriParser');
|
|
367
|
+
const { isDesktopLocalStaticServerUrl } = await import('@lobechat/utils/url');
|
|
361
368
|
|
|
362
369
|
// Mock for local URL
|
|
363
370
|
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
364
|
-
vi.mocked(
|
|
371
|
+
vi.mocked(isDesktopLocalStaticServerUrl).mockReturnValue(true); // This is a local URL
|
|
365
372
|
vi.mocked(imageUrlToBase64).mockResolvedValue({
|
|
366
373
|
base64: 'converted-base64-content',
|
|
367
374
|
mimeType: 'image/png',
|
|
@@ -397,7 +404,9 @@ describe('ChatService', () => {
|
|
|
397
404
|
|
|
398
405
|
// Verify the utility functions were called
|
|
399
406
|
expect(parseDataUri).toHaveBeenCalledWith('http://127.0.0.1:3000/uploads/image.png');
|
|
400
|
-
expect(
|
|
407
|
+
expect(isDesktopLocalStaticServerUrl).toHaveBeenCalledWith(
|
|
408
|
+
'http://127.0.0.1:3000/uploads/image.png',
|
|
409
|
+
);
|
|
401
410
|
expect(imageUrlToBase64).toHaveBeenCalledWith('http://127.0.0.1:3000/uploads/image.png');
|
|
402
411
|
|
|
403
412
|
// Verify the final result contains base64 converted URL
|
|
@@ -428,11 +437,13 @@ describe('ChatService', () => {
|
|
|
428
437
|
});
|
|
429
438
|
|
|
430
439
|
it('should not convert remote URLs to base64 and call processImageList', async () => {
|
|
431
|
-
const { imageUrlToBase64
|
|
440
|
+
const { imageUrlToBase64 } = await import('@lobechat/utils/imageToBase64');
|
|
441
|
+
const { parseDataUri } = await import('@lobechat/utils/uriParser');
|
|
442
|
+
const { isDesktopLocalStaticServerUrl } = await import('@lobechat/utils/url');
|
|
432
443
|
|
|
433
444
|
// Mock for remote URL
|
|
434
445
|
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
435
|
-
vi.mocked(
|
|
446
|
+
vi.mocked(isDesktopLocalStaticServerUrl).mockReturnValue(false); // This is NOT a local URL
|
|
436
447
|
vi.mocked(imageUrlToBase64).mockClear(); // Clear to ensure it's not called
|
|
437
448
|
|
|
438
449
|
const messages = [
|
|
@@ -464,7 +475,9 @@ describe('ChatService', () => {
|
|
|
464
475
|
|
|
465
476
|
// Verify the utility functions were called
|
|
466
477
|
expect(parseDataUri).toHaveBeenCalledWith('https://example.com/remote-image.jpg');
|
|
467
|
-
expect(
|
|
478
|
+
expect(isDesktopLocalStaticServerUrl).toHaveBeenCalledWith(
|
|
479
|
+
'https://example.com/remote-image.jpg',
|
|
480
|
+
);
|
|
468
481
|
expect(imageUrlToBase64).not.toHaveBeenCalled(); // Should NOT be called for remote URLs
|
|
469
482
|
|
|
470
483
|
// Verify the final result preserves original URL
|
|
@@ -492,13 +505,15 @@ describe('ChatService', () => {
|
|
|
492
505
|
});
|
|
493
506
|
|
|
494
507
|
it('should handle mixed local and remote URLs correctly', async () => {
|
|
495
|
-
const { imageUrlToBase64
|
|
508
|
+
const { imageUrlToBase64 } = await import('@lobechat/utils/imageToBase64');
|
|
509
|
+
const { parseDataUri } = await import('@lobechat/utils/uriParser');
|
|
510
|
+
const { isDesktopLocalStaticServerUrl } = await import('@lobechat/utils/url');
|
|
496
511
|
|
|
497
512
|
// Mock parseDataUri to always return url type
|
|
498
513
|
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
499
514
|
|
|
500
|
-
// Mock
|
|
501
|
-
vi.mocked(
|
|
515
|
+
// Mock isDesktopLocalStaticServerUrl to return true only for 127.0.0.1 URLs
|
|
516
|
+
vi.mocked(isDesktopLocalStaticServerUrl).mockImplementation((url: string) => {
|
|
502
517
|
return new URL(url).hostname === '127.0.0.1';
|
|
503
518
|
});
|
|
504
519
|
|
|
@@ -544,10 +559,16 @@ describe('ChatService', () => {
|
|
|
544
559
|
model: 'gpt-4-vision-preview',
|
|
545
560
|
});
|
|
546
561
|
|
|
547
|
-
// Verify
|
|
548
|
-
expect(
|
|
549
|
-
|
|
550
|
-
|
|
562
|
+
// Verify isDesktopLocalStaticServerUrl was called for each image
|
|
563
|
+
expect(isDesktopLocalStaticServerUrl).toHaveBeenCalledWith(
|
|
564
|
+
'http://127.0.0.1:3000/local1.jpg',
|
|
565
|
+
);
|
|
566
|
+
expect(isDesktopLocalStaticServerUrl).toHaveBeenCalledWith(
|
|
567
|
+
'https://example.com/remote1.png',
|
|
568
|
+
);
|
|
569
|
+
expect(isDesktopLocalStaticServerUrl).toHaveBeenCalledWith(
|
|
570
|
+
'http://127.0.0.1:8080/local2.gif',
|
|
571
|
+
);
|
|
551
572
|
|
|
552
573
|
// Verify imageUrlToBase64 was called only for local URLs
|
|
553
574
|
expect(imageUrlToBase64).toHaveBeenCalledWith('http://127.0.0.1:3000/local1.jpg');
|
|
@@ -46,7 +46,7 @@ vi.mock('@/utils/fetch', async (importOriginal) => {
|
|
|
46
46
|
|
|
47
47
|
// Mock image processing utilities
|
|
48
48
|
vi.mock('@/utils/url', () => ({
|
|
49
|
-
|
|
49
|
+
isDesktopLocalStaticServerUrl: vi.fn(),
|
|
50
50
|
}));
|
|
51
51
|
|
|
52
52
|
vi.mock('@/utils/imageToBase64', () => ({
|
|
@@ -81,12 +81,12 @@ beforeEach(async () => {
|
|
|
81
81
|
vi.clearAllMocks();
|
|
82
82
|
|
|
83
83
|
// Set default mock return values for image processing utilities
|
|
84
|
-
const {
|
|
84
|
+
const { isDesktopLocalStaticServerUrl } = await import('@/utils/url');
|
|
85
85
|
const { imageUrlToBase64 } = await import('@/utils/imageToBase64');
|
|
86
86
|
const { parseDataUri } = await import('@lobechat/model-runtime');
|
|
87
87
|
|
|
88
88
|
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
89
|
-
vi.mocked(
|
|
89
|
+
vi.mocked(isDesktopLocalStaticServerUrl).mockReturnValue(false);
|
|
90
90
|
vi.mocked(imageUrlToBase64).mockResolvedValue({
|
|
91
91
|
base64: 'mock-base64',
|
|
92
92
|
mimeType: 'image/jpeg',
|