@lobehub/chat 0.109.0 → 0.110.0
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 +50 -0
- package/README.md +23 -3
- package/README.zh-CN.md +23 -3
- package/package.json +6 -6
- package/src/app/chat/features/Conversation/ChatList/Extras/TTS/FilePlayer.tsx +25 -0
- package/src/app/chat/features/Conversation/ChatList/Extras/TTS/InitPlayer.tsx +93 -0
- package/src/app/chat/features/Conversation/ChatList/Extras/TTS/Player.tsx +61 -0
- package/src/app/chat/features/Conversation/ChatList/Extras/TTS/index.tsx +31 -0
- package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
- package/src/hooks/useTTS.ts +11 -15
- package/src/store/chat/actions/enhance.test.ts +139 -0
- package/src/store/chat/actions/enhance.ts +6 -3
- package/src/store/file/selectors.ts +4 -31
- package/src/store/file/slices/images/index.ts +1 -1
- package/src/store/file/slices/images/selectors.ts +34 -0
- package/src/store/file/slices/tts/action.test.ts +147 -0
- package/src/store/file/slices/tts/action.ts +60 -0
- package/src/store/file/slices/tts/index.ts +2 -0
- package/src/store/file/slices/tts/selectors.ts +3 -0
- package/src/store/file/store.ts +3 -1
- package/src/store/session/slices/agent/selectors.test.ts +228 -0
- package/src/store/session/slices/agent/selectors.ts +25 -0
- package/src/store/tool/helpers.ts +1 -1
- package/src/types/chatMessage.ts +3 -1
- package/vitest.config.ts +4 -0
- package/src/app/chat/features/Conversation/ChatList/Extras/TTS.tsx +0 -122
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
2
|
+
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { chainLangDetect } from '@/chains/langDetect';
|
|
5
|
+
import { chainTranslate } from '@/chains/translate';
|
|
6
|
+
import { chatService } from '@/services/chat';
|
|
7
|
+
import { messageService } from '@/services/message';
|
|
8
|
+
|
|
9
|
+
import { useChatStore } from '../store';
|
|
10
|
+
|
|
11
|
+
// Mock messageService 和 chatService
|
|
12
|
+
vi.mock('@/services/message', () => ({
|
|
13
|
+
messageService: {
|
|
14
|
+
updateMessageTTS: vi.fn(),
|
|
15
|
+
updateMessageTranslate: vi.fn(),
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock('@/services/chat', () => ({
|
|
20
|
+
chatService: {
|
|
21
|
+
fetchPresetTaskResult: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('@/chains/langDetect', () => ({
|
|
26
|
+
chainLangDetect: vi.fn(),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('@/chains/translate', () => ({
|
|
30
|
+
chainTranslate: vi.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Mock supportLocales
|
|
34
|
+
vi.mock('@/locales/options', () => ({
|
|
35
|
+
supportLocales: ['en-US', 'zh-CN'],
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
vi.clearAllMocks();
|
|
40
|
+
useChatStore.setState(
|
|
41
|
+
{
|
|
42
|
+
// ... 初始状态
|
|
43
|
+
},
|
|
44
|
+
false,
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
vi.restoreAllMocks();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('ChatEnhanceAction', () => {
|
|
53
|
+
describe('clearTTS', () => {
|
|
54
|
+
it('should clear TTS for a message and refresh messages', async () => {
|
|
55
|
+
const { result } = renderHook(() => useChatStore());
|
|
56
|
+
const messageId = 'message-id';
|
|
57
|
+
|
|
58
|
+
await act(async () => {
|
|
59
|
+
await result.current.clearTTS(messageId);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(messageService.updateMessageTTS).toHaveBeenCalledWith(messageId, null);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('translateMessage', () => {
|
|
67
|
+
it('should translate a message to the target language and refresh messages', async () => {
|
|
68
|
+
const { result } = renderHook(() => useChatStore());
|
|
69
|
+
const messageId = 'message-id';
|
|
70
|
+
const targetLang = 'zh-CN';
|
|
71
|
+
const messageContent = 'Hello World';
|
|
72
|
+
const detectedLang = 'en-US';
|
|
73
|
+
|
|
74
|
+
// 设置初始消息状态
|
|
75
|
+
useChatStore.setState({
|
|
76
|
+
messages: [
|
|
77
|
+
{
|
|
78
|
+
id: messageId,
|
|
79
|
+
content: messageContent,
|
|
80
|
+
createdAt: Date.now(),
|
|
81
|
+
updatedAt: Date.now(),
|
|
82
|
+
role: 'user',
|
|
83
|
+
sessionId: 'test',
|
|
84
|
+
topicId: 'test',
|
|
85
|
+
meta: {},
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// 模拟语言检测和翻译结果
|
|
91
|
+
(chatService.fetchPresetTaskResult as Mock).mockImplementation(({ params }) => {
|
|
92
|
+
if (params === chainLangDetect(messageContent)) {
|
|
93
|
+
return Promise.resolve(detectedLang);
|
|
94
|
+
}
|
|
95
|
+
if (params === chainTranslate(messageContent, targetLang)) {
|
|
96
|
+
return Promise.resolve('Hola Mundo');
|
|
97
|
+
}
|
|
98
|
+
return Promise.resolve(undefined);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await act(async () => {
|
|
102
|
+
await result.current.translateMessage(messageId, targetLang);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(messageService.updateMessageTranslate).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('clearTranslate', () => {
|
|
110
|
+
it('should clear translation for a message and refresh messages', async () => {
|
|
111
|
+
const { result } = renderHook(() => useChatStore());
|
|
112
|
+
const messageId = 'message-id';
|
|
113
|
+
|
|
114
|
+
await act(async () => {
|
|
115
|
+
await result.current.clearTranslate(messageId);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(messageService.updateMessageTranslate).toHaveBeenCalledWith(messageId, null);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('ttsMessage', () => {
|
|
123
|
+
it('should update TTS state for a message and refresh messages', async () => {
|
|
124
|
+
const { result } = renderHook(() => useChatStore());
|
|
125
|
+
const messageId = 'message-id';
|
|
126
|
+
const ttsState = {
|
|
127
|
+
contentMd5: 'some-md5',
|
|
128
|
+
file: 'path-to-tts-file',
|
|
129
|
+
voice: 'voice-type',
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
await act(async () => {
|
|
133
|
+
await result.current.ttsMessage(messageId, ttsState);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(messageService.updateMessageTTS).toHaveBeenCalledWith(messageId, ttsState);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -20,7 +20,10 @@ export interface ChatEnhanceAction {
|
|
|
20
20
|
|
|
21
21
|
clearTranslate: (id: string) => Promise<void>;
|
|
22
22
|
translateMessage: (id: string, targetLang: string) => Promise<void>;
|
|
23
|
-
ttsMessage: (
|
|
23
|
+
ttsMessage: (
|
|
24
|
+
id: string,
|
|
25
|
+
state?: { contentMd5?: string; file?: string; voice?: string },
|
|
26
|
+
) => Promise<void>;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export const chatEnhance: StateCreator<
|
|
@@ -88,8 +91,8 @@ export const chatEnhance: StateCreator<
|
|
|
88
91
|
toggleChatLoading(false);
|
|
89
92
|
},
|
|
90
93
|
|
|
91
|
-
ttsMessage: async (id,
|
|
92
|
-
await messageService.updateMessageTTS(id,
|
|
94
|
+
ttsMessage: async (id, state = {}) => {
|
|
95
|
+
await messageService.updateMessageTTS(id, state);
|
|
93
96
|
await get().refreshMessages();
|
|
94
97
|
},
|
|
95
98
|
});
|
|
@@ -1,34 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const getImageDetailByList = (list: string[]) => (s: FilesStoreState) =>
|
|
4
|
-
list.map((i) => s.imagesMap[i]).filter(Boolean);
|
|
5
|
-
|
|
6
|
-
const imageDetailList = (s: FilesStoreState) => getImageDetailByList(s.inputFilesList)(s);
|
|
7
|
-
|
|
8
|
-
const getImageUrlOrBase64ById =
|
|
9
|
-
(id: string) =>
|
|
10
|
-
(s: FilesStoreState): { id: string; url: string } | undefined => {
|
|
11
|
-
const preview = s.imagesMap[id];
|
|
12
|
-
|
|
13
|
-
if (!preview) return undefined;
|
|
14
|
-
|
|
15
|
-
const url = preview.saveMode === 'local' ? (preview.base64Url as string) : preview.url;
|
|
16
|
-
|
|
17
|
-
return { id, url: url };
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const getImageUrlOrBase64ByList = (idList: string[]) => (s: FilesStoreState) =>
|
|
21
|
-
idList.map((i) => getImageUrlOrBase64ById(i)(s)).filter(Boolean) as {
|
|
22
|
-
id: string;
|
|
23
|
-
url: string;
|
|
24
|
-
}[];
|
|
25
|
-
|
|
26
|
-
const imageUrlOrBase64List = (s: FilesStoreState) => getImageUrlOrBase64ByList(s.inputFilesList)(s);
|
|
1
|
+
import { filesSelectors as imageFilesSelectors } from './slices/images';
|
|
2
|
+
import { ttsFilesSelectors } from './slices/tts';
|
|
27
3
|
|
|
28
4
|
export const filesSelectors = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
getImageUrlOrBase64ByList,
|
|
32
|
-
imageDetailList,
|
|
33
|
-
imageUrlOrBase64List,
|
|
5
|
+
...imageFilesSelectors,
|
|
6
|
+
...ttsFilesSelectors,
|
|
34
7
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FilesStoreState } from '../../initialState';
|
|
2
|
+
|
|
3
|
+
const getImageDetailByList = (list: string[]) => (s: FilesStoreState) =>
|
|
4
|
+
list.map((i) => s.imagesMap[i]).filter(Boolean);
|
|
5
|
+
|
|
6
|
+
const imageDetailList = (s: FilesStoreState) => getImageDetailByList(s.inputFilesList)(s);
|
|
7
|
+
|
|
8
|
+
const getImageUrlOrBase64ById =
|
|
9
|
+
(id: string) =>
|
|
10
|
+
(s: FilesStoreState): { id: string; url: string } | undefined => {
|
|
11
|
+
const preview = s.imagesMap[id];
|
|
12
|
+
|
|
13
|
+
if (!preview) return undefined;
|
|
14
|
+
|
|
15
|
+
const url = preview.saveMode === 'local' ? (preview.base64Url as string) : preview.url;
|
|
16
|
+
|
|
17
|
+
return { id, url: url };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getImageUrlOrBase64ByList = (idList: string[]) => (s: FilesStoreState) =>
|
|
21
|
+
idList.map((i) => getImageUrlOrBase64ById(i)(s)).filter(Boolean) as {
|
|
22
|
+
id: string;
|
|
23
|
+
url: string;
|
|
24
|
+
}[];
|
|
25
|
+
|
|
26
|
+
const imageUrlOrBase64List = (s: FilesStoreState) => getImageUrlOrBase64ByList(s.inputFilesList)(s);
|
|
27
|
+
|
|
28
|
+
export const filesSelectors = {
|
|
29
|
+
getImageDetailByList,
|
|
30
|
+
getImageUrlOrBase64ById,
|
|
31
|
+
getImageUrlOrBase64ByList,
|
|
32
|
+
imageDetailList,
|
|
33
|
+
imageUrlOrBase64List,
|
|
34
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { Mock, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { fileService } from '@/services/file';
|
|
6
|
+
|
|
7
|
+
import { useFileStore as useStore } from '../../store';
|
|
8
|
+
|
|
9
|
+
vi.mock('zustand/traditional');
|
|
10
|
+
|
|
11
|
+
// Mocks for fileService
|
|
12
|
+
vi.mock('@/services/file', () => ({
|
|
13
|
+
fileService: {
|
|
14
|
+
removeFile: vi.fn(),
|
|
15
|
+
uploadFile: vi.fn(),
|
|
16
|
+
getFile: vi.fn(),
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
// Mock for useSWR
|
|
21
|
+
vi.mock('swr', () => ({
|
|
22
|
+
default: vi.fn(),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
// mock the arrayBuffer
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
Object.defineProperty(File.prototype, 'arrayBuffer', {
|
|
28
|
+
writable: true,
|
|
29
|
+
value: function () {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const reader = new FileReader();
|
|
32
|
+
reader.onloadend = () => {
|
|
33
|
+
resolve(reader.result);
|
|
34
|
+
};
|
|
35
|
+
reader.readAsArrayBuffer(this);
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
// Reset all mocks before each test
|
|
43
|
+
vi.resetAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('TTSFileAction', () => {
|
|
47
|
+
// Test for removeTTSFile
|
|
48
|
+
it('removeTTSFile should call fileService.removeFile', async () => {
|
|
49
|
+
const fileId = 'tts-file-id';
|
|
50
|
+
|
|
51
|
+
// Mock the fileService.removeFile to resolve
|
|
52
|
+
(fileService.removeFile as Mock).mockResolvedValue(undefined);
|
|
53
|
+
|
|
54
|
+
await act(async () => {
|
|
55
|
+
await useStore.getState().removeTTSFile(fileId);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(fileService.removeFile).toHaveBeenCalledWith(fileId);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Test for uploadTTSFile
|
|
62
|
+
it('uploadTTSFile should upload the file and return the file id', async () => {
|
|
63
|
+
const testFile = new File(['content'], 'test.mp3', { type: 'audio/mp3' });
|
|
64
|
+
const uploadedFileData = {
|
|
65
|
+
id: 'new-tts-file-id',
|
|
66
|
+
createdAt: testFile.lastModified,
|
|
67
|
+
data: await testFile.arrayBuffer(),
|
|
68
|
+
fileType: testFile.type,
|
|
69
|
+
name: testFile.name,
|
|
70
|
+
saveMode: 'local',
|
|
71
|
+
size: testFile.size,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Mock the fileService.uploadFile to resolve with uploadedFileData
|
|
75
|
+
(fileService.uploadFile as Mock).mockResolvedValue(uploadedFileData);
|
|
76
|
+
|
|
77
|
+
let fileId;
|
|
78
|
+
await act(async () => {
|
|
79
|
+
fileId = await useStore.getState().uploadTTSFile(testFile);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(fileService.uploadFile).toHaveBeenCalledWith({
|
|
83
|
+
createdAt: testFile.lastModified,
|
|
84
|
+
data: await testFile.arrayBuffer(),
|
|
85
|
+
fileType: testFile.type,
|
|
86
|
+
name: testFile.name,
|
|
87
|
+
saveMode: 'local',
|
|
88
|
+
size: testFile.size,
|
|
89
|
+
});
|
|
90
|
+
expect(fileId).toBe(uploadedFileData.id);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Test for uploadTTSByArrayBuffers
|
|
94
|
+
it('uploadTTSByArrayBuffers should create a file and call uploadTTSFile', async () => {
|
|
95
|
+
const messageId = 'message-id';
|
|
96
|
+
const arrayBuffers = [new ArrayBuffer(10)];
|
|
97
|
+
const fileType = 'audio/mp3';
|
|
98
|
+
const fileName = `${messageId}.mp3`;
|
|
99
|
+
|
|
100
|
+
// Spy on uploadTTSFile to simulate a successful upload
|
|
101
|
+
const uploadTTSFileSpy = vi
|
|
102
|
+
.spyOn(useStore.getState(), 'uploadTTSFile')
|
|
103
|
+
.mockResolvedValue('new-tts-file-id');
|
|
104
|
+
|
|
105
|
+
let fileId;
|
|
106
|
+
await act(async () => {
|
|
107
|
+
fileId = await useStore.getState().uploadTTSByArrayBuffers(messageId, arrayBuffers);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(uploadTTSFileSpy).toHaveBeenCalled();
|
|
111
|
+
expect(fileId).toBe('new-tts-file-id');
|
|
112
|
+
|
|
113
|
+
// Cleanup spy
|
|
114
|
+
uploadTTSFileSpy.mockRestore();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Test for useFetchTTSFile
|
|
118
|
+
it('useFetchTTSFile should call useSWR and return file data', async () => {
|
|
119
|
+
const fileId = 'tts-file-id';
|
|
120
|
+
const fileData = {
|
|
121
|
+
id: fileId,
|
|
122
|
+
name: 'test',
|
|
123
|
+
url: 'blob:test',
|
|
124
|
+
fileType: 'audio/mp3',
|
|
125
|
+
base64Url: '',
|
|
126
|
+
saveMode: 'local',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Mock the fileService.getFile to resolve with fileData
|
|
130
|
+
(fileService.getFile as Mock).mockResolvedValue(fileData);
|
|
131
|
+
|
|
132
|
+
// Mock useSWR to call the fetcher function immediately
|
|
133
|
+
const useSWRMock = vi.mocked(useSWR);
|
|
134
|
+
useSWRMock.mockImplementation(((key: string, fetcher: any) => {
|
|
135
|
+
const data = fetcher(key);
|
|
136
|
+
return { data, error: undefined, isValidating: false, mutate: vi.fn() };
|
|
137
|
+
}) as any);
|
|
138
|
+
|
|
139
|
+
const { result } = renderHook(() => useStore.getState().useFetchTTSFile(fileId));
|
|
140
|
+
|
|
141
|
+
await act(async () => {
|
|
142
|
+
await result.current.data;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(fileService.getFile).toHaveBeenCalledWith(fileId);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import useSWR, { SWRResponse } from 'swr';
|
|
2
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
3
|
+
|
|
4
|
+
import { fileService } from '@/services/file';
|
|
5
|
+
import { FilePreview } from '@/types/files';
|
|
6
|
+
|
|
7
|
+
import { FileStore } from '../../store';
|
|
8
|
+
|
|
9
|
+
export interface TTSFileAction {
|
|
10
|
+
removeTTSFile: (id: string) => Promise<void>;
|
|
11
|
+
|
|
12
|
+
uploadTTSByArrayBuffers: (
|
|
13
|
+
messageId: string,
|
|
14
|
+
arrayBuffers: ArrayBuffer[],
|
|
15
|
+
) => Promise<string | undefined>;
|
|
16
|
+
|
|
17
|
+
uploadTTSFile: (file: File) => Promise<string | undefined>;
|
|
18
|
+
|
|
19
|
+
useFetchTTSFile: (id: string | null) => SWRResponse<FilePreview>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const createTTSFileSlice: StateCreator<
|
|
23
|
+
FileStore,
|
|
24
|
+
[['zustand/devtools', never]],
|
|
25
|
+
[],
|
|
26
|
+
TTSFileAction
|
|
27
|
+
> = (_, get) => ({
|
|
28
|
+
removeTTSFile: async (id) => {
|
|
29
|
+
await fileService.removeFile(id);
|
|
30
|
+
},
|
|
31
|
+
uploadTTSByArrayBuffers: async (messageId, arrayBuffers) => {
|
|
32
|
+
const fileType = 'audio/mp3';
|
|
33
|
+
const blob = new Blob(arrayBuffers, { type: fileType });
|
|
34
|
+
const fileName = `${messageId}.mp3`;
|
|
35
|
+
const fileOptions = {
|
|
36
|
+
lastModified: Date.now(),
|
|
37
|
+
type: fileType,
|
|
38
|
+
};
|
|
39
|
+
const file = new File([blob], fileName, fileOptions);
|
|
40
|
+
return get().uploadTTSFile(file);
|
|
41
|
+
},
|
|
42
|
+
uploadTTSFile: async (file) => {
|
|
43
|
+
try {
|
|
44
|
+
const data = await fileService.uploadFile({
|
|
45
|
+
createdAt: file.lastModified,
|
|
46
|
+
data: await file.arrayBuffer(),
|
|
47
|
+
fileType: file.type,
|
|
48
|
+
name: file.name,
|
|
49
|
+
saveMode: 'local',
|
|
50
|
+
size: file.size,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return data.id;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// 提示用户上传失败
|
|
56
|
+
console.error('upload error:', error);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
useFetchTTSFile: (id) => useSWR(id, fileService.getFile),
|
|
60
|
+
});
|
package/src/store/file/store.ts
CHANGED
|
@@ -7,14 +7,16 @@ import { isDev } from '@/utils/env';
|
|
|
7
7
|
|
|
8
8
|
import { FilesStoreState, initialState } from './initialState';
|
|
9
9
|
import { FileAction, createFileSlice } from './slices/images';
|
|
10
|
+
import { TTSFileAction, createTTSFileSlice } from './slices/tts';
|
|
10
11
|
|
|
11
12
|
// =============== 聚合 createStoreFn ============ //
|
|
12
13
|
|
|
13
|
-
export type FileStore = FilesStoreState & FileAction;
|
|
14
|
+
export type FileStore = FilesStoreState & FileAction & TTSFileAction;
|
|
14
15
|
|
|
15
16
|
const createStore: StateCreator<FileStore, [['zustand/devtools', never]]> = (...parameters) => ({
|
|
16
17
|
...initialState,
|
|
17
18
|
...createFileSlice(...parameters),
|
|
19
|
+
...createTTSFileSlice(...parameters),
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
// =============== 实装 useStore ============ //
|