@lobehub/chat 0.147.12 → 0.147.13

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.
Files changed (28) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +1 -1
  3. package/src/features/PluginDevModal/UrlManifestForm.tsx +2 -2
  4. package/src/services/__tests__/__snapshots__/{plugin.test.ts.snap → tool.test.ts.snap} +2 -2
  5. package/src/services/__tests__/chat.test.ts +0 -1
  6. package/src/services/__tests__/{plugin.test.ts → tool.test.ts} +13 -160
  7. package/src/services/{file.ts → file/client.ts} +2 -4
  8. package/src/services/{__tests__ → file}/file.test.ts +3 -1
  9. package/src/services/file/index.ts +3 -0
  10. package/src/services/{__tests__/message.test.ts → message/client.test.ts} +5 -9
  11. package/src/services/{message.ts → message/client.ts} +0 -2
  12. package/src/services/message/index.ts +4 -0
  13. package/src/services/plugin/client.ts +44 -0
  14. package/src/services/plugin/index.ts +5 -0
  15. package/src/services/plugin/plugin.test.ts +162 -0
  16. package/src/services/{session.ts → session/client.ts} +1 -3
  17. package/src/services/session/index.ts +3 -0
  18. package/src/services/{__tests__ → session}/session.test.ts +3 -1
  19. package/src/services/{plugin.ts → tool.ts} +2 -41
  20. package/src/services/{topic.ts → topic/client.ts} +1 -3
  21. package/src/services/topic/index.ts +3 -0
  22. package/src/services/{__tests__ → topic}/topic.test.ts +2 -1
  23. package/src/services/{user.ts → user/client.ts} +1 -3
  24. package/src/services/user/index.ts +5 -0
  25. package/src/store/tool/slices/customPlugin/action.test.ts +7 -1
  26. package/src/store/tool/slices/customPlugin/action.ts +2 -1
  27. package/src/store/tool/slices/store/action.test.ts +16 -10
  28. package/src/store/tool/slices/store/action.ts +3 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.147.13](https://github.com/lobehub/lobe-chat/compare/v0.147.12...v0.147.13)
6
+
7
+ <sup>Released on **2024-04-14**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor the service with browser db invoke.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor the service with browser db invoke, closes [#2038](https://github.com/lobehub/lobe-chat/issues/2038) ([43a2791](https://github.com/lobehub/lobe-chat/commit/43a2791))
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 0.147.12](https://github.com/lobehub/lobe-chat/compare/v0.147.11...v0.147.12)
6
31
 
7
32
  <sup>Released on **2024-04-14**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.147.12",
3
+ "version": "0.147.13",
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",
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import ManifestPreviewer from '@/components/ManifestPreviewer';
10
- import { pluginService } from '@/services/plugin';
10
+ import { toolService } from '@/services/tool';
11
11
  import { useToolStore } from '@/store/tool';
12
12
  import { pluginSelectors } from '@/store/tool/selectors';
13
13
  import { PluginInstallError } from '@/types/tool/plugin';
@@ -77,7 +77,7 @@ const UrlManifestForm = memo<{ form: FormInstance; isEditMode: boolean }>(
77
77
 
78
78
  try {
79
79
  const useProxy = form.getFieldValue(proxyKey);
80
- const data = await pluginService.getPluginManifest(value, useProxy);
80
+ const data = await toolService.getPluginManifest(value, useProxy);
81
81
  setManifest(data);
82
82
 
83
83
  form.setFieldsValue({ identifier: data.identifier, manifest: data });
@@ -1,6 +1,6 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`PluginService > can parse the OpenAI plugin 1`] = `
3
+ exports[`ToolService > can parse the OpenAI plugin 1`] = `
4
4
  {
5
5
  "api": [],
6
6
  "homepage": "https://products.wolframalpha.com/api/commercial-termsofuse",
@@ -78,7 +78,7 @@ getWolframCloudResults guidelines:
78
78
  }
79
79
  `;
80
80
 
81
- exports[`PluginService > getPluginManifest > support OpenAPI manifest > should get plugin manifest 1`] = `
81
+ exports[`ToolService > getPluginManifest > support OpenAPI manifest > should get plugin manifest 1`] = `
82
82
  {
83
83
  "$schema": "../node_modules/@lobehub/chat-plugin-sdk/schema.json",
84
84
  "api": [
@@ -9,7 +9,6 @@ import { DalleManifest } from '@/tools/dalle';
9
9
  import { ChatMessage } from '@/types/message';
10
10
  import { ChatStreamPayload } from '@/types/openai/chat';
11
11
  import { LobeTool } from '@/types/tool';
12
- import { FetchSSEOptions, fetchSSE } from '@/utils/fetch';
13
12
 
14
13
  import { chatService } from '../chat';
15
14
 
@@ -1,13 +1,8 @@
1
- import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
1
  import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
3
2
 
4
- import { PluginModel } from '@/database/client/models/plugin';
5
- import { DB_Plugin } from '@/database/client/schemas/plugin';
6
3
  import { globalHelpers } from '@/store/global/helpers';
7
- import { LobeTool } from '@/types/tool';
8
- import { LobeToolCustomPlugin } from '@/types/tool/plugin';
9
4
 
10
- import { InstallPluginParams, pluginService } from '../plugin';
5
+ import { toolService } from '../tool';
11
6
  import openAPIV3 from './openai/OpenAPI_V3.json';
12
7
  import OpenAIPlugin from './openai/plugin.json';
13
8
 
@@ -18,21 +13,12 @@ vi.mock('@/store/global/helpers', () => ({
18
13
  getCurrentLanguage: vi.fn(),
19
14
  },
20
15
  }));
21
- vi.mock('@/database/client/models/plugin', () => ({
22
- PluginModel: {
23
- getList: vi.fn(),
24
- create: vi.fn(),
25
- delete: vi.fn(),
26
- update: vi.fn(),
27
- clear: vi.fn(),
28
- },
29
- }));
30
16
 
31
17
  beforeEach(() => {
32
18
  vi.resetAllMocks();
33
19
  });
34
20
 
35
- describe('PluginService', () => {
21
+ describe('ToolService', () => {
36
22
  describe('getPluginList', () => {
37
23
  it('should fetch and return the plugin list', async () => {
38
24
  // Arrange
@@ -45,7 +31,7 @@ describe('PluginService', () => {
45
31
  ) as any;
46
32
 
47
33
  // Act
48
- const pluginList = await pluginService.getPluginList();
34
+ const pluginList = await toolService.getPluginList();
49
35
 
50
36
  // Assert
51
37
  expect(globalHelpers.getCurrentLanguage).toHaveBeenCalled();
@@ -60,7 +46,7 @@ describe('PluginService', () => {
60
46
  global.fetch = vi.fn(() => Promise.reject(new Error('Network error')));
61
47
 
62
48
  // Act & Assert
63
- await expect(pluginService.getPluginList()).rejects.toThrow('Network error');
49
+ await expect(toolService.getPluginList()).rejects.toThrow('Network error');
64
50
  });
65
51
  });
66
52
 
@@ -112,7 +98,7 @@ describe('PluginService', () => {
112
98
  }),
113
99
  ) as any;
114
100
 
115
- const manifest = await pluginService.getPluginManifest(manifestUrl);
101
+ const manifest = await toolService.getPluginManifest(manifestUrl);
116
102
 
117
103
  expect(fetch).toHaveBeenCalledWith(manifestUrl);
118
104
  expect(manifest).toEqual(fakeManifest);
@@ -120,7 +106,7 @@ describe('PluginService', () => {
120
106
 
121
107
  it('should return error on noManifest', async () => {
122
108
  try {
123
- await pluginService.getPluginManifest();
109
+ await toolService.getPluginManifest();
124
110
  } catch (e) {
125
111
  expect(e).toEqual(new TypeError('noManifest'));
126
112
  }
@@ -138,7 +124,7 @@ describe('PluginService', () => {
138
124
  ) as any;
139
125
 
140
126
  try {
141
- await pluginService.getPluginManifest(manifestUrl);
127
+ await toolService.getPluginManifest(manifestUrl);
142
128
  } catch (e) {
143
129
  expect(e).toEqual(new TypeError('manifestInvalid'));
144
130
  }
@@ -149,7 +135,7 @@ describe('PluginService', () => {
149
135
  global.fetch = vi.fn(() => Promise.reject(new Error('Network error')));
150
136
 
151
137
  try {
152
- await pluginService.getPluginManifest(manifestUrl);
138
+ await toolService.getPluginManifest(manifestUrl);
153
139
  } catch (e) {
154
140
  expect(e).toEqual(new TypeError('fetchError'));
155
141
  }
@@ -170,7 +156,7 @@ describe('PluginService', () => {
170
156
  ) as any;
171
157
 
172
158
  try {
173
- await pluginService.getPluginManifest(manifestUrl);
159
+ await toolService.getPluginManifest(manifestUrl);
174
160
  } catch (e) {
175
161
  expect(e).toEqual(new TypeError('urlError'));
176
162
  }
@@ -188,7 +174,7 @@ describe('PluginService', () => {
188
174
  ) as any;
189
175
 
190
176
  try {
191
- await pluginService.getPluginManifest(manifestUrl);
177
+ await toolService.getPluginManifest(manifestUrl);
192
178
  } catch (e) {
193
179
  expect(e).toEqual(new TypeError('fetchError'));
194
180
  }
@@ -228,7 +214,7 @@ describe('PluginService', () => {
228
214
  }),
229
215
  ) as any;
230
216
 
231
- const manifest = await pluginService.getPluginManifest(manifestUrl);
217
+ const manifest = await toolService.getPluginManifest(manifestUrl);
232
218
 
233
219
  expect(manifest).toMatchSnapshot();
234
220
  });
@@ -266,7 +252,7 @@ describe('PluginService', () => {
266
252
  ) as any;
267
253
 
268
254
  try {
269
- await pluginService.getPluginManifest(manifestUrl);
255
+ await toolService.getPluginManifest(manifestUrl);
270
256
  } catch (e) {
271
257
  expect(e).toEqual(new TypeError('openAPIInvalid'));
272
258
  }
@@ -274,141 +260,8 @@ describe('PluginService', () => {
274
260
  });
275
261
  });
276
262
 
277
- describe('installPlugin', () => {
278
- it('should install a plugin', async () => {
279
- // Arrange
280
- const fakePlugin = {
281
- identifier: 'test-plugin',
282
- manifest: { name: 'TestPlugin', version: '1.0.0' } as unknown as LobeChatPluginManifest,
283
- type: 'plugin',
284
- } as InstallPluginParams;
285
- vi.mocked(PluginModel.create).mockResolvedValue(fakePlugin);
286
-
287
- // Act
288
- const installedPlugin = await pluginService.installPlugin(fakePlugin);
289
-
290
- // Assert
291
- expect(PluginModel.create).toHaveBeenCalledWith(fakePlugin);
292
- expect(installedPlugin).toEqual(fakePlugin);
293
- });
294
- });
295
-
296
- describe('getInstalledPlugins', () => {
297
- it('should return a list of installed plugins', async () => {
298
- // Arrange
299
- const fakePlugins = [{ identifier: 'test-plugin', type: 'plugin' }] as LobeTool[];
300
- vi.mocked(PluginModel.getList).mockResolvedValue(fakePlugins as DB_Plugin[]);
301
-
302
- // Act
303
- const installedPlugins = await pluginService.getInstalledPlugins();
304
-
305
- // Assert
306
- expect(PluginModel.getList).toHaveBeenCalled();
307
- expect(installedPlugins).toEqual(fakePlugins);
308
- });
309
- });
310
-
311
- describe('uninstallPlugin', () => {
312
- it('should uninstall a plugin', async () => {
313
- // Arrange
314
- const identifier = 'test-plugin';
315
- vi.mocked(PluginModel.delete).mockResolvedValue();
316
-
317
- // Act
318
- const result = await pluginService.uninstallPlugin(identifier);
319
-
320
- // Assert
321
- expect(PluginModel.delete).toHaveBeenCalledWith(identifier);
322
- expect(result).toBe(undefined);
323
- });
324
- });
325
-
326
- describe('createCustomPlugin', () => {
327
- it('should create a custom plugin', async () => {
328
- // Arrange
329
- const customPlugin = {
330
- identifier: 'custom-plugin',
331
- manifest: {},
332
- type: 'customPlugin',
333
- } as LobeToolCustomPlugin;
334
- vi.mocked(PluginModel.create).mockResolvedValue(customPlugin);
335
-
336
- // Act
337
- const result = await pluginService.createCustomPlugin(customPlugin);
338
-
339
- // Assert
340
- expect(PluginModel.create).toHaveBeenCalledWith({
341
- ...customPlugin,
342
- type: 'customPlugin',
343
- });
344
- expect(result).toEqual(customPlugin);
345
- });
346
- });
347
-
348
- describe('updatePlugin', () => {
349
- it('should update a plugin', async () => {
350
- // Arrange
351
- const id = 'plugin-id';
352
- const value = { settings: { ab: '1' } } as unknown as LobeToolCustomPlugin;
353
- vi.mocked(PluginModel.update).mockResolvedValue(1);
354
-
355
- // Act
356
- const result = await pluginService.updatePlugin(id, value);
357
-
358
- // Assert
359
- expect(PluginModel.update).toHaveBeenCalledWith(id, value);
360
- expect(result).toEqual(1);
361
- });
362
- });
363
-
364
- describe('updatePluginManifest', () => {
365
- it('should update a plugin manifest', async () => {
366
- // Arrange
367
- const id = 'plugin-id';
368
- const manifest = { name: 'NewPluginManifest' } as unknown as LobeChatPluginManifest;
369
- vi.mocked(PluginModel.update).mockResolvedValue(1);
370
-
371
- // Act
372
- const result = await pluginService.updatePluginManifest(id, manifest);
373
-
374
- // Assert
375
- expect(PluginModel.update).toHaveBeenCalledWith(id, { manifest });
376
- expect(result).toEqual(1);
377
- });
378
- });
379
-
380
- describe('removeAllPlugins', () => {
381
- it('should remove all plugins', async () => {
382
- // Arrange
383
- vi.mocked(PluginModel.clear).mockResolvedValue(undefined);
384
-
385
- // Act
386
- const result = await pluginService.removeAllPlugins();
387
-
388
- // Assert
389
- expect(PluginModel.clear).toHaveBeenCalled();
390
- expect(result).toBe(undefined);
391
- });
392
- });
393
-
394
- describe('updatePluginSettings', () => {
395
- it('should update plugin settings', async () => {
396
- // Arrange
397
- const id = 'plugin-id';
398
- const settings = { color: 'blue' };
399
- vi.mocked(PluginModel.update).mockResolvedValue(1);
400
-
401
- // Act
402
- const result = await pluginService.updatePluginSettings(id, settings);
403
-
404
- // Assert
405
- expect(PluginModel.update).toHaveBeenCalledWith(id, { settings });
406
- expect(result).toEqual(1);
407
- });
408
- });
409
-
410
263
  it('can parse the OpenAI plugin', async () => {
411
- const manifest = pluginService['convertOpenAIManifestToLobeManifest'](OpenAIPlugin as any);
264
+ const manifest = toolService['convertOpenAIManifestToLobeManifest'](OpenAIPlugin as any);
412
265
 
413
266
  expect(manifest).toMatchSnapshot();
414
267
  });
@@ -3,9 +3,9 @@ import { DB_File } from '@/database/client/schemas/files';
3
3
  import { FilePreview } from '@/types/files';
4
4
  import compressImage from '@/utils/compressImage';
5
5
 
6
- import { API_ENDPOINTS } from './_url';
6
+ import { API_ENDPOINTS } from '../_url';
7
7
 
8
- class FileService {
8
+ export class FileService {
9
9
  private isImage(fileType: string) {
10
10
  const imageRegex = /^image\//;
11
11
  return imageRegex.test(fileType);
@@ -84,5 +84,3 @@ class FileService {
84
84
  };
85
85
  }
86
86
  }
87
-
88
- export const fileService = new FileService();
@@ -3,7 +3,9 @@ import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { FileModel } from '@/database/client/models/file';
4
4
  import { DB_File } from '@/database/client/schemas/files';
5
5
 
6
- import { fileService } from '../file';
6
+ import { FileService } from './client';
7
+
8
+ const fileService = new FileService();
7
9
 
8
10
  // Mocks for the FileModel
9
11
  vi.mock('@/database/client/models/file', () => ({
@@ -0,0 +1,3 @@
1
+ import { FileService } from './client';
2
+
3
+ export const fileService = new FileService();
@@ -1,15 +1,11 @@
1
1
  import { Mock, describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  import { CreateMessageParams, MessageModel } from '@/database/client/models/message';
4
- import {
5
- ChatMessage,
6
- ChatMessageError,
7
- ChatPluginPayload,
8
- ChatTTS,
9
- ChatTranslate,
10
- } from '@/types/message';
11
-
12
- import { messageService } from '../message';
4
+ import { ChatMessage, ChatMessageError, ChatPluginPayload } from '@/types/message';
5
+
6
+ import { MessageService } from './client';
7
+
8
+ const messageService = new MessageService();
13
9
 
14
10
  // Mock the MessageModel
15
11
  vi.mock('@/database/client/models/message', () => {
@@ -76,5 +76,3 @@ export class MessageService {
76
76
  return MessageModel.queryAll();
77
77
  }
78
78
  }
79
-
80
- export const messageService = new MessageService();
@@ -0,0 +1,4 @@
1
+ import { MessageService } from './client';
2
+
3
+ export const messageService = new MessageService();
4
+ export type { CreateMessageParams } from './client';
@@ -0,0 +1,44 @@
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
+
3
+ import { PluginModel } from '@/database/client/models/plugin';
4
+ import { LobeTool } from '@/types/tool';
5
+ import { LobeToolCustomPlugin } from '@/types/tool/plugin';
6
+
7
+ export interface InstallPluginParams {
8
+ identifier: string;
9
+ manifest: LobeChatPluginManifest;
10
+ type: 'plugin' | 'customPlugin';
11
+ }
12
+
13
+ export class PluginService {
14
+ installPlugin = async (plugin: InstallPluginParams) => {
15
+ return PluginModel.create(plugin);
16
+ };
17
+
18
+ getInstalledPlugins = () => {
19
+ return PluginModel.getList() as Promise<LobeTool[]>;
20
+ };
21
+
22
+ uninstallPlugin(identifier: string) {
23
+ return PluginModel.delete(identifier);
24
+ }
25
+
26
+ async createCustomPlugin(customPlugin: LobeToolCustomPlugin) {
27
+ return PluginModel.create({ ...customPlugin, type: 'customPlugin' });
28
+ }
29
+
30
+ async updatePlugin(id: string, value: LobeToolCustomPlugin) {
31
+ return PluginModel.update(id, value);
32
+ }
33
+ async updatePluginManifest(id: string, manifest: LobeChatPluginManifest) {
34
+ return PluginModel.update(id, { manifest });
35
+ }
36
+
37
+ async removeAllPlugins() {
38
+ return PluginModel.clear();
39
+ }
40
+
41
+ async updatePluginSettings(id: string, settings: any) {
42
+ return PluginModel.update(id, { settings });
43
+ }
44
+ }
@@ -0,0 +1,5 @@
1
+ import { PluginService } from './client';
2
+
3
+ export type { InstallPluginParams } from './client';
4
+
5
+ export const pluginService = new PluginService();
@@ -0,0 +1,162 @@
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { PluginModel } from '@/database/client/models/plugin';
5
+ import { DB_Plugin } from '@/database/client/schemas/plugin';
6
+ import { LobeTool } from '@/types/tool';
7
+ import { LobeToolCustomPlugin } from '@/types/tool/plugin';
8
+
9
+ import { InstallPluginParams, PluginService } from './client';
10
+
11
+ const pluginService = new PluginService();
12
+
13
+ // Mocking modules and functions
14
+
15
+ vi.mock('@/database/client/models/plugin', () => ({
16
+ PluginModel: {
17
+ getList: vi.fn(),
18
+ create: vi.fn(),
19
+ delete: vi.fn(),
20
+ update: vi.fn(),
21
+ clear: vi.fn(),
22
+ },
23
+ }));
24
+
25
+ beforeEach(() => {
26
+ vi.resetAllMocks();
27
+ });
28
+
29
+ describe('PluginService', () => {
30
+ describe('installPlugin', () => {
31
+ it('should install a plugin', async () => {
32
+ // Arrange
33
+ const fakePlugin = {
34
+ identifier: 'test-plugin',
35
+ manifest: { name: 'TestPlugin', version: '1.0.0' } as unknown as LobeChatPluginManifest,
36
+ type: 'plugin',
37
+ } as InstallPluginParams;
38
+ vi.mocked(PluginModel.create).mockResolvedValue(fakePlugin);
39
+
40
+ // Act
41
+ const installedPlugin = await pluginService.installPlugin(fakePlugin);
42
+
43
+ // Assert
44
+ expect(PluginModel.create).toHaveBeenCalledWith(fakePlugin);
45
+ expect(installedPlugin).toEqual(fakePlugin);
46
+ });
47
+ });
48
+
49
+ describe('getInstalledPlugins', () => {
50
+ it('should return a list of installed plugins', async () => {
51
+ // Arrange
52
+ const fakePlugins = [{ identifier: 'test-plugin', type: 'plugin' }] as LobeTool[];
53
+ vi.mocked(PluginModel.getList).mockResolvedValue(fakePlugins as DB_Plugin[]);
54
+
55
+ // Act
56
+ const installedPlugins = await pluginService.getInstalledPlugins();
57
+
58
+ // Assert
59
+ expect(PluginModel.getList).toHaveBeenCalled();
60
+ expect(installedPlugins).toEqual(fakePlugins);
61
+ });
62
+ });
63
+
64
+ describe('uninstallPlugin', () => {
65
+ it('should uninstall a plugin', async () => {
66
+ // Arrange
67
+ const identifier = 'test-plugin';
68
+ vi.mocked(PluginModel.delete).mockResolvedValue();
69
+
70
+ // Act
71
+ const result = await pluginService.uninstallPlugin(identifier);
72
+
73
+ // Assert
74
+ expect(PluginModel.delete).toHaveBeenCalledWith(identifier);
75
+ expect(result).toBe(undefined);
76
+ });
77
+ });
78
+
79
+ describe('createCustomPlugin', () => {
80
+ it('should create a custom plugin', async () => {
81
+ // Arrange
82
+ const customPlugin = {
83
+ identifier: 'custom-plugin',
84
+ manifest: {},
85
+ type: 'customPlugin',
86
+ } as LobeToolCustomPlugin;
87
+ vi.mocked(PluginModel.create).mockResolvedValue(customPlugin);
88
+
89
+ // Act
90
+ const result = await pluginService.createCustomPlugin(customPlugin);
91
+
92
+ // Assert
93
+ expect(PluginModel.create).toHaveBeenCalledWith({
94
+ ...customPlugin,
95
+ type: 'customPlugin',
96
+ });
97
+ expect(result).toEqual(customPlugin);
98
+ });
99
+ });
100
+
101
+ describe('updatePlugin', () => {
102
+ it('should update a plugin', async () => {
103
+ // Arrange
104
+ const id = 'plugin-id';
105
+ const value = { settings: { ab: '1' } } as unknown as LobeToolCustomPlugin;
106
+ vi.mocked(PluginModel.update).mockResolvedValue(1);
107
+
108
+ // Act
109
+ const result = await pluginService.updatePlugin(id, value);
110
+
111
+ // Assert
112
+ expect(PluginModel.update).toHaveBeenCalledWith(id, value);
113
+ expect(result).toEqual(1);
114
+ });
115
+ });
116
+
117
+ describe('updatePluginManifest', () => {
118
+ it('should update a plugin manifest', async () => {
119
+ // Arrange
120
+ const id = 'plugin-id';
121
+ const manifest = { name: 'NewPluginManifest' } as unknown as LobeChatPluginManifest;
122
+ vi.mocked(PluginModel.update).mockResolvedValue(1);
123
+
124
+ // Act
125
+ const result = await pluginService.updatePluginManifest(id, manifest);
126
+
127
+ // Assert
128
+ expect(PluginModel.update).toHaveBeenCalledWith(id, { manifest });
129
+ expect(result).toEqual(1);
130
+ });
131
+ });
132
+
133
+ describe('removeAllPlugins', () => {
134
+ it('should remove all plugins', async () => {
135
+ // Arrange
136
+ vi.mocked(PluginModel.clear).mockResolvedValue(undefined);
137
+
138
+ // Act
139
+ const result = await pluginService.removeAllPlugins();
140
+
141
+ // Assert
142
+ expect(PluginModel.clear).toHaveBeenCalled();
143
+ expect(result).toBe(undefined);
144
+ });
145
+ });
146
+
147
+ describe('updatePluginSettings', () => {
148
+ it('should update plugin settings', async () => {
149
+ // Arrange
150
+ const id = 'plugin-id';
151
+ const settings = { color: 'blue' };
152
+ vi.mocked(PluginModel.update).mockResolvedValue(1);
153
+
154
+ // Act
155
+ const result = await pluginService.updatePluginSettings(id, settings);
156
+
157
+ // Assert
158
+ expect(PluginModel.update).toHaveBeenCalledWith(id, { settings });
159
+ expect(result).toEqual(1);
160
+ });
161
+ });
162
+ });
@@ -14,7 +14,7 @@ import {
14
14
  SessionGroups,
15
15
  } from '@/types/session';
16
16
 
17
- class SessionService {
17
+ export class SessionService {
18
18
  async createNewSession(
19
19
  type: LobeSessionType,
20
20
  defaultValue: Partial<LobeAgentSession>,
@@ -118,5 +118,3 @@ class SessionService {
118
118
  return SessionGroupModel.clear();
119
119
  }
120
120
  }
121
-
122
- export const sessionService = new SessionService();
@@ -0,0 +1,3 @@
1
+ import { SessionService } from './client';
2
+
3
+ export const sessionService = new SessionService();
@@ -5,7 +5,9 @@ import { SessionGroupModel } from '@/database/client/models/sessionGroup';
5
5
  import { LobeAgentConfig } from '@/types/agent';
6
6
  import { LobeAgentSession, LobeSessionType, SessionGroups } from '@/types/session';
7
7
 
8
- import { sessionService } from '../session';
8
+ import { SessionService } from './client';
9
+
10
+ const sessionService = new SessionService();
9
11
 
10
12
  // Mock the SessionModel
11
13
  vi.mock('@/database/client/models/session', () => {
@@ -4,20 +4,12 @@ import {
4
4
  pluginManifestSchema,
5
5
  } from '@lobehub/chat-plugin-sdk';
6
6
 
7
- import { PluginModel } from '@/database/client/models/plugin';
8
7
  import { globalHelpers } from '@/store/global/helpers';
9
8
  import { OpenAIPluginManifest } from '@/types/openai/plugin';
10
- import { LobeTool } from '@/types/tool';
11
- import { LobeToolCustomPlugin } from '@/types/tool/plugin';
12
9
 
13
10
  import { API_ENDPOINTS } from './_url';
14
11
 
15
- export interface InstallPluginParams {
16
- identifier: string;
17
- manifest: LobeChatPluginManifest;
18
- type: 'plugin' | 'customPlugin';
19
- }
20
- class PluginService {
12
+ class ToolService {
21
13
  private _fetchJSON = async <T = any>(url: string, proxy = false): Promise<T> => {
22
14
  // 2. 发送请求
23
15
  let res: Response;
@@ -107,37 +99,6 @@ class PluginService {
107
99
  return data;
108
100
  };
109
101
 
110
- installPlugin = async (plugin: InstallPluginParams) => {
111
- return PluginModel.create(plugin);
112
- };
113
-
114
- getInstalledPlugins = () => {
115
- return PluginModel.getList() as Promise<LobeTool[]>;
116
- };
117
-
118
- uninstallPlugin(identifier: string) {
119
- return PluginModel.delete(identifier);
120
- }
121
-
122
- async createCustomPlugin(customPlugin: LobeToolCustomPlugin) {
123
- return PluginModel.create({ ...customPlugin, type: 'customPlugin' });
124
- }
125
-
126
- async updatePlugin(id: string, value: LobeToolCustomPlugin) {
127
- return PluginModel.update(id, value);
128
- }
129
- async updatePluginManifest(id: string, manifest: LobeChatPluginManifest) {
130
- return PluginModel.update(id, { manifest });
131
- }
132
-
133
- async removeAllPlugins() {
134
- return PluginModel.clear();
135
- }
136
-
137
- async updatePluginSettings(id: string, settings: any) {
138
- return PluginModel.update(id, { settings });
139
- }
140
-
141
102
  private convertOpenAIManifestToLobeManifest = (
142
103
  data: OpenAIPluginManifest,
143
104
  ): LobeChatPluginManifest => {
@@ -180,4 +141,4 @@ class PluginService {
180
141
  };
181
142
  }
182
143
 
183
- export const pluginService = new PluginService();
144
+ export const toolService = new ToolService();
@@ -1,7 +1,7 @@
1
1
  import { CreateTopicParams, QueryTopicParams, TopicModel } from '@/database/client/models/topic';
2
2
  import { ChatTopic } from '@/types/topic';
3
3
 
4
- class TopicService {
4
+ export class TopicService {
5
5
  async createTopic(params: CreateTopicParams): Promise<string> {
6
6
  const item = await TopicModel.create(params);
7
7
 
@@ -56,5 +56,3 @@ class TopicService {
56
56
  return TopicModel.duplicateTopic(id, newTitle);
57
57
  }
58
58
  }
59
-
60
- export const topicService = new TopicService();
@@ -0,0 +1,3 @@
1
+ import { TopicService } from './client';
2
+
3
+ export const topicService = new TopicService();
@@ -3,8 +3,9 @@ import { Mock, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { CreateTopicParams, TopicModel } from '@/database/client/models/topic';
4
4
  import { ChatTopic } from '@/types/topic';
5
5
 
6
- import { topicService } from '../topic';
6
+ import { TopicService } from './client';
7
7
 
8
+ const topicService = new TopicService();
8
9
  // Mock the TopicModel
9
10
  vi.mock('@/database/client/models/topic', () => {
10
11
  return {
@@ -9,7 +9,7 @@ export interface UserConfig {
9
9
  uuid: string;
10
10
  }
11
11
 
12
- class UserService {
12
+ export class UserService {
13
13
  getUserConfig = async () => {
14
14
  const user = await UserModel.getUser();
15
15
  return user as unknown as UserConfig;
@@ -27,5 +27,3 @@ class UserService {
27
27
  return UserModel.updateAvatar(avatar);
28
28
  }
29
29
  }
30
-
31
- export const userService = new UserService();
@@ -0,0 +1,5 @@
1
+ import { UserService } from './client';
2
+
3
+ export type { UserConfig } from './client';
4
+
5
+ export const userService = new UserService();
@@ -12,13 +12,19 @@ beforeEach(() => {
12
12
  });
13
13
  vi.mock('@/services/plugin', () => ({
14
14
  pluginService: {
15
- getPluginManifest: vi.fn(),
16
15
  updatePlugin: vi.fn(),
17
16
  createCustomPlugin: vi.fn(),
18
17
  uninstallPlugin: vi.fn(),
19
18
  updatePluginManifest: vi.fn(),
20
19
  },
21
20
  }));
21
+
22
+ vi.mock('@/services/tool', () => ({
23
+ toolService: {
24
+ getPluginManifest: vi.fn(),
25
+ },
26
+ }));
27
+
22
28
  describe('useToolStore:customPlugin', () => {
23
29
  describe('deleteCustomPlugin', () => {
24
30
  it('should delete custom plugin and related settings', async () => {
@@ -4,6 +4,7 @@ import { StateCreator } from 'zustand/vanilla';
4
4
 
5
5
  import { notification } from '@/components/AntdStaticMethods';
6
6
  import { pluginService } from '@/services/plugin';
7
+ import { toolService } from '@/services/tool';
7
8
  import { pluginHelpers } from '@/store/tool/helpers';
8
9
  import { LobeToolCustomPlugin, PluginInstallError } from '@/types/tool/plugin';
9
10
  import { setNamespace } from '@/utils/storeDebug';
@@ -41,7 +42,7 @@ export const createCustomPluginSlice: StateCreator<
41
42
  const { refreshPlugins, updateInstallLoadingState } = get();
42
43
  try {
43
44
  updateInstallLoadingState(id, true);
44
- const manifest = await pluginService.getPluginManifest(
45
+ const manifest = await toolService.getPluginManifest(
45
46
  plugin.customParams?.manifestUrl,
46
47
  plugin.customParams?.useProxy,
47
48
  );
@@ -5,6 +5,7 @@ import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
 
6
6
  import { notification } from '@/components/AntdStaticMethods';
7
7
  import { pluginService } from '@/services/plugin';
8
+ import { toolService } from '@/services/tool';
8
9
 
9
10
  import { useToolStore } from '../../store';
10
11
 
@@ -17,13 +18,18 @@ vi.mock('@/components/AntdStaticMethods', () => ({
17
18
  // Mock the pluginService.getPluginList method
18
19
  vi.mock('@/services/plugin', () => ({
19
20
  pluginService: {
20
- getPluginManifest: vi.fn(),
21
- getPluginList: vi.fn(),
22
21
  uninstallPlugin: vi.fn(),
23
22
  installPlugin: vi.fn(),
24
23
  },
25
24
  }));
26
25
 
26
+ vi.mock('@/services/tool', () => ({
27
+ toolService: {
28
+ getPluginManifest: vi.fn(),
29
+ getPluginList: vi.fn(),
30
+ },
31
+ }));
32
+
27
33
  // Mock i18next
28
34
  vi.mock('i18next', () => ({
29
35
  t: vi.fn((key) => key),
@@ -97,7 +103,7 @@ describe('useToolStore:pluginStore', () => {
97
103
  it('should load plugin list and update state', async () => {
98
104
  // Given
99
105
  const pluginListMock = { plugins: [{ identifier: 'plugin1' }, { identifier: 'plugin2' }] };
100
- (pluginService.getPluginList as Mock).mockResolvedValue(pluginListMock);
106
+ (toolService.getPluginList as Mock).mockResolvedValue(pluginListMock);
101
107
 
102
108
  // When
103
109
  let pluginList;
@@ -106,7 +112,7 @@ describe('useToolStore:pluginStore', () => {
106
112
  });
107
113
 
108
114
  // Then
109
- expect(pluginService.getPluginList).toHaveBeenCalled();
115
+ expect(toolService.getPluginList).toHaveBeenCalled();
110
116
  expect(pluginList).toEqual(pluginListMock);
111
117
  expect(useToolStore.getState().pluginStoreList).toEqual(pluginListMock.plugins);
112
118
  });
@@ -114,7 +120,7 @@ describe('useToolStore:pluginStore', () => {
114
120
  it('should handle errors when loading plugin list', async () => {
115
121
  // Given
116
122
  const error = new Error('Failed to load plugin list');
117
- (pluginService.getPluginList as Mock).mockRejectedValue(error);
123
+ (toolService.getPluginList as Mock).mockRejectedValue(error);
118
124
 
119
125
  // When
120
126
  let pluginList;
@@ -128,7 +134,7 @@ describe('useToolStore:pluginStore', () => {
128
134
  }
129
135
 
130
136
  // Then
131
- expect(pluginService.getPluginList).toHaveBeenCalled();
137
+ expect(toolService.getPluginList).toHaveBeenCalled();
132
138
  expect(errorOccurred).toBe(true);
133
139
  expect(pluginList).toBeUndefined();
134
140
  // Ensure the state is not updated with an undefined value
@@ -224,14 +230,14 @@ describe('useToolStore:pluginStore', () => {
224
230
  },
225
231
  version: '1',
226
232
  };
227
- (pluginService.getPluginManifest as Mock).mockResolvedValue(pluginManifestMock);
233
+ (toolService.getPluginManifest as Mock).mockResolvedValue(pluginManifestMock);
228
234
 
229
235
  await act(async () => {
230
236
  await useToolStore.getState().installPlugin(pluginIdentifier);
231
237
  });
232
238
 
233
239
  // Then
234
- expect(pluginService.getPluginManifest).toHaveBeenCalled();
240
+ expect(toolService.getPluginManifest).toHaveBeenCalled();
235
241
  expect(notification.error).not.toHaveBeenCalled();
236
242
  expect(updateInstallLoadingStateMock).toHaveBeenCalledTimes(2);
237
243
  expect(pluginService.installPlugin).toHaveBeenCalledWith({
@@ -253,7 +259,7 @@ describe('useToolStore:pluginStore', () => {
253
259
  const error = new TypeError('noManifest');
254
260
 
255
261
  // Mock necessary modules and functions
256
- (pluginService.getPluginManifest as Mock).mockRejectedValue(error);
262
+ (toolService.getPluginManifest as Mock).mockRejectedValue(error);
257
263
 
258
264
  useToolStore.setState({
259
265
  pluginStoreList: [
@@ -297,7 +303,7 @@ describe('useToolStore:pluginStore', () => {
297
303
 
298
304
  const plugins = ['plugin1', 'plugin2'];
299
305
 
300
- (pluginService.getPluginManifest as Mock).mockResolvedValue(pluginManifestMock);
306
+ (toolService.getPluginManifest as Mock).mockResolvedValue(pluginManifestMock);
301
307
 
302
308
  // When
303
309
  await act(async () => {
@@ -6,6 +6,7 @@ import { StateCreator } from 'zustand/vanilla';
6
6
 
7
7
  import { notification } from '@/components/AntdStaticMethods';
8
8
  import { pluginService } from '@/services/plugin';
9
+ import { toolService } from '@/services/tool';
9
10
  import { pluginStoreSelectors } from '@/store/tool/selectors';
10
11
  import { LobeTool } from '@/types/tool';
11
12
  import { PluginInstallError } from '@/types/tool/plugin';
@@ -43,7 +44,7 @@ export const createPluginStoreSlice: StateCreator<
43
44
  const { updateInstallLoadingState, refreshPlugins } = get();
44
45
  try {
45
46
  updateInstallLoadingState(name, true);
46
- const data = await pluginService.getPluginManifest(plugin.manifest);
47
+ const data = await toolService.getPluginManifest(plugin.manifest);
47
48
  updateInstallLoadingState(name, undefined);
48
49
 
49
50
  // 4. 存储 manifest 信息
@@ -66,7 +67,7 @@ export const createPluginStoreSlice: StateCreator<
66
67
  await Promise.all(plugins.map((identifier) => installPlugin(identifier)));
67
68
  },
68
69
  loadPluginStore: async () => {
69
- const pluginMarketIndex = await pluginService.getPluginList();
70
+ const pluginMarketIndex = await toolService.getPluginList();
70
71
 
71
72
  set({ pluginStoreList: pluginMarketIndex.plugins }, false, n('loadPluginList'));
72
73