@lobehub/chat 0.107.16 → 0.109.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 +1 -1
- package/README.zh-CN.md +1 -1
- package/docs/Environment-Variable.md +2 -2
- package/docs/Environment-Variable.zh-CN.md +2 -2
- package/locales/en_US/plugin.json +19 -0
- package/locales/en_US/setting.json +26 -0
- package/locales/ja_JP/plugin.json +19 -0
- package/locales/ja_JP/setting.json +26 -0
- package/locales/ko_KR/plugin.json +19 -0
- package/locales/ko_KR/setting.json +26 -0
- package/locales/ru_RU/plugin.json +19 -0
- package/locales/ru_RU/setting.json +26 -0
- package/locales/zh_CN/plugin.json +19 -0
- package/locales/zh_CN/setting.json +26 -5
- package/locales/zh_TW/plugin.json +19 -0
- package/locales/zh_TW/setting.json +26 -0
- package/package.json +3 -3
- package/src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx +6 -6
- package/src/app/chat/features/ChatHeader/PluginTag/index.tsx +4 -4
- package/src/app/chat/features/ChatInput/ActionBar/Tools/ToolItem.tsx +37 -0
- package/src/app/chat/features/ChatInput/ActionBar/Tools/index.tsx +116 -0
- package/src/app/chat/features/ChatInput/ActionBar/config.ts +3 -1
- package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +4 -4
- package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +3 -3
- package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +4 -4
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +4 -4
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/index.tsx +2 -2
- package/src/app/chat/features/Conversation/useInitConversation.ts +2 -2
- package/src/app/settings/(desktop)/index.tsx +3 -2
- package/src/app/settings/common/Common.tsx +8 -3
- package/src/app/settings/common/index.tsx +3 -3
- package/src/app/settings/common/page.tsx +7 -1
- package/src/app/settings/page.tsx +4 -1
- package/src/config/server.ts +5 -1
- package/src/features/AgentSetting/AgentPlugin/AddPluginButton.tsx +56 -0
- package/src/features/AgentSetting/AgentPlugin/LoadingList.tsx +48 -0
- package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +5 -5
- package/src/features/AgentSetting/AgentPlugin/PluginAction/index.tsx +36 -0
- package/src/features/AgentSetting/AgentPlugin/index.tsx +157 -9
- package/src/features/AgentSetting/store/action.ts +0 -1
- package/src/features/PluginDevModal/MetaForm.tsx +3 -3
- package/src/features/PluginDevModal/PluginPreview.tsx +1 -1
- package/src/features/PluginDevModal/index.tsx +4 -1
- package/src/features/PluginSettings/index.tsx +3 -3
- package/src/features/PluginSettingsModal/index.tsx +22 -17
- package/src/features/PluginStore/Loading.tsx +13 -0
- package/src/features/PluginStore/PluginItem.tsx +81 -0
- package/src/features/PluginStore/PluginSettings.tsx +40 -0
- package/src/features/{AgentSetting/AgentPlugin/MarketSettingModal.tsx → PluginStore/StoreSetting.tsx} +2 -2
- package/src/features/PluginStore/index.tsx +95 -0
- package/src/layout/GlobalLayout/StoreHydration.tsx +3 -3
- package/src/locales/default/plugin.ts +19 -0
- package/src/locales/default/setting.ts +26 -5
- package/src/services/__tests__/chat.test.ts +221 -0
- package/src/services/__tests__/message.test.ts +333 -0
- package/src/services/__tests__/plugin.test.ts +88 -0
- package/src/services/__tests__/session.test.ts +227 -0
- package/src/services/__tests__/topic.test.ts +220 -0
- package/src/services/chat.ts +4 -6
- package/src/services/plugin.ts +12 -1
- package/src/store/chat/actions/message.test.ts +0 -1
- package/src/store/session/slices/agent/action.ts +24 -8
- package/src/store/{plugin → tool}/helpers.ts +2 -13
- package/src/store/tool/index.ts +2 -0
- package/src/store/tool/initialState.ts +11 -0
- package/src/store/tool/selectors.ts +3 -0
- package/src/store/tool/slices/customPlugin/action.test.ts +164 -0
- package/src/store/{plugin → tool}/slices/customPlugin/action.ts +2 -2
- package/src/store/tool/slices/customPlugin/selectors.test.ts +59 -0
- package/src/store/tool/slices/customPlugin/selectors.ts +9 -0
- package/src/store/tool/slices/plugin/action.test.ts +141 -0
- package/src/store/tool/slices/plugin/action.ts +95 -0
- package/src/store/{plugin → tool}/slices/plugin/initialState.ts +0 -7
- package/src/store/{plugin → tool/slices/plugin}/selectors.test.ts +4 -41
- package/src/store/{plugin → tool/slices/plugin}/selectors.ts +17 -19
- package/src/store/tool/slices/store/action.test.ts +396 -0
- package/src/store/tool/slices/store/action.ts +103 -0
- package/src/store/tool/slices/store/index.ts +3 -0
- package/src/store/tool/slices/store/initialState.ts +17 -0
- package/src/store/tool/slices/store/selectors.test.ts +48 -0
- package/src/store/tool/slices/store/selectors.ts +21 -0
- package/src/store/{plugin → tool}/store.ts +8 -6
- package/vitest.config.ts +1 -0
- package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +0 -232
- package/src/features/AgentSetting/AgentPlugin/PluginSettings.tsx +0 -80
- package/src/store/chat/reducers/files.test.ts +0 -38
- package/src/store/chat/reducers/files.ts +0 -37
- package/src/store/plugin/hooks/useSessionHydrated.ts +0 -21
- package/src/store/plugin/index.ts +0 -2
- package/src/store/plugin/initialState.ts +0 -9
- package/src/store/plugin/slices/plugin/action.ts +0 -173
- /package/src/store/{plugin → tool}/slices/customPlugin/index.ts +0 -0
- /package/src/store/{plugin → tool}/slices/customPlugin/initialState.ts +0 -0
- /package/src/store/{plugin → tool}/slices/customPlugin/reducers/customPluginList.ts +0 -0
- /package/src/store/{plugin → tool}/slices/plugin/index.ts +0 -0
- /package/src/store/{plugin → tool}/slices/plugin/reducers/manifest.ts +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { act } from '@testing-library/react';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { VISION_MODEL_WHITE_LIST } from '@/const/llm';
|
|
6
|
+
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
|
7
|
+
import { useFileStore } from '@/store/file';
|
|
8
|
+
import { useToolStore } from '@/store/tool';
|
|
9
|
+
import { ChatMessage } from '@/types/chatMessage';
|
|
10
|
+
import { OpenAIChatStreamPayload } from '@/types/openai/chat';
|
|
11
|
+
import { fetchAIFactory } from '@/utils/fetch';
|
|
12
|
+
|
|
13
|
+
import { chatService } from '../chat';
|
|
14
|
+
|
|
15
|
+
// Mocking external dependencies
|
|
16
|
+
vi.stubGlobal(
|
|
17
|
+
'fetch',
|
|
18
|
+
vi.fn(() => Promise.resolve(new Response(JSON.stringify({ some: 'data' })))),
|
|
19
|
+
); // 用你的模拟响应替换这里的内容
|
|
20
|
+
|
|
21
|
+
vi.mock('@/utils/fetch', () => ({
|
|
22
|
+
fetchAIFactory: vi.fn(),
|
|
23
|
+
getMessageError: vi.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
describe('ChatService', () => {
|
|
27
|
+
describe('createAssistantMessage', () => {
|
|
28
|
+
it('should process messages and call getChatCompletion with the right parameters', async () => {
|
|
29
|
+
const getChatCompletionSpy = vi.spyOn(chatService, 'getChatCompletion');
|
|
30
|
+
const messages = [{ content: 'Hello', role: 'user' }] as ChatMessage[];
|
|
31
|
+
const enabledPlugins = ['plugin1'];
|
|
32
|
+
await act(async () => {
|
|
33
|
+
useToolStore.setState({
|
|
34
|
+
pluginManifestMap: {
|
|
35
|
+
plugin1: {
|
|
36
|
+
identifier: 'plugin1',
|
|
37
|
+
api: [{ name: 'api1' }],
|
|
38
|
+
type: 'default',
|
|
39
|
+
} as LobeChatPluginManifest,
|
|
40
|
+
plugin2: {
|
|
41
|
+
identifier: 'plugin2',
|
|
42
|
+
api: [{ name: 'api2' }],
|
|
43
|
+
type: 'standalone',
|
|
44
|
+
} as LobeChatPluginManifest,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
await chatService.createAssistantMessage({ messages, plugins: enabledPlugins });
|
|
49
|
+
|
|
50
|
+
expect(getChatCompletionSpy).toHaveBeenCalledWith(
|
|
51
|
+
expect.objectContaining({
|
|
52
|
+
functions: expect.arrayContaining([{ name: 'plugin1____api1____default' }]),
|
|
53
|
+
messages: expect.anything(),
|
|
54
|
+
}),
|
|
55
|
+
undefined,
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should not use tools for models in the vision model whitelist', async () => {
|
|
60
|
+
const getChatCompletionSpy = vi.spyOn(chatService, 'getChatCompletion');
|
|
61
|
+
const messages = [{ content: 'Hello', role: 'user' }] as ChatMessage[];
|
|
62
|
+
const modelInWhitelist = VISION_MODEL_WHITE_LIST[0];
|
|
63
|
+
|
|
64
|
+
await chatService.createAssistantMessage({
|
|
65
|
+
messages,
|
|
66
|
+
model: modelInWhitelist,
|
|
67
|
+
plugins: ['plugin1'],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(getChatCompletionSpy).toHaveBeenCalledWith(
|
|
71
|
+
expect.objectContaining({
|
|
72
|
+
functions: undefined,
|
|
73
|
+
model: modelInWhitelist,
|
|
74
|
+
}),
|
|
75
|
+
undefined,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// New test case for processMessages
|
|
80
|
+
it('should correctly process messages and handle content for vision models', async () => {
|
|
81
|
+
const messages = [
|
|
82
|
+
{ content: 'Hello', role: 'user', files: ['file1'] }, // Message with files
|
|
83
|
+
{ content: 'Hi', role: 'function', plugin: { identifier: 'plugin1' } }, // Message with function role
|
|
84
|
+
{ content: 'Hey', role: 'assistant' }, // Regular user message
|
|
85
|
+
] as ChatMessage[];
|
|
86
|
+
|
|
87
|
+
// Mock file store state to return a specific image URL or Base64 for the given files
|
|
88
|
+
act(() => {
|
|
89
|
+
useFileStore.setState({
|
|
90
|
+
imagesMap: {
|
|
91
|
+
file1: {
|
|
92
|
+
name: 'abc.png',
|
|
93
|
+
saveMode: 'url',
|
|
94
|
+
fileType: 'image/png',
|
|
95
|
+
url: 'http://example.com/image.jpg',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const getChatCompletionSpy = vi.spyOn(chatService, 'getChatCompletion');
|
|
102
|
+
await chatService.createAssistantMessage({ messages, plugins: [] });
|
|
103
|
+
|
|
104
|
+
expect(getChatCompletionSpy).toHaveBeenCalledWith(
|
|
105
|
+
{
|
|
106
|
+
messages: [
|
|
107
|
+
{
|
|
108
|
+
content: [
|
|
109
|
+
{ text: 'Hello', type: 'text' },
|
|
110
|
+
{
|
|
111
|
+
image_url: { detail: 'auto', url: 'http://example.com/image.jpg' },
|
|
112
|
+
type: 'image_url',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
role: 'user',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
content: 'Hi',
|
|
119
|
+
name: 'plugin1',
|
|
120
|
+
role: 'function',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
content: 'Hey',
|
|
124
|
+
role: 'assistant',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
undefined,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should correctly process messages and handle content for vision models', async () => {
|
|
133
|
+
const messages = [
|
|
134
|
+
{ content: 'Hello', role: 'user', files: ['file2'] }, // Message with files
|
|
135
|
+
{ content: 'Hi', role: 'function', plugin: { identifier: 'plugin1' } }, // Message with function role
|
|
136
|
+
{ content: 'Hey', role: 'assistant' }, // Regular user message
|
|
137
|
+
] as ChatMessage[];
|
|
138
|
+
|
|
139
|
+
// Mock file store state to return a specific image URL or Base64 for the given files
|
|
140
|
+
act(() => {
|
|
141
|
+
useFileStore.setState({
|
|
142
|
+
imagesMap: {
|
|
143
|
+
file1: {
|
|
144
|
+
name: 'abc.png',
|
|
145
|
+
saveMode: 'url',
|
|
146
|
+
fileType: 'image/png',
|
|
147
|
+
url: 'http://example.com/image.jpg',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const getChatCompletionSpy = vi.spyOn(chatService, 'getChatCompletion');
|
|
154
|
+
await chatService.createAssistantMessage({ messages, plugins: [] });
|
|
155
|
+
|
|
156
|
+
expect(getChatCompletionSpy).toHaveBeenCalledWith(
|
|
157
|
+
{
|
|
158
|
+
messages: [
|
|
159
|
+
{
|
|
160
|
+
content: 'Hello',
|
|
161
|
+
role: 'user',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
content: 'Hi',
|
|
165
|
+
name: 'plugin1',
|
|
166
|
+
role: 'function',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
content: 'Hey',
|
|
170
|
+
role: 'assistant',
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
undefined,
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
describe('getChatCompletion', () => {
|
|
179
|
+
it('should make a POST request with the correct payload', async () => {
|
|
180
|
+
const params: Partial<OpenAIChatStreamPayload> = {
|
|
181
|
+
model: 'test-model',
|
|
182
|
+
messages: [],
|
|
183
|
+
};
|
|
184
|
+
const options = {};
|
|
185
|
+
const expectedPayload = {
|
|
186
|
+
model: DEFAULT_AGENT_CONFIG.model,
|
|
187
|
+
stream: true,
|
|
188
|
+
...DEFAULT_AGENT_CONFIG.params,
|
|
189
|
+
...params,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
await chatService.getChatCompletion(params, options);
|
|
193
|
+
|
|
194
|
+
expect(global.fetch).toHaveBeenCalledWith(expect.any(String), {
|
|
195
|
+
body: JSON.stringify(expectedPayload),
|
|
196
|
+
headers: expect.any(Object),
|
|
197
|
+
method: 'POST',
|
|
198
|
+
signal: undefined,
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Add more test cases to cover different scenarios and edge cases
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('runPluginApi', () => {
|
|
206
|
+
it('should make a POST request and return the result text', async () => {
|
|
207
|
+
const params = { identifier: 'test-plugin', apiName: '1' }; // Add more properties if needed
|
|
208
|
+
const options = {};
|
|
209
|
+
const mockResponse = new Response('Plugin Result', { status: 200 });
|
|
210
|
+
|
|
211
|
+
global.fetch = vi.fn(() => Promise.resolve(mockResponse));
|
|
212
|
+
|
|
213
|
+
const result = await chatService.runPluginApi(params, options);
|
|
214
|
+
|
|
215
|
+
expect(global.fetch).toHaveBeenCalledWith(expect.any(String), expect.any(Object));
|
|
216
|
+
expect(result).toBe('Plugin Result');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Add more test cases to cover different scenarios and edge cases
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { Mock, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { CreateMessageParams, MessageModel } from '@/database/models/message';
|
|
4
|
+
import {
|
|
5
|
+
ChatMessage,
|
|
6
|
+
ChatMessageError,
|
|
7
|
+
ChatPluginPayload,
|
|
8
|
+
ChatTTS,
|
|
9
|
+
ChatTranslate,
|
|
10
|
+
} from '@/types/chatMessage';
|
|
11
|
+
|
|
12
|
+
import { messageService } from '../message';
|
|
13
|
+
|
|
14
|
+
// Mock the MessageModel
|
|
15
|
+
vi.mock('@/database/models/message', () => {
|
|
16
|
+
return {
|
|
17
|
+
MessageModel: {
|
|
18
|
+
create: vi.fn(),
|
|
19
|
+
batchCreate: vi.fn(),
|
|
20
|
+
isEmpty: vi.fn(),
|
|
21
|
+
query: vi.fn(),
|
|
22
|
+
delete: vi.fn(),
|
|
23
|
+
queryBySessionId: vi.fn(),
|
|
24
|
+
update: vi.fn(),
|
|
25
|
+
batchDelete: vi.fn(),
|
|
26
|
+
clearTable: vi.fn(),
|
|
27
|
+
batchUpdate: vi.fn(),
|
|
28
|
+
queryAll: vi.fn(),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('MessageService', () => {
|
|
34
|
+
// Mock data
|
|
35
|
+
const mockMessageId = 'mock-message-id';
|
|
36
|
+
const mockMessage = {
|
|
37
|
+
id: mockMessageId,
|
|
38
|
+
content: 'Mock message content',
|
|
39
|
+
sessionId: 'mock-session-id',
|
|
40
|
+
createdAt: 100,
|
|
41
|
+
updatedAt: 100,
|
|
42
|
+
role: 'user',
|
|
43
|
+
// ... other properties
|
|
44
|
+
} as ChatMessage;
|
|
45
|
+
const mockMessages = [mockMessage];
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
// Reset all mocks before running each test case
|
|
49
|
+
vi.resetAllMocks();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('create', () => {
|
|
53
|
+
it('should create a message and return its id', async () => {
|
|
54
|
+
// Setup
|
|
55
|
+
const createParams = {
|
|
56
|
+
content: 'New message content',
|
|
57
|
+
sessionId: '1',
|
|
58
|
+
// ... other properties
|
|
59
|
+
} as CreateMessageParams;
|
|
60
|
+
(MessageModel.create as Mock).mockResolvedValue({ id: mockMessageId });
|
|
61
|
+
|
|
62
|
+
// Execute
|
|
63
|
+
const messageId = await messageService.create(createParams);
|
|
64
|
+
|
|
65
|
+
// Assert
|
|
66
|
+
expect(MessageModel.create).toHaveBeenCalledWith(createParams);
|
|
67
|
+
expect(messageId).toBe(mockMessageId);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('batchCreate', () => {
|
|
72
|
+
it('should batch create messages', async () => {
|
|
73
|
+
// Setup
|
|
74
|
+
(MessageModel.batchCreate as Mock).mockResolvedValue(mockMessages);
|
|
75
|
+
|
|
76
|
+
// Execute
|
|
77
|
+
const result = await messageService.batchCreate(mockMessages);
|
|
78
|
+
|
|
79
|
+
// Assert
|
|
80
|
+
expect(MessageModel.batchCreate).toHaveBeenCalledWith(mockMessages);
|
|
81
|
+
expect(result).toBe(mockMessages);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('hasMessages', () => {
|
|
86
|
+
it('should return true if there are messages', async () => {
|
|
87
|
+
// Setup
|
|
88
|
+
(MessageModel.isEmpty as Mock).mockResolvedValue(false);
|
|
89
|
+
|
|
90
|
+
// Execute
|
|
91
|
+
const hasMessages = await messageService.hasMessages();
|
|
92
|
+
|
|
93
|
+
// Assert
|
|
94
|
+
expect(MessageModel.isEmpty).toHaveBeenCalled();
|
|
95
|
+
expect(hasMessages).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should return false if there are no messages', async () => {
|
|
99
|
+
// Setup
|
|
100
|
+
(MessageModel.isEmpty as Mock).mockResolvedValue(true);
|
|
101
|
+
|
|
102
|
+
// Execute
|
|
103
|
+
const hasMessages = await messageService.hasMessages();
|
|
104
|
+
|
|
105
|
+
// Assert
|
|
106
|
+
expect(MessageModel.isEmpty).toHaveBeenCalled();
|
|
107
|
+
expect(hasMessages).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('removeMessage', () => {
|
|
112
|
+
it('should remove a message by id', async () => {
|
|
113
|
+
// Setup
|
|
114
|
+
(MessageModel.delete as Mock).mockResolvedValue(true);
|
|
115
|
+
|
|
116
|
+
// Execute
|
|
117
|
+
const result = await messageService.removeMessage(mockMessageId);
|
|
118
|
+
|
|
119
|
+
// Assert
|
|
120
|
+
expect(MessageModel.delete).toHaveBeenCalledWith(mockMessageId);
|
|
121
|
+
expect(result).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('getMessages', () => {
|
|
126
|
+
it('should retrieve messages by sessionId and topicId', async () => {
|
|
127
|
+
// Setup
|
|
128
|
+
const sessionId = 'session-id';
|
|
129
|
+
const topicId = 'topic-id';
|
|
130
|
+
(MessageModel.query as Mock).mockResolvedValue(mockMessages);
|
|
131
|
+
|
|
132
|
+
// Execute
|
|
133
|
+
const messages = await messageService.getMessages(sessionId, topicId);
|
|
134
|
+
|
|
135
|
+
// Assert
|
|
136
|
+
expect(MessageModel.query).toHaveBeenCalledWith({ sessionId, topicId });
|
|
137
|
+
expect(messages).toBe(mockMessages);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('getAllMessagesInSession', () => {
|
|
142
|
+
it('should retrieve all messages in a session', async () => {
|
|
143
|
+
// Setup
|
|
144
|
+
const sessionId = 'session-id';
|
|
145
|
+
(MessageModel.queryBySessionId as Mock).mockResolvedValue(mockMessages);
|
|
146
|
+
|
|
147
|
+
// Execute
|
|
148
|
+
const messages = await messageService.getAllMessagesInSession(sessionId);
|
|
149
|
+
|
|
150
|
+
// Assert
|
|
151
|
+
expect(MessageModel.queryBySessionId).toHaveBeenCalledWith(sessionId);
|
|
152
|
+
expect(messages).toBe(mockMessages);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('updateMessageContent', () => {
|
|
157
|
+
it('should update the content of a message', async () => {
|
|
158
|
+
// Setup
|
|
159
|
+
const newContent = 'Updated message content';
|
|
160
|
+
(MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, content: newContent });
|
|
161
|
+
|
|
162
|
+
// Execute
|
|
163
|
+
const result = await messageService.updateMessageContent(mockMessageId, newContent);
|
|
164
|
+
|
|
165
|
+
// Assert
|
|
166
|
+
expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { content: newContent });
|
|
167
|
+
expect(result).toEqual({ ...mockMessage, content: newContent });
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('removeMessages', () => {
|
|
172
|
+
it('should batch remove messages by assistantId and topicId', async () => {
|
|
173
|
+
// Setup
|
|
174
|
+
const assistantId = 'assistant-id';
|
|
175
|
+
const topicId = 'topic-id';
|
|
176
|
+
(MessageModel.batchDelete as Mock).mockResolvedValue(true);
|
|
177
|
+
|
|
178
|
+
// Execute
|
|
179
|
+
const result = await messageService.removeMessages(assistantId, topicId);
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
expect(MessageModel.batchDelete).toHaveBeenCalledWith(assistantId, topicId);
|
|
183
|
+
expect(result).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('clearAllMessage', () => {
|
|
188
|
+
it('should clear all messages from the table', async () => {
|
|
189
|
+
// Setup
|
|
190
|
+
(MessageModel.clearTable as Mock).mockResolvedValue(true);
|
|
191
|
+
|
|
192
|
+
// Execute
|
|
193
|
+
const result = await messageService.clearAllMessage();
|
|
194
|
+
|
|
195
|
+
// Assert
|
|
196
|
+
expect(MessageModel.clearTable).toHaveBeenCalled();
|
|
197
|
+
expect(result).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('bindMessagesToTopic', () => {
|
|
202
|
+
it('should batch update messages to bind them to a topic', async () => {
|
|
203
|
+
// Setup
|
|
204
|
+
const topicId = 'topic-id';
|
|
205
|
+
const messageIds = [mockMessageId];
|
|
206
|
+
(MessageModel.batchUpdate as Mock).mockResolvedValue(mockMessages);
|
|
207
|
+
|
|
208
|
+
// Execute
|
|
209
|
+
const result = await messageService.bindMessagesToTopic(topicId, messageIds);
|
|
210
|
+
|
|
211
|
+
// Assert
|
|
212
|
+
expect(MessageModel.batchUpdate).toHaveBeenCalledWith(messageIds, { topicId });
|
|
213
|
+
expect(result).toBe(mockMessages);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('getAllMessages', () => {
|
|
218
|
+
it('should retrieve all messages', async () => {
|
|
219
|
+
// Setup
|
|
220
|
+
(MessageModel.queryAll as Mock).mockResolvedValue(mockMessages);
|
|
221
|
+
|
|
222
|
+
// Execute
|
|
223
|
+
const messages = await messageService.getAllMessages();
|
|
224
|
+
|
|
225
|
+
// Assert
|
|
226
|
+
expect(MessageModel.queryAll).toHaveBeenCalled();
|
|
227
|
+
expect(messages).toBe(mockMessages);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('updateMessageError', () => {
|
|
232
|
+
it('should update the error field of a message', async () => {
|
|
233
|
+
// Setup
|
|
234
|
+
const newError = { type: 'NoAPIKey', message: 'Error occurred' } as ChatMessageError;
|
|
235
|
+
(MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, error: newError });
|
|
236
|
+
|
|
237
|
+
// Execute
|
|
238
|
+
const result = await messageService.updateMessageError(mockMessageId, newError);
|
|
239
|
+
|
|
240
|
+
// Assert
|
|
241
|
+
expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { error: newError });
|
|
242
|
+
expect(result).toEqual({ ...mockMessage, error: newError });
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('updateMessageTranslate', () => {
|
|
247
|
+
it('should update the translate field of a message', async () => {
|
|
248
|
+
// Setup
|
|
249
|
+
const newTranslate = { content: 'Translated text', to: 'es' } as ChatTranslate;
|
|
250
|
+
(MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, translate: newTranslate });
|
|
251
|
+
|
|
252
|
+
// Execute
|
|
253
|
+
const result = await messageService.updateMessageTranslate(mockMessageId, newTranslate);
|
|
254
|
+
|
|
255
|
+
// Assert
|
|
256
|
+
expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { translate: newTranslate });
|
|
257
|
+
expect(result).toEqual({ ...mockMessage, translate: newTranslate });
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('updateMessageTTS', () => {
|
|
262
|
+
it('should update the tts field of a message', async () => {
|
|
263
|
+
// Setup
|
|
264
|
+
const newTTS = { init: false } as ChatTTS;
|
|
265
|
+
(MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, tts: newTTS });
|
|
266
|
+
|
|
267
|
+
// Execute
|
|
268
|
+
const result = await messageService.updateMessageTTS(mockMessageId, newTTS);
|
|
269
|
+
|
|
270
|
+
// Assert
|
|
271
|
+
expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { tts: newTTS });
|
|
272
|
+
expect(result).toEqual({ ...mockMessage, tts: newTTS });
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('updateMessageRole', () => {
|
|
277
|
+
it('should update the role of a message', async () => {
|
|
278
|
+
// Setup
|
|
279
|
+
const newRole = 'user';
|
|
280
|
+
(MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, role: newRole });
|
|
281
|
+
|
|
282
|
+
// Execute
|
|
283
|
+
const result = await messageService.updateMessageRole(mockMessageId, newRole);
|
|
284
|
+
|
|
285
|
+
// Assert
|
|
286
|
+
expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { role: newRole });
|
|
287
|
+
expect(result).toEqual({ ...mockMessage, role: newRole });
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe('updateMessagePlugin', () => {
|
|
292
|
+
it('should update the plugin payload of a message', async () => {
|
|
293
|
+
// Setup
|
|
294
|
+
const newPlugin = {
|
|
295
|
+
type: 'default',
|
|
296
|
+
apiName: 'abc',
|
|
297
|
+
arguments: '',
|
|
298
|
+
identifier: 'plugin1',
|
|
299
|
+
} as ChatPluginPayload;
|
|
300
|
+
|
|
301
|
+
(MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, plugin: newPlugin });
|
|
302
|
+
|
|
303
|
+
// Execute
|
|
304
|
+
const result = await messageService.updateMessagePlugin(mockMessageId, newPlugin);
|
|
305
|
+
|
|
306
|
+
// Assert
|
|
307
|
+
expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { plugin: newPlugin });
|
|
308
|
+
expect(result).toEqual({ ...mockMessage, plugin: newPlugin });
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe('updateMessagePluginState', () => {
|
|
313
|
+
it('should update the plugin state of a message', async () => {
|
|
314
|
+
// Setup
|
|
315
|
+
const key = 'stateKey';
|
|
316
|
+
const value = 'stateValue';
|
|
317
|
+
const newPluginState = { [key]: value };
|
|
318
|
+
(MessageModel.update as Mock).mockResolvedValue({
|
|
319
|
+
...mockMessage,
|
|
320
|
+
pluginState: newPluginState,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Execute
|
|
324
|
+
const result = await messageService.updateMessagePluginState(mockMessageId, key, value);
|
|
325
|
+
|
|
326
|
+
// Assert
|
|
327
|
+
expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, {
|
|
328
|
+
pluginState: newPluginState,
|
|
329
|
+
});
|
|
330
|
+
expect(result).toEqual({ ...mockMessage, pluginState: newPluginState });
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { getPluginIndexJSON } from '@/const/url';
|
|
4
|
+
import { getCurrentLanguage } from '@/store/global/helpers';
|
|
5
|
+
|
|
6
|
+
import { pluginService } from '../plugin';
|
|
7
|
+
|
|
8
|
+
// Mocking modules and functions
|
|
9
|
+
vi.mock('@/const/url', () => ({
|
|
10
|
+
getPluginIndexJSON: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
vi.mock('@/store/global/helpers', () => ({
|
|
13
|
+
getCurrentLanguage: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe('PluginService', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.resetAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('getPluginList', () => {
|
|
22
|
+
it('should fetch and return the plugin list', async () => {
|
|
23
|
+
// Arrange
|
|
24
|
+
const fakeResponse = { plugins: [{ name: 'TestPlugin' }] };
|
|
25
|
+
const fakeUrl = 'http://fake-url.com/plugins.json';
|
|
26
|
+
(getCurrentLanguage as Mock).mockReturnValue('en');
|
|
27
|
+
(getPluginIndexJSON as Mock).mockReturnValue(fakeUrl);
|
|
28
|
+
global.fetch = vi.fn(() =>
|
|
29
|
+
Promise.resolve({
|
|
30
|
+
json: () => Promise.resolve(fakeResponse),
|
|
31
|
+
}),
|
|
32
|
+
) as any;
|
|
33
|
+
|
|
34
|
+
// Act
|
|
35
|
+
const pluginList = await pluginService.getPluginList();
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
expect(getCurrentLanguage).toHaveBeenCalled();
|
|
39
|
+
expect(getPluginIndexJSON).toHaveBeenCalledWith('en');
|
|
40
|
+
expect(fetch).toHaveBeenCalledWith(fakeUrl);
|
|
41
|
+
expect(pluginList).toEqual(fakeResponse);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should handle fetch error', async () => {
|
|
45
|
+
// Arrange
|
|
46
|
+
const fakeUrl = 'http://fake-url.com/plugins.json';
|
|
47
|
+
(getCurrentLanguage as Mock).mockReturnValue('en');
|
|
48
|
+
(getPluginIndexJSON as Mock).mockReturnValue(fakeUrl);
|
|
49
|
+
global.fetch = vi.fn(() => Promise.reject(new Error('Network error')));
|
|
50
|
+
|
|
51
|
+
// Act & Assert
|
|
52
|
+
await expect(pluginService.getPluginList()).rejects.toThrow('Network error');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('fetchManifest', () => {
|
|
57
|
+
it('should fetch and return the plugin manifest', async () => {
|
|
58
|
+
// Arrange
|
|
59
|
+
const fakeManifest = { name: 'TestPlugin', version: '1.0.0' };
|
|
60
|
+
const manifestUrl = 'http://fake-url.com/manifest.json';
|
|
61
|
+
global.fetch = vi.fn(() =>
|
|
62
|
+
Promise.resolve({
|
|
63
|
+
json: () => Promise.resolve(fakeManifest),
|
|
64
|
+
}),
|
|
65
|
+
) as any;
|
|
66
|
+
|
|
67
|
+
// Act
|
|
68
|
+
const manifest = await pluginService.fetchManifest(manifestUrl);
|
|
69
|
+
|
|
70
|
+
// Assert
|
|
71
|
+
expect(fetch).toHaveBeenCalledWith(manifestUrl);
|
|
72
|
+
expect(manifest).toEqual(fakeManifest);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return null on fetch error', async () => {
|
|
76
|
+
// Arrange
|
|
77
|
+
const manifestUrl = 'http://fake-url.com/manifest.json';
|
|
78
|
+
global.fetch = vi.fn(() => Promise.reject(new Error('Network error')));
|
|
79
|
+
|
|
80
|
+
// Act
|
|
81
|
+
const manifest = await pluginService.fetchManifest(manifestUrl);
|
|
82
|
+
|
|
83
|
+
// Assert
|
|
84
|
+
expect(fetch).toHaveBeenCalledWith(manifestUrl);
|
|
85
|
+
expect(manifest).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|