@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.
- package/CHANGELOG.md +25 -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/common/Common.tsx +2 -2
- 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,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
|
+
});
|
package/src/services/chat.ts
CHANGED
|
@@ -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 {
|
|
8
|
-
import { pluginSelectors } from '@/store/
|
|
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)(
|
|
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
|
|
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);
|
package/src/services/plugin.ts
CHANGED
|
@@ -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
|
-
|
|
29
|
-
|
|
30
|
+
await get().togglePlugin(id, false);
|
|
31
|
+
},
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
togglePlugin: async (id, open) => {
|
|
34
|
+
const originConfig = agentSelectors.currentAgentConfig(get());
|
|
32
35
|
|
|
33
|
-
const config = produce(
|
|
34
|
-
draft.plugins = draft.plugins
|
|
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
|
-
|
|
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']) =>
|
|
20
|
-
const getPluginDesc = (meta?: LobeChatPluginMeta['meta']) =>
|
|
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,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
|
+
};
|