@lobehub/chat 0.107.12 → 0.107.14

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.
@@ -0,0 +1,481 @@
1
+ import { act, renderHook, waitFor } from '@testing-library/react';
2
+ import useSWR, { mutate } from 'swr';
3
+ import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { LOADING_FLAT } from '@/const/message';
6
+ import { chatService } from '@/services/chat';
7
+ import { messageService } from '@/services/message';
8
+ import { topicService } from '@/services/topic';
9
+ import { chatSelectors } from '@/store/chat/selectors';
10
+ import { ChatMessage } from '@/types/chatMessage';
11
+
12
+ import { useChatStore } from '../store';
13
+
14
+ // Mock service
15
+ vi.mock('@/services/message', () => ({
16
+ messageService: {
17
+ getMessages: vi.fn(),
18
+ updateMessageError: vi.fn(),
19
+ removeMessage: vi.fn(),
20
+ createAssistantMessage: vi.fn(() => Promise.resolve('content-content-content')),
21
+ removeMessages: vi.fn(() => Promise.resolve()),
22
+ create: vi.fn(() => Promise.resolve('new-message-id')),
23
+ updateMessageContent: vi.fn(),
24
+ clearAllMessage: vi.fn(() => Promise.resolve()),
25
+ },
26
+ }));
27
+ vi.mock('@/services/topic', () => ({
28
+ topicService: {
29
+ removeTopic: vi.fn(() => Promise.resolve()),
30
+ },
31
+ }));
32
+ vi.mock('@/services/chat', () => ({
33
+ chatService: {
34
+ createAssistantMessage: vi.fn(() => Promise.resolve('assistant-message')),
35
+ },
36
+ }));
37
+
38
+ vi.mock('@/store/chat/selectors', () => ({
39
+ chatSelectors: {
40
+ currentChats: vi.fn(),
41
+ },
42
+ }));
43
+
44
+ const realCoreProcessMessage = useChatStore.getState().coreProcessMessage;
45
+ const realRefreshMessages = useChatStore.getState().refreshMessages;
46
+ // Mock state
47
+ const mockState = {
48
+ activeId: 'session-id',
49
+ activeTopicId: 'topic-id',
50
+ messages: [],
51
+ refreshMessages: vi.fn(),
52
+ refreshTopic: vi.fn(),
53
+ coreProcessMessage: vi.fn(),
54
+ saveToTopic: vi.fn(),
55
+ };
56
+
57
+ beforeEach(() => {
58
+ vi.clearAllMocks();
59
+ useChatStore.setState(mockState, false);
60
+ });
61
+ afterEach(() => {
62
+ vi.restoreAllMocks();
63
+ });
64
+
65
+ describe('chatMessage actions', () => {
66
+ it('clearMessage should remove messages from the active session and topic', async () => {
67
+ const { result } = renderHook(() => useChatStore());
68
+ const clearSpy = vi.spyOn(result.current, 'clearMessage');
69
+ const switchTopicSpy = vi.spyOn(result.current, 'switchTopic');
70
+
71
+ await act(async () => {
72
+ await result.current.clearMessage();
73
+ });
74
+
75
+ expect(clearSpy).toHaveBeenCalled();
76
+ expect(result.current.refreshMessages).toHaveBeenCalled();
77
+ expect(result.current.refreshTopic).toHaveBeenCalled();
78
+ expect(switchTopicSpy).toHaveBeenCalled();
79
+ });
80
+
81
+ it('deleteMessage should remove a message by id', async () => {
82
+ const { result } = renderHook(() => useChatStore());
83
+ const messageId = 'message-id';
84
+ const deleteSpy = vi.spyOn(result.current, 'deleteMessage');
85
+
86
+ await act(async () => {
87
+ await result.current.deleteMessage(messageId);
88
+ });
89
+
90
+ expect(deleteSpy).toHaveBeenCalledWith(messageId);
91
+ expect(result.current.refreshMessages).toHaveBeenCalled();
92
+ });
93
+
94
+ it('clearAllMessages should remove all messages', async () => {
95
+ const { result } = renderHook(() => useChatStore());
96
+ const clearAllSpy = vi.spyOn(result.current, 'clearAllMessages');
97
+
98
+ await act(async () => {
99
+ await result.current.clearAllMessages();
100
+ });
101
+
102
+ expect(clearAllSpy).toHaveBeenCalled();
103
+ expect(result.current.refreshMessages).toHaveBeenCalled();
104
+ });
105
+
106
+ it('updateInputMessage should update the input message state', () => {
107
+ const { result } = renderHook(() => useChatStore());
108
+ const newInputMessage = 'Updated message';
109
+ act(() => {
110
+ result.current.updateInputMessage(newInputMessage);
111
+ });
112
+
113
+ expect(result.current.inputMessage).toEqual(newInputMessage);
114
+ });
115
+
116
+ describe('clearMessage', () => {
117
+ beforeEach(() => {
118
+ vi.clearAllMocks(); // 清除 mocks
119
+ useChatStore.setState(mockState, false); // 重置 state
120
+ });
121
+
122
+ afterEach(() => {
123
+ vi.restoreAllMocks(); // 恢复所有模拟
124
+ });
125
+
126
+ it('should remove messages from the active session and topic, then refresh topics and messages', async () => {
127
+ const { result } = renderHook(() => useChatStore());
128
+ const switchTopicSpy = vi.spyOn(result.current, 'switchTopic');
129
+ const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
130
+
131
+ await act(async () => {
132
+ await result.current.clearMessage();
133
+ });
134
+
135
+ expect(mockState.refreshMessages).toHaveBeenCalled();
136
+ expect(refreshTopicSpy).toHaveBeenCalled();
137
+ expect(switchTopicSpy).toHaveBeenCalled();
138
+
139
+ // 检查 activeTopicId 是否被清除,需要在状态更新后进行检查
140
+ expect(useChatStore.getState().activeTopicId).toBeUndefined();
141
+ });
142
+
143
+ it('should call removeTopic if there is an activeTopicId', async () => {
144
+ const { result } = renderHook(() => useChatStore());
145
+ const switchTopicSpy = vi.spyOn(result.current, 'switchTopic');
146
+ const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
147
+
148
+ await act(async () => {
149
+ await result.current.clearMessage();
150
+ });
151
+
152
+ expect(mockState.activeTopicId).not.toBeUndefined(); // 确保在测试前 activeTopicId 存在
153
+ expect(refreshTopicSpy).toHaveBeenCalled();
154
+ expect(mockState.refreshMessages).toHaveBeenCalled();
155
+ expect(topicService.removeTopic).toHaveBeenCalledWith(mockState.activeTopicId);
156
+ expect(switchTopicSpy).toHaveBeenCalled();
157
+ });
158
+ });
159
+
160
+ describe('sendMessage', () => {
161
+ it('should not send message if there is no active session', async () => {
162
+ useChatStore.setState({ activeId: undefined });
163
+ const { result } = renderHook(() => useChatStore());
164
+ const message = 'Test message';
165
+
166
+ await act(async () => {
167
+ await result.current.sendMessage(message);
168
+ });
169
+
170
+ expect(messageService.create).not.toHaveBeenCalled();
171
+ expect(result.current.refreshMessages).not.toHaveBeenCalled();
172
+ expect(result.current.coreProcessMessage).not.toHaveBeenCalled();
173
+ });
174
+
175
+ it('should not send message if message is empty and there are no files', async () => {
176
+ const { result } = renderHook(() => useChatStore());
177
+ const message = '';
178
+
179
+ await act(async () => {
180
+ await result.current.sendMessage(message);
181
+ });
182
+
183
+ expect(messageService.create).not.toHaveBeenCalled();
184
+ expect(result.current.refreshMessages).not.toHaveBeenCalled();
185
+ expect(result.current.coreProcessMessage).not.toHaveBeenCalled();
186
+ });
187
+
188
+ it('should not send message if message is empty and there are empty files', async () => {
189
+ const { result } = renderHook(() => useChatStore());
190
+ const message = '';
191
+
192
+ await act(async () => {
193
+ await result.current.sendMessage(message, []);
194
+ });
195
+
196
+ expect(messageService.create).not.toHaveBeenCalled();
197
+ expect(result.current.refreshMessages).not.toHaveBeenCalled();
198
+ expect(result.current.coreProcessMessage).not.toHaveBeenCalled();
199
+ });
200
+
201
+ it('should create message and call coreProcessMessage if message or files are provided', async () => {
202
+ const { result } = renderHook(() => useChatStore());
203
+ const message = 'Test message';
204
+ const files = [{ id: 'file-id', url: 'file-url' }];
205
+
206
+ // Mock messageService.create to resolve with a message id
207
+ (messageService.create as Mock).mockResolvedValue('new-message-id');
208
+
209
+ await act(async () => {
210
+ await result.current.sendMessage(message, files);
211
+ });
212
+
213
+ expect(messageService.create).toHaveBeenCalledWith({
214
+ content: message,
215
+ files: files.map((f) => f.id),
216
+ role: 'user',
217
+ sessionId: mockState.activeId,
218
+ topicId: mockState.activeTopicId,
219
+ });
220
+ expect(result.current.refreshMessages).toHaveBeenCalled();
221
+ expect(result.current.coreProcessMessage).toHaveBeenCalled();
222
+ });
223
+
224
+ // it('should auto-create topic and switch to it if enabled and threshold is reached', async () => {
225
+ // const { result } = renderHook(() => useChatStore());
226
+ // const message = 'Test message';
227
+ // const autoCreateTopicThreshold = 5;
228
+ // const enableAutoCreateTopic = true;
229
+ //
230
+ // // Mock state with the necessary settings
231
+ // useChatStore.setState({
232
+ // ...mockState,
233
+ // messages: Array(autoCreateTopicThreshold).fill({}), // Fill with dummy messages to reach threshold
234
+ // });
235
+ //
236
+ // // Mock messageService.create to resolve with a message id
237
+ // (messageService.create as vi.Mock).mockResolvedValue('new-message-id');
238
+ //
239
+ // await act(async () => {
240
+ // await result.current.sendMessage(message);
241
+ // });
242
+ //
243
+ // expect(result.current.saveToTopic).toHaveBeenCalled();
244
+ // expect(result.current.switchTopic).toHaveBeenCalled();
245
+ // });
246
+ // 其他可能的测试用例...
247
+ });
248
+
249
+ describe('resendMessage action', () => {
250
+ it('should resend a message by id and refresh messages', async () => {
251
+ const { result } = renderHook(() => useChatStore());
252
+ const messageId = 'message-id';
253
+
254
+ // Mock the currentChats selector to return a list that includes the message to be resent
255
+ (chatSelectors.currentChats as Mock).mockReturnValue([
256
+ // ... other messages
257
+ { id: messageId, role: 'user', content: 'Resend this message' },
258
+ // ... other messages
259
+ ]);
260
+
261
+ // Mock the coreProcessMessage function to resolve immediately
262
+ mockState.coreProcessMessage.mockResolvedValue(undefined);
263
+
264
+ await act(async () => {
265
+ await result.current.resendMessage(messageId);
266
+ });
267
+
268
+ expect(messageService.removeMessage).not.toHaveBeenCalledWith(messageId);
269
+ expect(mockState.coreProcessMessage).toHaveBeenCalledWith(expect.any(Array), messageId);
270
+ });
271
+
272
+ it('should not perform any action if the message id does not exist', async () => {
273
+ const { result } = renderHook(() => useChatStore());
274
+ const messageId = 'non-existing-message-id';
275
+
276
+ // Mock the currentChats selector to return a list that does not include the message to be resent
277
+ (chatSelectors.currentChats as Mock).mockReturnValue([
278
+ // ... other messages
279
+ ]);
280
+
281
+ await act(async () => {
282
+ await result.current.resendMessage(messageId);
283
+ });
284
+
285
+ expect(messageService.removeMessage).not.toHaveBeenCalledWith(messageId);
286
+ expect(mockState.coreProcessMessage).not.toHaveBeenCalled();
287
+ expect(mockState.refreshMessages).not.toHaveBeenCalled();
288
+ });
289
+ });
290
+
291
+ describe('updateMessageContent action', () => {
292
+ it('should call messageService.updateMessageContent with correct parameters', async () => {
293
+ const { result } = renderHook(() => useChatStore());
294
+ const messageId = 'message-id';
295
+ const newContent = 'Updated content';
296
+
297
+ await act(async () => {
298
+ await result.current.updateMessageContent(messageId, newContent);
299
+ });
300
+
301
+ expect(messageService.updateMessageContent).toHaveBeenCalledWith(messageId, newContent);
302
+ });
303
+
304
+ it('should dispatch message update action', async () => {
305
+ const { result } = renderHook(() => useChatStore());
306
+ const messageId = 'message-id';
307
+ const newContent = 'Updated content';
308
+ const dispatchMessageSpy = vi.spyOn(result.current, 'dispatchMessage');
309
+
310
+ await act(async () => {
311
+ await result.current.updateMessageContent(messageId, newContent);
312
+ });
313
+
314
+ expect(dispatchMessageSpy).toHaveBeenCalledWith({
315
+ id: messageId,
316
+ key: 'content',
317
+ type: 'updateMessage',
318
+ value: newContent,
319
+ });
320
+ });
321
+
322
+ it('should refresh messages after updating content', async () => {
323
+ const { result } = renderHook(() => useChatStore());
324
+ const messageId = 'message-id';
325
+ const newContent = 'Updated content';
326
+
327
+ await act(async () => {
328
+ await result.current.updateMessageContent(messageId, newContent);
329
+ });
330
+
331
+ expect(result.current.refreshMessages).toHaveBeenCalled();
332
+ });
333
+ });
334
+
335
+ describe('coreProcessMessage action', () => {
336
+ it('should handle the core AI message processing', async () => {
337
+ useChatStore.setState({ coreProcessMessage: realCoreProcessMessage });
338
+
339
+ const { result } = renderHook(() => useChatStore());
340
+ const userMessage = {
341
+ id: 'user-message-id',
342
+ role: 'user',
343
+ content: 'Hello, world!',
344
+ sessionId: mockState.activeId,
345
+ topicId: mockState.activeTopicId,
346
+ } as ChatMessage;
347
+ const messages = [userMessage];
348
+
349
+ // 模拟 AI 响应
350
+ const aiResponse = 'Hello, human!';
351
+ (chatService.createAssistantMessage as Mock).mockResolvedValue(aiResponse);
352
+
353
+ // 模拟消息创建
354
+ (messageService.create as Mock).mockResolvedValue('assistant-message-id');
355
+
356
+ await act(async () => {
357
+ await result.current.coreProcessMessage(messages, userMessage.id);
358
+ });
359
+
360
+ // 验证是否创建了代表 AI 响应的消息
361
+ expect(messageService.create).toHaveBeenCalledWith(
362
+ expect.objectContaining({
363
+ role: 'assistant',
364
+ content: LOADING_FLAT,
365
+ fromModel: expect.anything(),
366
+ parentId: userMessage.id,
367
+ sessionId: mockState.activeId,
368
+ topicId: mockState.activeTopicId,
369
+ }),
370
+ );
371
+
372
+ // 验证 AI 服务是否被调用
373
+ expect(chatService.createAssistantMessage).toHaveBeenCalled();
374
+
375
+ // 验证消息列表是否刷新
376
+ expect(mockState.refreshMessages).toHaveBeenCalled();
377
+ });
378
+ });
379
+
380
+ describe('stopGenerateMessage action', () => {
381
+ it('should stop generating message and set loading states correctly', async () => {
382
+ const { result } = renderHook(() => useChatStore());
383
+ const toggleChatLoadingSpy = vi.spyOn(result.current, 'toggleChatLoading');
384
+ const abortController = new AbortController();
385
+
386
+ // 设置模拟 abortController
387
+ useChatStore.setState({ abortController });
388
+
389
+ await act(async () => {
390
+ result.current.stopGenerateMessage();
391
+ });
392
+
393
+ expect(abortController.signal.aborted).toBe(true);
394
+ expect(toggleChatLoadingSpy).toHaveBeenCalledWith(false, undefined, expect.any(String));
395
+ });
396
+
397
+ it('should not do anything if there is no abortController', async () => {
398
+ const { result } = renderHook(() => useChatStore());
399
+
400
+ // 确保没有设置 abortController
401
+ useChatStore.setState({ abortController: undefined });
402
+
403
+ await act(async () => {
404
+ result.current.stopGenerateMessage();
405
+ });
406
+
407
+ // 由于没有 abortController,不应调用任何方法
408
+ expect(result.current.abortController).toBeUndefined();
409
+ });
410
+ });
411
+
412
+ describe('refreshMessages action', () => {
413
+ beforeEach(() => {
414
+ vi.mock('swr', async () => {
415
+ const actual = await vi.importActual('swr');
416
+ return {
417
+ ...(actual as any),
418
+ mutate: vi.fn(),
419
+ };
420
+ });
421
+ });
422
+ afterEach(() => {
423
+ // 在每个测试用例开始前恢复到实际的 SWR 实现
424
+ vi.resetAllMocks();
425
+ });
426
+ it('should refresh messages by calling mutate with current activeId and activeTopicId', async () => {
427
+ useChatStore.setState({ refreshMessages: realRefreshMessages });
428
+
429
+ const { result } = renderHook(() => useChatStore());
430
+ const activeId = useChatStore.getState().activeId;
431
+ const activeTopicId = useChatStore.getState().activeTopicId;
432
+
433
+ // 在这里,我们不需要再次模拟 mutate,因为它已经在顶部被模拟了
434
+ await act(async () => {
435
+ await result.current.refreshMessages();
436
+ });
437
+
438
+ // 确保 mutate 调用了正确的参数
439
+ expect(mutate).toHaveBeenCalledWith([activeId, activeTopicId]);
440
+ });
441
+ it('should handle errors during refreshing messages', async () => {
442
+ useChatStore.setState({ refreshMessages: realRefreshMessages });
443
+ const { result } = renderHook(() => useChatStore());
444
+
445
+ // 设置模拟错误
446
+ (mutate as Mock).mockImplementation(() => {
447
+ throw new Error('Mutate error');
448
+ });
449
+
450
+ await act(async () => {
451
+ await expect(result.current.refreshMessages()).rejects.toThrow('Mutate error');
452
+ });
453
+
454
+ // 确保恢复 mutate 的模拟,以免影响其他测试
455
+ (mutate as Mock).mockReset();
456
+ });
457
+ });
458
+
459
+ describe('useFetchMessages hook', () => {
460
+ // beforeEach(() => {
461
+ // vi.mocked(useSWR).mockRestore();
462
+ // });
463
+
464
+ it('should fetch messages for given session and topic ids', async () => {
465
+ const sessionId = 'session-id';
466
+ const topicId = 'topic-id';
467
+ const messages = [{ id: 'message-id', content: 'Hello' }];
468
+
469
+ // 设置模拟返回值
470
+ (messageService.getMessages as Mock).mockResolvedValue(messages);
471
+
472
+ // renderHook 在 @testing-library/react-hooks 中使用,所以我们使用 render 来替代
473
+ const { result } = renderHook(() => useChatStore().useFetchMessages(sessionId, topicId));
474
+
475
+ // 等待异步操作完成
476
+ await waitFor(() => {
477
+ expect(result.current.data).toEqual(messages);
478
+ });
479
+ });
480
+ });
481
+ });
@@ -146,10 +146,13 @@ export const chatMessage: StateCreator<
146
146
  },
147
147
  sendMessage: async (message, files) => {
148
148
  const { coreProcessMessage, activeTopicId, activeId } = get();
149
- if (!message || !activeId) return;
149
+ if (!activeId) return;
150
150
 
151
151
  const fileIdList = files?.map((f) => f.id);
152
152
 
153
+ // if message is empty and no files, then stop
154
+ if (!message && (!fileIdList || fileIdList?.length === 0)) return;
155
+
153
156
  let newMessage: CreateMessageParams = {
154
157
  content: message,
155
158
  // if message has attached with files, then add files to message and the agent
@@ -1,84 +0,0 @@
1
- import { createStyles } from 'antd-style';
2
-
3
- export const IMAGE_SIZE = 64;
4
- const imageBorderRaidus = 8;
5
-
6
- export const useStyles = createStyles(({ css, cx, token, isDarkMode }) => {
7
- const closeIcon = cx(css`
8
- cursor: pointer;
9
-
10
- position: absolute;
11
- top: -6px;
12
- right: -6px;
13
-
14
- width: 16px;
15
- height: 16px;
16
-
17
- color: ${isDarkMode ? token.colorTextQuaternary : token.colorTextTertiary};
18
-
19
- opacity: 0;
20
- background: ${isDarkMode ? token.colorTextSecondary : token.colorBgContainer};
21
- border-radius: 50%;
22
-
23
- transition: all 0.2s ease-in;
24
-
25
- &:hover {
26
- scale: 1.2;
27
- }
28
- `);
29
-
30
- return {
31
- alwaysShowClose: css`
32
- opacity: 1 !important;
33
- `,
34
- closeIcon,
35
- container: css`
36
- cursor: pointer;
37
-
38
- position: relative;
39
-
40
- width: ${IMAGE_SIZE}px;
41
- height: ${IMAGE_SIZE}px;
42
-
43
- border-radius: ${imageBorderRaidus}px;
44
-
45
- &:hover {
46
- .${closeIcon} {
47
- opacity: 1;
48
- }
49
- }
50
- `,
51
- image: css`
52
- opacity: ${isDarkMode ? 0.6 : 1};
53
- object-fit: cover;
54
- border-radius: 8px;
55
- animation: fade-in 0.3s ease-in;
56
-
57
- @keyframes fade-in {
58
- from {
59
- scale: 1.2;
60
- opacity: 1;
61
- }
62
-
63
- to {
64
- scale: 1;
65
- opacity: 1;
66
- }
67
- }
68
- `,
69
- imageCtn: css`
70
- position: relative;
71
- `,
72
- imageWrapper: css`
73
- overflow: hidden;
74
- width: ${IMAGE_SIZE}px;
75
- height: ${IMAGE_SIZE}px;
76
- border-radius: ${imageBorderRaidus}px;
77
- `,
78
- notFound: css`
79
- color: ${token.colorTextSecondary};
80
- background: ${token.colorFillTertiary};
81
- border-radius: ${imageBorderRaidus}px;
82
- `,
83
- };
84
- });
@@ -1,73 +0,0 @@
1
- import { CloseCircleFilled } from '@ant-design/icons';
2
- import { Icon } from '@lobehub/ui';
3
- import { Skeleton } from 'antd';
4
- import { LucideImageOff } from 'lucide-react';
5
- import Image from 'next/image';
6
- import { memo } from 'react';
7
- import { Center, Flexbox } from 'react-layout-kit';
8
-
9
- import { useFileStore } from '@/store/file';
10
-
11
- import { IMAGE_SIZE, useStyles } from './FileItem.style';
12
-
13
- interface FileItemProps {
14
- alwaysShowClose?: boolean;
15
- editable: boolean;
16
- id: string;
17
- onClick: () => void;
18
- }
19
- const FileItem = memo<FileItemProps>(({ editable, id, onClick, alwaysShowClose }) => {
20
- const { styles, cx } = useStyles();
21
- const [useFetchFile, removeFile] = useFileStore((s) => [s.useFetchFile, s.removeFile]);
22
-
23
- const { data, isLoading } = useFetchFile(id);
24
-
25
- return (
26
- <Flexbox className={styles.container} onClick={onClick}>
27
- {isLoading ? (
28
- <Skeleton
29
- active
30
- title={{
31
- style: { borderRadius: 8, height: IMAGE_SIZE },
32
- width: IMAGE_SIZE,
33
- }}
34
- />
35
- ) : (
36
- <Flexbox className={styles.imageCtn}>
37
- <div className={styles.imageWrapper}>
38
- {data ? (
39
- <Image
40
- alt={data.name || ''}
41
- className={styles.image}
42
- fetchPriority={'high'}
43
- height={IMAGE_SIZE}
44
- loading={'lazy'}
45
- src={data.url}
46
- width={IMAGE_SIZE}
47
- />
48
- ) : (
49
- <Center className={styles.notFound} height={'100%'}>
50
- <Icon icon={LucideImageOff} size={{ fontSize: 28 }} />
51
- </Center>
52
- )}
53
- </div>
54
- </Flexbox>
55
- )}
56
- {/* only show close icon when editable */}
57
- {editable && (
58
- <Center
59
- className={cx(styles.closeIcon, alwaysShowClose && styles.alwaysShowClose)}
60
- onClick={(e) => {
61
- e.stopPropagation();
62
-
63
- removeFile(id);
64
- }}
65
- >
66
- <CloseCircleFilled />
67
- </Center>
68
- )}
69
- </Flexbox>
70
- );
71
- });
72
-
73
- export default FileItem;
@@ -1,46 +0,0 @@
1
- import { createStyles } from 'antd-style';
2
- import { memo } from 'react';
3
- import Lightbox from 'react-spring-lightbox';
4
-
5
- import { filesSelectors, useFileStore } from '@/store/file';
6
-
7
- const useStyles = createStyles(({ css, token }) => ({
8
- wrapper: css`
9
- background: ${token.colorBgMask};
10
- backdrop-filter: blur(4px);
11
- `,
12
- }));
13
-
14
- interface LightBoxProps {
15
- index: number;
16
- items: string[];
17
- onNext: () => void;
18
- onOpenChange: (open: boolean) => void;
19
- onPrev: () => void;
20
- open: boolean;
21
- }
22
-
23
- const LightBox = memo<LightBoxProps>(({ onOpenChange, open, items, index, onNext, onPrev }) => {
24
- const { styles } = useStyles();
25
-
26
- const list = useFileStore(filesSelectors.getImageDetailByList(items));
27
- return (
28
- <Lightbox
29
- className={styles.wrapper}
30
- currentIndex={index}
31
- images={list.map((i) => ({
32
- alt: i.name,
33
- loading: 'lazy',
34
- src: i.url,
35
- }))}
36
- isOpen={open}
37
- onClose={() => {
38
- onOpenChange(false);
39
- }}
40
- onNext={onNext}
41
- onPrev={onPrev}
42
- />
43
- );
44
- });
45
-
46
- export default LightBox;