@lobehub/chat 0.108.0 → 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.
Files changed (93) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/docs/Environment-Variable.md +2 -2
  5. package/docs/Environment-Variable.zh-CN.md +2 -2
  6. package/locales/en_US/plugin.json +19 -0
  7. package/locales/en_US/setting.json +26 -0
  8. package/locales/ja_JP/plugin.json +19 -0
  9. package/locales/ja_JP/setting.json +26 -0
  10. package/locales/ko_KR/plugin.json +19 -0
  11. package/locales/ko_KR/setting.json +26 -0
  12. package/locales/ru_RU/plugin.json +19 -0
  13. package/locales/ru_RU/setting.json +26 -0
  14. package/locales/zh_CN/plugin.json +19 -0
  15. package/locales/zh_CN/setting.json +26 -5
  16. package/locales/zh_TW/plugin.json +19 -0
  17. package/locales/zh_TW/setting.json +26 -0
  18. package/package.json +3 -3
  19. package/src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx +6 -6
  20. package/src/app/chat/features/ChatHeader/PluginTag/index.tsx +4 -4
  21. package/src/app/chat/features/ChatInput/ActionBar/Tools/ToolItem.tsx +37 -0
  22. package/src/app/chat/features/ChatInput/ActionBar/Tools/index.tsx +116 -0
  23. package/src/app/chat/features/ChatInput/ActionBar/config.ts +3 -1
  24. package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +4 -4
  25. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +3 -3
  26. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +4 -4
  27. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +2 -2
  28. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +4 -4
  29. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/index.tsx +2 -2
  30. package/src/app/chat/features/Conversation/useInitConversation.ts +2 -2
  31. package/src/app/settings/common/Common.tsx +2 -2
  32. package/src/features/AgentSetting/AgentPlugin/AddPluginButton.tsx +56 -0
  33. package/src/features/AgentSetting/AgentPlugin/LoadingList.tsx +48 -0
  34. package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +5 -5
  35. package/src/features/AgentSetting/AgentPlugin/PluginAction/index.tsx +36 -0
  36. package/src/features/AgentSetting/AgentPlugin/index.tsx +157 -9
  37. package/src/features/AgentSetting/store/action.ts +0 -1
  38. package/src/features/PluginDevModal/MetaForm.tsx +3 -3
  39. package/src/features/PluginDevModal/PluginPreview.tsx +1 -1
  40. package/src/features/PluginDevModal/index.tsx +4 -1
  41. package/src/features/PluginSettings/index.tsx +3 -3
  42. package/src/features/PluginSettingsModal/index.tsx +22 -17
  43. package/src/features/PluginStore/Loading.tsx +13 -0
  44. package/src/features/PluginStore/PluginItem.tsx +81 -0
  45. package/src/features/PluginStore/PluginSettings.tsx +40 -0
  46. package/src/features/{AgentSetting/AgentPlugin/MarketSettingModal.tsx → PluginStore/StoreSetting.tsx} +2 -2
  47. package/src/features/PluginStore/index.tsx +95 -0
  48. package/src/layout/GlobalLayout/StoreHydration.tsx +3 -3
  49. package/src/locales/default/plugin.ts +19 -0
  50. package/src/locales/default/setting.ts +26 -5
  51. package/src/services/__tests__/chat.test.ts +221 -0
  52. package/src/services/__tests__/message.test.ts +333 -0
  53. package/src/services/__tests__/plugin.test.ts +88 -0
  54. package/src/services/__tests__/session.test.ts +227 -0
  55. package/src/services/__tests__/topic.test.ts +220 -0
  56. package/src/services/chat.ts +4 -6
  57. package/src/services/plugin.ts +12 -1
  58. package/src/store/chat/actions/message.test.ts +0 -1
  59. package/src/store/session/slices/agent/action.ts +24 -8
  60. package/src/store/{plugin → tool}/helpers.ts +2 -13
  61. package/src/store/tool/index.ts +2 -0
  62. package/src/store/tool/initialState.ts +11 -0
  63. package/src/store/tool/selectors.ts +3 -0
  64. package/src/store/tool/slices/customPlugin/action.test.ts +164 -0
  65. package/src/store/{plugin → tool}/slices/customPlugin/action.ts +2 -2
  66. package/src/store/tool/slices/customPlugin/selectors.test.ts +59 -0
  67. package/src/store/tool/slices/customPlugin/selectors.ts +9 -0
  68. package/src/store/tool/slices/plugin/action.test.ts +141 -0
  69. package/src/store/tool/slices/plugin/action.ts +95 -0
  70. package/src/store/{plugin → tool}/slices/plugin/initialState.ts +0 -7
  71. package/src/store/{plugin → tool/slices/plugin}/selectors.test.ts +4 -41
  72. package/src/store/{plugin → tool/slices/plugin}/selectors.ts +17 -19
  73. package/src/store/tool/slices/store/action.test.ts +396 -0
  74. package/src/store/tool/slices/store/action.ts +103 -0
  75. package/src/store/tool/slices/store/index.ts +3 -0
  76. package/src/store/tool/slices/store/initialState.ts +17 -0
  77. package/src/store/tool/slices/store/selectors.test.ts +48 -0
  78. package/src/store/tool/slices/store/selectors.ts +21 -0
  79. package/src/store/{plugin → tool}/store.ts +8 -6
  80. package/vitest.config.ts +1 -0
  81. package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +0 -232
  82. package/src/features/AgentSetting/AgentPlugin/PluginSettings.tsx +0 -80
  83. package/src/store/chat/reducers/files.test.ts +0 -38
  84. package/src/store/chat/reducers/files.ts +0 -37
  85. package/src/store/plugin/hooks/useSessionHydrated.ts +0 -21
  86. package/src/store/plugin/index.ts +0 -2
  87. package/src/store/plugin/initialState.ts +0 -9
  88. package/src/store/plugin/slices/plugin/action.ts +0 -173
  89. /package/src/store/{plugin → tool}/slices/customPlugin/index.ts +0 -0
  90. /package/src/store/{plugin → tool}/slices/customPlugin/initialState.ts +0 -0
  91. /package/src/store/{plugin → tool}/slices/customPlugin/reducers/customPluginList.ts +0 -0
  92. /package/src/store/{plugin → tool}/slices/plugin/index.ts +0 -0
  93. /package/src/store/{plugin → tool}/slices/plugin/reducers/manifest.ts +0 -0
@@ -0,0 +1,227 @@
1
+ import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { SessionModel } from '@/database/models/session';
4
+ import { LobeAgentConfig } from '@/types/agent';
5
+ import { LobeAgentSession, LobeSessionType } from '@/types/session';
6
+
7
+ import { sessionService } from '../session';
8
+
9
+ // 请确保路径与你的项目结构匹配
10
+
11
+ // Mock the SessionModel
12
+ vi.mock('@/database/models/session', () => {
13
+ return {
14
+ SessionModel: {
15
+ create: vi.fn(),
16
+ query: vi.fn(),
17
+ delete: vi.fn(),
18
+ clearTable: vi.fn(),
19
+ update: vi.fn(),
20
+ batchCreate: vi.fn(),
21
+ isEmpty: vi.fn(),
22
+ queryByKeyword: vi.fn(),
23
+ updateConfig: vi.fn(),
24
+ },
25
+ };
26
+ });
27
+
28
+ describe('SessionService', () => {
29
+ const mockSessionId = 'mock-session-id';
30
+ const mockSession = {
31
+ id: mockSessionId,
32
+ type: 'agent',
33
+ meta: { title: 'Mock Session' },
34
+ } as LobeAgentSession;
35
+ const mockSessions = [mockSession];
36
+
37
+ beforeEach(() => {
38
+ // Reset all mocks before running each test case
39
+ vi.resetAllMocks();
40
+ });
41
+
42
+ describe('createNewSession', () => {
43
+ it('should create a new session and return its id', async () => {
44
+ // Setup
45
+ const sessionType = LobeSessionType.Agent;
46
+ const defaultValue = { meta: { title: 'New Session' } } as Partial<LobeAgentSession>;
47
+ (SessionModel.create as Mock).mockResolvedValue(mockSession);
48
+
49
+ // Execute
50
+ const sessionId = await sessionService.createNewSession(sessionType, defaultValue);
51
+
52
+ // Assert
53
+ expect(SessionModel.create).toHaveBeenCalledWith(sessionType, defaultValue);
54
+ expect(sessionId).toBe(mockSessionId);
55
+ });
56
+
57
+ it('should throw an error if session creation fails', async () => {
58
+ // Setup
59
+ const sessionType = LobeSessionType.Agent;
60
+ const defaultValue = { meta: { title: 'New Session' } } as Partial<LobeAgentSession>;
61
+ (SessionModel.create as Mock).mockResolvedValue(null);
62
+
63
+ // Execute & Assert
64
+ await expect(sessionService.createNewSession(sessionType, defaultValue)).rejects.toThrow(
65
+ 'session create Error',
66
+ );
67
+ });
68
+ });
69
+ // ... (前面的测试代码)
70
+
71
+ describe('batchCreateSessions', () => {
72
+ it('should batch create sessions', async () => {
73
+ // Setup
74
+ (SessionModel.batchCreate as Mock).mockResolvedValue(mockSessions);
75
+
76
+ // Execute
77
+ const result = await sessionService.batchCreateSessions(mockSessions);
78
+
79
+ // Assert
80
+ expect(SessionModel.batchCreate).toHaveBeenCalledWith(mockSessions);
81
+ expect(result).toBe(mockSessions);
82
+ });
83
+ });
84
+ describe('getSessions', () => {
85
+ it('should retrieve sessions', async () => {
86
+ // Setup
87
+ (SessionModel.query as Mock).mockResolvedValue(mockSessions);
88
+
89
+ // Execute
90
+ const sessions = await sessionService.getSessions();
91
+
92
+ // Assert
93
+ expect(SessionModel.query).toHaveBeenCalled();
94
+ expect(sessions).toBe(mockSessions);
95
+ });
96
+ });
97
+
98
+ describe('getAllAgents', () => {
99
+ it('should retrieve all agent sessions', async () => {
100
+ // Setup
101
+ // Assuming that SessionModel.query has been modified to accept filters
102
+ const agentSessions = mockSessions.filter((session) => session.type === 'agent');
103
+ (SessionModel.query as Mock).mockResolvedValue(agentSessions);
104
+
105
+ // Execute
106
+ const result = await sessionService.getAllAgents();
107
+
108
+ // Assert
109
+ // Assuming that SessionModel.query would be called with a filter for agents
110
+ expect(SessionModel.query).toHaveBeenCalled(); // Add filter argument if applicable
111
+ expect(result).toBe(agentSessions);
112
+ });
113
+ });
114
+
115
+ describe('removeSession', () => {
116
+ it('should remove a session by its id', async () => {
117
+ // Setup
118
+ (SessionModel.delete as Mock).mockResolvedValue(true);
119
+
120
+ // Execute
121
+ const result = await sessionService.removeSession(mockSessionId);
122
+
123
+ // Assert
124
+ expect(SessionModel.delete).toHaveBeenCalledWith(mockSessionId);
125
+ expect(result).toBe(true);
126
+ });
127
+ });
128
+
129
+ describe('removeAllSessions', () => {
130
+ it('should clear all sessions from the table', async () => {
131
+ // Setup
132
+ (SessionModel.clearTable as Mock).mockResolvedValue(true);
133
+
134
+ // Execute
135
+ const result = await sessionService.removeAllSessions();
136
+
137
+ // Assert
138
+ expect(SessionModel.clearTable).toHaveBeenCalled();
139
+ expect(result).toBe(true);
140
+ });
141
+ });
142
+
143
+ describe('updateSessionGroup', () => {
144
+ it('should update the group of a session', async () => {
145
+ // Setup
146
+ const groupId = 'new-group';
147
+ (SessionModel.update as Mock).mockResolvedValue({ ...mockSession, group: groupId });
148
+
149
+ // Execute
150
+ const result = await sessionService.updateSessionGroup(mockSessionId, groupId);
151
+
152
+ // Assert
153
+ expect(SessionModel.update).toHaveBeenCalledWith(mockSessionId, { group: groupId });
154
+ expect(result).toEqual({ ...mockSession, group: groupId });
155
+ });
156
+ });
157
+
158
+ describe('updateSessionMeta', () => {
159
+ it('should update the meta of a session', async () => {
160
+ // Setup
161
+ const newMeta = { description: 'Updated description' };
162
+ (SessionModel.update as Mock).mockResolvedValue({ ...mockSession, meta: newMeta });
163
+
164
+ // Execute
165
+ const result = await sessionService.updateSessionMeta(mockSessionId, newMeta);
166
+
167
+ // Assert
168
+ expect(SessionModel.update).toHaveBeenCalledWith(mockSessionId, { meta: newMeta });
169
+ expect(result).toEqual({ ...mockSession, meta: newMeta });
170
+ });
171
+ });
172
+
173
+ describe('updateSessionConfig', () => {
174
+ it('should update the config of a session', async () => {
175
+ // Setup
176
+ const newConfig = { compressThreshold: 2 } as LobeAgentConfig;
177
+ (SessionModel.updateConfig as Mock).mockResolvedValue({ ...mockSession, config: newConfig });
178
+
179
+ // Execute
180
+ const result = await sessionService.updateSessionConfig(mockSessionId, newConfig);
181
+
182
+ // Assert
183
+ expect(SessionModel.updateConfig).toHaveBeenCalledWith(mockSessionId, newConfig);
184
+ expect(result).toEqual({ ...mockSession, config: newConfig });
185
+ });
186
+ });
187
+ describe('hasSessions', () => {
188
+ it('should return false if no sessions exist', async () => {
189
+ // Setup
190
+ (SessionModel.isEmpty as Mock).mockResolvedValue(true);
191
+
192
+ // Execute
193
+ const result = await sessionService.hasSessions();
194
+
195
+ // Assert
196
+ expect(SessionModel.isEmpty).toHaveBeenCalled();
197
+ expect(result).toBe(false);
198
+ });
199
+
200
+ it('should return true if sessions exist', async () => {
201
+ // Setup
202
+ (SessionModel.isEmpty as Mock).mockResolvedValue(false);
203
+
204
+ // Execute
205
+ const result = await sessionService.hasSessions();
206
+
207
+ // Assert
208
+ expect(SessionModel.isEmpty).toHaveBeenCalled();
209
+ expect(result).toBe(true);
210
+ });
211
+ });
212
+
213
+ describe('searchSessions', () => {
214
+ it('should return sessions that match the keyword', async () => {
215
+ // Setup
216
+ const keyword = 'search';
217
+ (SessionModel.queryByKeyword as Mock).mockResolvedValue(mockSessions);
218
+
219
+ // Execute
220
+ const result = await sessionService.searchSessions(keyword);
221
+
222
+ // Assert
223
+ expect(SessionModel.queryByKeyword).toHaveBeenCalledWith(keyword);
224
+ expect(result).toBe(mockSessions);
225
+ });
226
+ });
227
+ });
@@ -0,0 +1,220 @@
1
+ import { Mock, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { CreateTopicParams, TopicModel } from '@/database/models/topic';
4
+ import { ChatTopic } from '@/types/topic';
5
+
6
+ import { topicService } from '../topic';
7
+
8
+ // Mock the TopicModel
9
+ vi.mock('@/database/models/topic', () => {
10
+ return {
11
+ TopicModel: {
12
+ create: vi.fn(),
13
+ query: vi.fn(),
14
+ delete: vi.fn(),
15
+ batchDeleteBySessionId: vi.fn(),
16
+ batchDelete: vi.fn(),
17
+ clearTable: vi.fn(),
18
+ toggleFavorite: vi.fn(),
19
+ batchCreate: vi.fn(),
20
+ update: vi.fn(),
21
+ queryAll: vi.fn(),
22
+ queryByKeyword: vi.fn(),
23
+ },
24
+ };
25
+ });
26
+
27
+ describe('TopicService', () => {
28
+ // Mock data
29
+ const mockTopicId = 'mock-topic-id';
30
+ const mockTopic: ChatTopic = {
31
+ createdAt: 100,
32
+ updatedAt: 100,
33
+ id: mockTopicId,
34
+ title: 'Mock Topic',
35
+ };
36
+ const mockTopics = [mockTopic];
37
+
38
+ beforeEach(() => {
39
+ // Reset all mocks before running each test case
40
+ vi.resetAllMocks();
41
+ });
42
+
43
+ describe('createTopic', () => {
44
+ it('should create a topic and return its id', async () => {
45
+ // Setup
46
+ const createParams: CreateTopicParams = {
47
+ title: 'New Topic',
48
+ sessionId: '1',
49
+ };
50
+ (TopicModel.create as Mock).mockResolvedValue(mockTopic);
51
+
52
+ // Execute
53
+ const topicId = await topicService.createTopic(createParams);
54
+
55
+ // Assert
56
+ expect(TopicModel.create).toHaveBeenCalledWith(createParams);
57
+ expect(topicId).toBe(mockTopicId);
58
+ });
59
+ it('should throw an error if topic creation fails', async () => {
60
+ // Setup
61
+ const createParams: CreateTopicParams = {
62
+ title: 'New Topic',
63
+ sessionId: '1',
64
+ };
65
+
66
+ (TopicModel.create as Mock).mockResolvedValue(null);
67
+
68
+ // Execute & Assert
69
+ await expect(topicService.createTopic(createParams)).rejects.toThrow('topic create Error');
70
+ });
71
+ });
72
+
73
+ describe('getTopics', () => {
74
+ // Example for getTopics
75
+ it('should query topics with given parameters', async () => {
76
+ // Setup
77
+ const queryParams = { sessionId: 'session-id' };
78
+ (TopicModel.query as Mock).mockResolvedValue(mockTopics);
79
+
80
+ // Execute
81
+ const topics = await topicService.getTopics(queryParams);
82
+
83
+ // Assert
84
+ expect(TopicModel.query).toHaveBeenCalledWith(queryParams);
85
+ expect(topics).toBe(mockTopics);
86
+ });
87
+ });
88
+
89
+ describe('updateFavorite', () => {
90
+ // Example for updateFavorite
91
+ it('should toggle favorite status of a topic', async () => {
92
+ // Setup
93
+ const newState = true;
94
+ (TopicModel.toggleFavorite as Mock).mockResolvedValue({ ...mockTopic, favorite: newState });
95
+
96
+ // Execute
97
+ const result = await topicService.updateFavorite(mockTopicId, newState);
98
+
99
+ // Assert
100
+ expect(TopicModel.toggleFavorite).toHaveBeenCalledWith(mockTopicId, newState);
101
+ expect(result).toEqual({ ...mockTopic, favorite: newState });
102
+ });
103
+ });
104
+
105
+ describe('removeTopic', () => {
106
+ it('should remove a topic by id', async () => {
107
+ // Setup
108
+ (TopicModel.delete as Mock).mockResolvedValue(true);
109
+
110
+ // Execute
111
+ const result = await topicService.removeTopic(mockTopicId);
112
+
113
+ // Assert
114
+ expect(TopicModel.delete).toHaveBeenCalledWith(mockTopicId);
115
+ expect(result).toBe(true);
116
+ });
117
+ });
118
+
119
+ describe('removeTopics', () => {
120
+ it('should remove all topics with a given session id', async () => {
121
+ // Setup
122
+ const sessionId = 'session-id';
123
+ (TopicModel.batchDeleteBySessionId as Mock).mockResolvedValue(true);
124
+
125
+ // Execute
126
+ const result = await topicService.removeTopics(sessionId);
127
+
128
+ // Assert
129
+ expect(TopicModel.batchDeleteBySessionId).toHaveBeenCalledWith(sessionId);
130
+ expect(result).toBe(true);
131
+ });
132
+ });
133
+
134
+ describe('batchRemoveTopics', () => {
135
+ it('should batch remove topics', async () => {
136
+ // Setup
137
+ const topicIds = [mockTopicId, 'another-topic-id'];
138
+ (TopicModel.batchDelete as Mock).mockResolvedValue(true);
139
+
140
+ // Execute
141
+ const result = await topicService.batchRemoveTopics(topicIds);
142
+
143
+ // Assert
144
+ expect(TopicModel.batchDelete).toHaveBeenCalledWith(topicIds);
145
+ expect(result).toBe(true);
146
+ });
147
+ });
148
+
149
+ describe('removeAllTopic', () => {
150
+ it('should clear all topics from the table', async () => {
151
+ // Setup
152
+ (TopicModel.clearTable as Mock).mockResolvedValue(true);
153
+
154
+ // Execute
155
+ const result = await topicService.removeAllTopic();
156
+
157
+ // Assert
158
+ expect(TopicModel.clearTable).toHaveBeenCalled();
159
+ expect(result).toBe(true);
160
+ });
161
+ });
162
+
163
+ describe('batchCreateTopics', () => {
164
+ it('should batch create topics', async () => {
165
+ // Setup
166
+ (TopicModel.batchCreate as Mock).mockResolvedValue(mockTopics);
167
+
168
+ // Execute
169
+ const result = await topicService.batchCreateTopics(mockTopics);
170
+
171
+ // Assert
172
+ expect(TopicModel.batchCreate).toHaveBeenCalledWith(mockTopics);
173
+ expect(result).toBe(mockTopics);
174
+ });
175
+ });
176
+
177
+ describe('updateTitle', () => {
178
+ it('should update the title of a topic', async () => {
179
+ // Setup
180
+ const newTitle = 'Updated Topic Title';
181
+ (TopicModel.update as Mock).mockResolvedValue({ ...mockTopic, title: newTitle });
182
+
183
+ // Execute
184
+ const result = await topicService.updateTitle(mockTopicId, newTitle);
185
+
186
+ // Assert
187
+ expect(TopicModel.update).toHaveBeenCalledWith(mockTopicId, { title: newTitle });
188
+ expect(result).toEqual({ ...mockTopic, title: newTitle });
189
+ });
190
+ });
191
+
192
+ describe('getAllTopics', () => {
193
+ it('should retrieve all topics', async () => {
194
+ // Setup
195
+ (TopicModel.queryAll as Mock).mockResolvedValue(mockTopics);
196
+
197
+ // Execute
198
+ const result = await topicService.getAllTopics();
199
+
200
+ // Assert
201
+ expect(TopicModel.queryAll).toHaveBeenCalled();
202
+ expect(result).toBe(mockTopics);
203
+ });
204
+ });
205
+
206
+ describe('searchTopics', () => {
207
+ it('should return topics that match the keyword', async () => {
208
+ // Setup
209
+ const keyword = 'search';
210
+ (TopicModel.queryByKeyword as Mock).mockResolvedValue(mockTopics);
211
+
212
+ // Execute
213
+ const result = await topicService.searchTopics(keyword);
214
+
215
+ // Assert
216
+ expect(TopicModel.queryByKeyword).toHaveBeenCalledWith(keyword);
217
+ expect(result).toBe(mockTopics);
218
+ });
219
+ });
220
+ });
@@ -4,8 +4,8 @@ import { merge } from 'lodash-es';
4
4
  import { VISION_MODEL_WHITE_LIST } from '@/const/llm';
5
5
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
6
6
  import { filesSelectors, useFileStore } from '@/store/file';
7
- import { usePluginStore } from '@/store/plugin';
8
- import { pluginSelectors } from '@/store/plugin/selectors';
7
+ import { useToolStore } from '@/store/tool';
8
+ import { pluginSelectors } from '@/store/tool/selectors';
9
9
  import { ChatMessage } from '@/types/chatMessage';
10
10
  import type { OpenAIChatMessage, OpenAIChatStreamPayload } from '@/types/openai/chat';
11
11
  import { UserMessageContentPart } from '@/types/openai/chat';
@@ -41,7 +41,7 @@ class ChatService {
41
41
 
42
42
  // ============ 2. preprocess tools ============ //
43
43
 
44
- const filterTools = pluginSelectors.enabledSchema(enabledPlugins)(usePluginStore.getState());
44
+ const filterTools = pluginSelectors.enabledSchema(enabledPlugins)(useToolStore.getState());
45
45
 
46
46
  // the rule that model can use tools:
47
47
  // 1. tools is not empty
@@ -79,9 +79,7 @@ class ChatService {
79
79
  * @param options
80
80
  */
81
81
  runPluginApi = async (params: PluginRequestPayload, options?: FetchOptions) => {
82
- const { usePluginStore } = await import('@/store/plugin');
83
-
84
- const s = usePluginStore.getState();
82
+ const s = useToolStore.getState();
85
83
 
86
84
  const settings = pluginSelectors.getPluginSettingsById(params.identifier)(s);
87
85
  const manifest = pluginSelectors.getPluginManifestById(params.identifier)(s);
@@ -1,4 +1,4 @@
1
- import { LobeChatPluginsMarketIndex } from '@lobehub/chat-plugin-sdk';
1
+ import { LobeChatPluginManifest, LobeChatPluginsMarketIndex } from '@lobehub/chat-plugin-sdk';
2
2
 
3
3
  import { getPluginIndexJSON } from '@/const/url';
4
4
  import { getCurrentLanguage } from '@/store/global/helpers';
@@ -16,6 +16,17 @@ class PluginService {
16
16
 
17
17
  return data;
18
18
  };
19
+
20
+ fetchManifest = async (manifest: string) => {
21
+ try {
22
+ const res = await fetch(manifest);
23
+
24
+ return (await res.json()) as LobeChatPluginManifest;
25
+ } catch (error) {
26
+ console.error(error);
27
+ return null;
28
+ }
29
+ };
19
30
  }
20
31
 
21
32
  export const pluginService = new PluginService();
@@ -469,7 +469,6 @@ describe('chatMessage actions', () => {
469
469
  // 设置模拟返回值
470
470
  (messageService.getMessages as Mock).mockResolvedValue(messages);
471
471
 
472
- // renderHook 在 @testing-library/react-hooks 中使用,所以我们使用 render 来替代
473
472
  const { result } = renderHook(() => useChatStore().useFetchMessages(sessionId, topicId));
474
473
 
475
474
  // 等待异步操作完成
@@ -3,6 +3,7 @@ import { StateCreator } from 'zustand/vanilla';
3
3
 
4
4
  import { sessionService } from '@/services/session';
5
5
  import { useGlobalStore } from '@/store/global';
6
+ import { agentSelectors } from '@/store/session/selectors';
6
7
  import { LobeAgentConfig } from '@/types/agent';
7
8
  import { MetaData } from '@/types/meta';
8
9
 
@@ -14,6 +15,7 @@ import { sessionSelectors } from '../session/selectors';
14
15
  */
15
16
  export interface AgentAction {
16
17
  removePlugin: (id: string) => void;
18
+ togglePlugin: (id: string, open?: boolean) => Promise<void>;
17
19
  updateAgentConfig: (config: Partial<LobeAgentConfig>) => void;
18
20
  updateAgentMeta: (meta: Partial<MetaData>) => void;
19
21
  }
@@ -25,19 +27,33 @@ export const createAgentSlice: StateCreator<
25
27
  AgentAction
26
28
  > = (set, get) => ({
27
29
  removePlugin: async (id) => {
28
- const session = sessionSelectors.currentSession(get());
29
- if (!session) return;
30
+ await get().togglePlugin(id, false);
31
+ },
30
32
 
31
- const { activeId, refreshSessions } = get();
33
+ togglePlugin: async (id, open) => {
34
+ const originConfig = agentSelectors.currentAgentConfig(get());
32
35
 
33
- const config = produce(session.config, (draft) => {
34
- draft.plugins = draft.plugins?.filter((i) => i !== id) || [];
36
+ const config = produce(originConfig, (draft) => {
37
+ draft.plugins = produce(draft.plugins || [], (plugins) => {
38
+ const index = plugins.indexOf(id);
39
+ const shouldOpen = open !== undefined ? open : index === -1;
40
+
41
+ if (shouldOpen) {
42
+ // 如果 open 为 true 或者 id 不存在于 plugins 中,则添加它
43
+ if (index === -1) {
44
+ plugins.push(id);
45
+ }
46
+ } else {
47
+ // 如果 open 为 false 或者 id 存在于 plugins 中,则移除它
48
+ if (index !== -1) {
49
+ plugins.splice(index, 1);
50
+ }
51
+ }
52
+ });
35
53
  });
36
54
 
37
- await sessionService.updateSessionConfig(activeId, config);
38
- await refreshSessions();
55
+ get().updateAgentConfig(config);
39
56
  },
40
-
41
57
  updateAgentConfig: async (config) => {
42
58
  // if is the inbox session, update the global config
43
59
  const isInbox = sessionSelectors.isInboxSession(get());
@@ -1,23 +1,12 @@
1
1
  import { LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
2
- import i18n from 'i18next';
3
2
 
4
3
  import { CustomPlugin } from '@/types/plugin';
5
4
 
6
- const getI18nValue = (value: string | Record<string, string> | undefined) => {
7
- if (!value) return;
8
-
9
- if (typeof value === 'string') return value;
10
-
11
- if (value[i18n.language]) return value[i18n.language];
12
-
13
- return Object.values(value)[0];
14
- };
15
-
16
5
  const getPluginFormList = (pluginList: LobeChatPluginMeta[], id: string) =>
17
6
  pluginList?.find((p) => p.identifier === id);
18
7
 
19
- const getPluginTitle = (meta?: LobeChatPluginMeta['meta']) => getI18nValue(meta?.title);
20
- const getPluginDesc = (meta?: LobeChatPluginMeta['meta']) => getI18nValue(meta?.description);
8
+ const getPluginTitle = (meta?: LobeChatPluginMeta['meta']) => meta?.title;
9
+ const getPluginDesc = (meta?: LobeChatPluginMeta['meta']) => meta?.description;
21
10
  const getPluginAvatar = (meta?: LobeChatPluginMeta['meta']) => meta?.avatar;
22
11
 
23
12
  const isCustomPlugin = (id: string, pluginList: CustomPlugin[]) =>
@@ -0,0 +1,2 @@
1
+ export * from './helpers';
2
+ export { useToolStore } from './store';
@@ -0,0 +1,11 @@
1
+ import { CustomPluginState, initialCustomPluginState } from './slices/customPlugin';
2
+ import { PluginState, initialPluginState } from './slices/plugin';
3
+ import { PluginStoreState, initialPluginStoreState } from './slices/store';
4
+
5
+ export type ToolStoreState = PluginState & CustomPluginState & PluginStoreState;
6
+
7
+ export const initialState: ToolStoreState = {
8
+ ...initialPluginState,
9
+ ...initialCustomPluginState,
10
+ ...initialPluginStoreState,
11
+ };
@@ -0,0 +1,3 @@
1
+ export { customPluginSelectors } from './slices/customPlugin/selectors';
2
+ export { pluginSelectors } from './slices/plugin/selectors';
3
+ export { pluginStoreSelectors } from './slices/store/selectors';