@lobehub/chat 1.120.5 → 1.120.6
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 1.120.6](https://github.com/lobehub/lobe-chat/compare/v1.120.5...v1.120.6)
|
6
|
+
|
7
|
+
<sup>Released on **2025-09-01**</sup>
|
8
|
+
|
9
|
+
#### 💄 Styles
|
10
|
+
|
11
|
+
- **misc**: Add upload hint for non-visual model.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Styles
|
19
|
+
|
20
|
+
- **misc**: Add upload hint for non-visual model, closes [#7969](https://github.com/lobehub/lobe-chat/issues/7969) ([1224f4e](https://github.com/lobehub/lobe-chat/commit/1224f4e))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
### [Version 1.120.5](https://github.com/lobehub/lobe-chat/compare/v1.120.4...v1.120.5)
|
6
31
|
|
7
32
|
<sup>Released on **2025-09-01**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.120.
|
3
|
+
"version": "1.120.6",
|
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,15 +1,43 @@
|
|
1
1
|
import { act, renderHook } from '@testing-library/react';
|
2
|
+
import { App } from 'antd';
|
2
3
|
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
3
4
|
|
5
|
+
import { useModelSupportVision } from '@/hooks/useModelSupportVision';
|
6
|
+
import { useAgentStore } from '@/store/agent';
|
7
|
+
import { agentSelectors } from '@/store/agent/slices/chat';
|
8
|
+
|
4
9
|
import { getContainer, useDragUpload } from './useDragUpload';
|
5
10
|
|
11
|
+
// Mock the hooks and components
|
12
|
+
vi.mock('@/hooks/useModelSupportVision');
|
13
|
+
vi.mock('@/store/agent');
|
14
|
+
vi.mock('antd', () => ({
|
15
|
+
App: {
|
16
|
+
useApp: () => ({
|
17
|
+
message: {
|
18
|
+
warning: vi.fn(),
|
19
|
+
},
|
20
|
+
}),
|
21
|
+
},
|
22
|
+
}));
|
23
|
+
|
6
24
|
describe('useDragUpload', () => {
|
7
25
|
let mockOnUploadFiles: Mock;
|
26
|
+
let mockMessage: { warning: Mock };
|
8
27
|
|
9
28
|
beforeEach(() => {
|
10
29
|
mockOnUploadFiles = vi.fn();
|
30
|
+
mockMessage = { warning: vi.fn() };
|
11
31
|
vi.useFakeTimers();
|
12
32
|
document.body.innerHTML = '';
|
33
|
+
|
34
|
+
// Mock the hooks
|
35
|
+
(useModelSupportVision as Mock).mockReturnValue(false);
|
36
|
+
(useAgentStore as unknown as Mock).mockImplementation((selector) => {
|
37
|
+
if (selector === agentSelectors.currentAgentModel) return 'test-model';
|
38
|
+
if (selector === agentSelectors.currentAgentModelProvider) return 'test-provider';
|
39
|
+
return null;
|
40
|
+
});
|
13
41
|
});
|
14
42
|
|
15
43
|
afterEach(() => {
|
@@ -115,6 +143,89 @@ describe('useDragUpload', () => {
|
|
115
143
|
|
116
144
|
expect(mockOnUploadFiles).toHaveBeenCalledWith([mockFile]);
|
117
145
|
});
|
146
|
+
|
147
|
+
it('should show warning when dropping image file with vision not supported', async () => {
|
148
|
+
renderHook(() => useDragUpload(mockOnUploadFiles));
|
149
|
+
|
150
|
+
const mockImageFile = new File([''], 'test.png', { type: 'image/png' });
|
151
|
+
const dropEvent = new Event('drop') as DragEvent;
|
152
|
+
Object.defineProperty(dropEvent, 'dataTransfer', {
|
153
|
+
value: {
|
154
|
+
items: [
|
155
|
+
{
|
156
|
+
kind: 'file',
|
157
|
+
getAsFile: () => mockImageFile,
|
158
|
+
webkitGetAsEntry: () => ({
|
159
|
+
isFile: true,
|
160
|
+
file: (cb: (file: File) => void) => cb(mockImageFile),
|
161
|
+
}),
|
162
|
+
},
|
163
|
+
],
|
164
|
+
types: ['Files'],
|
165
|
+
},
|
166
|
+
});
|
167
|
+
|
168
|
+
await act(async () => {
|
169
|
+
window.dispatchEvent(dropEvent);
|
170
|
+
});
|
171
|
+
|
172
|
+
expect(mockOnUploadFiles).not.toHaveBeenCalled();
|
173
|
+
});
|
174
|
+
|
175
|
+
it('should show warning when pasting image file with vision not supported', async () => {
|
176
|
+
renderHook(() => useDragUpload(mockOnUploadFiles));
|
177
|
+
|
178
|
+
const mockImageFile = new File([''], 'test.png', { type: 'image/png' });
|
179
|
+
const pasteEvent = new Event('paste') as ClipboardEvent;
|
180
|
+
Object.defineProperty(pasteEvent, 'clipboardData', {
|
181
|
+
value: {
|
182
|
+
items: [
|
183
|
+
{
|
184
|
+
kind: 'file',
|
185
|
+
getAsFile: () => mockImageFile,
|
186
|
+
webkitGetAsEntry: () => null,
|
187
|
+
},
|
188
|
+
],
|
189
|
+
},
|
190
|
+
});
|
191
|
+
|
192
|
+
await act(async () => {
|
193
|
+
window.dispatchEvent(pasteEvent);
|
194
|
+
});
|
195
|
+
|
196
|
+
expect(mockOnUploadFiles).not.toHaveBeenCalled();
|
197
|
+
});
|
198
|
+
|
199
|
+
it('should allow image files when vision is supported', async () => {
|
200
|
+
(useModelSupportVision as Mock).mockReturnValue(true);
|
201
|
+
|
202
|
+
renderHook(() => useDragUpload(mockOnUploadFiles));
|
203
|
+
|
204
|
+
const mockImageFile = new File([''], 'test.png', { type: 'image/png' });
|
205
|
+
const dropEvent = new Event('drop') as DragEvent;
|
206
|
+
Object.defineProperty(dropEvent, 'dataTransfer', {
|
207
|
+
value: {
|
208
|
+
items: [
|
209
|
+
{
|
210
|
+
kind: 'file',
|
211
|
+
getAsFile: () => mockImageFile,
|
212
|
+
webkitGetAsEntry: () => ({
|
213
|
+
isFile: true,
|
214
|
+
file: (cb: (file: File) => void) => cb(mockImageFile),
|
215
|
+
}),
|
216
|
+
},
|
217
|
+
],
|
218
|
+
types: ['Files'],
|
219
|
+
},
|
220
|
+
});
|
221
|
+
|
222
|
+
await act(async () => {
|
223
|
+
window.dispatchEvent(dropEvent);
|
224
|
+
});
|
225
|
+
|
226
|
+
expect(mockOnUploadFiles).toHaveBeenCalledWith([mockImageFile]);
|
227
|
+
expect(App.useApp().message.warning).not.toHaveBeenCalled();
|
228
|
+
});
|
118
229
|
});
|
119
230
|
|
120
231
|
describe('getContainer', () => {
|
@@ -1,5 +1,11 @@
|
|
1
1
|
/* eslint-disable no-undef */
|
2
|
+
import { App } from 'antd';
|
2
3
|
import { useEffect, useRef, useState } from 'react';
|
4
|
+
import { useTranslation } from 'react-i18next';
|
5
|
+
|
6
|
+
import { useModelSupportVision } from '@/hooks/useModelSupportVision';
|
7
|
+
import { useAgentStore } from '@/store/agent';
|
8
|
+
import { agentSelectors } from '@/store/agent/selectors';
|
3
9
|
|
4
10
|
const DRAGGING_ROOT_ID = 'dragging-root';
|
5
11
|
export const getContainer = () => document.querySelector(`#${DRAGGING_ROOT_ID}`);
|
@@ -62,12 +68,18 @@ const getFileListFromDataTransferItems = async (items: DataTransferItem[]) => {
|
|
62
68
|
};
|
63
69
|
|
64
70
|
export const useDragUpload = (onUploadFiles: (files: File[]) => Promise<void>) => {
|
71
|
+
const { t } = useTranslation('chat');
|
72
|
+
const { message } = App.useApp();
|
65
73
|
const [isDragging, setIsDragging] = useState(false);
|
66
74
|
// When a file is dragged to a different area, the 'dragleave' event may be triggered,
|
67
75
|
// causing isDragging to be mistakenly set to false.
|
68
76
|
// to fix this issue, use a counter to ensure the status change only when drag event left the browser window .
|
69
77
|
const dragCounter = useRef(0);
|
70
78
|
|
79
|
+
const model = useAgentStore(agentSelectors.currentAgentModel);
|
80
|
+
const provider = useAgentStore(agentSelectors.currentAgentModelProvider);
|
81
|
+
const supportVision = useModelSupportVision(model, provider);
|
82
|
+
|
71
83
|
const handleDragEnter = (e: DragEvent) => {
|
72
84
|
if (!e.dataTransfer?.items || e.dataTransfer.items.length === 0) return;
|
73
85
|
|
@@ -113,6 +125,13 @@ export const useDragUpload = (onUploadFiles: (files: File[]) => Promise<void>) =
|
|
113
125
|
|
114
126
|
if (files.length === 0) return;
|
115
127
|
|
128
|
+
// 检查是否有图片文件且模型不支持视觉功能
|
129
|
+
const hasImageFiles = files.some((file) => file.type.startsWith('image/'));
|
130
|
+
if (hasImageFiles && !supportVision) {
|
131
|
+
message.warning(t('upload.clientMode.visionNotSupported'));
|
132
|
+
return;
|
133
|
+
}
|
134
|
+
|
116
135
|
// upload files
|
117
136
|
onUploadFiles(files);
|
118
137
|
};
|
@@ -125,6 +144,13 @@ export const useDragUpload = (onUploadFiles: (files: File[]) => Promise<void>) =
|
|
125
144
|
const files = await getFileListFromDataTransferItems(items);
|
126
145
|
if (files.length === 0) return;
|
127
146
|
|
147
|
+
// 检查是否有图片文件且模型不支持视觉功能
|
148
|
+
const hasImageFiles = files.some((file) => file.type.startsWith('image/'));
|
149
|
+
if (hasImageFiles && !supportVision) {
|
150
|
+
message.warning(t('upload.clientMode.visionNotSupported'));
|
151
|
+
return;
|
152
|
+
}
|
153
|
+
|
128
154
|
onUploadFiles(files);
|
129
155
|
};
|
130
156
|
|