@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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Add upload hint for non-visual model."
6
+ ]
7
+ },
8
+ "date": "2025-09-01",
9
+ "version": "1.120.6"
10
+ },
2
11
  {
3
12
  "children": {},
4
13
  "date": "2025-09-01",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.120.5",
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
 
@@ -278,6 +278,7 @@ export default {
278
278
  actionFiletip: '上传文件',
279
279
  actionTooltip: '上传',
280
280
  disabled: '当前模型不支持视觉识别和文件分析,请切换模型后使用',
281
+ visionNotSupported: '当前模型不支持视觉识别,请切换模型后使用',
281
282
  },
282
283
  preview: {
283
284
  prepareTasks: '准备分块...',