@lobehub/chat 0.111.6 → 0.112.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 (130) hide show
  1. package/.i18nrc.js +1 -0
  2. package/CHANGELOG.md +59 -0
  3. package/locales/en_US/chat.json +12 -12
  4. package/locales/en_US/common.json +4 -4
  5. package/locales/en_US/error.json +16 -14
  6. package/locales/en_US/market.json +2 -2
  7. package/locales/en_US/migration.json +4 -4
  8. package/locales/en_US/plugin.json +75 -38
  9. package/locales/en_US/setting.json +7 -7
  10. package/locales/fr_FR/chat.json +11 -11
  11. package/locales/fr_FR/common.json +4 -4
  12. package/locales/fr_FR/error.json +12 -10
  13. package/locales/fr_FR/plugin.json +42 -5
  14. package/locales/fr_FR/setting.json +12 -12
  15. package/locales/fr_FR/welcome.json +3 -3
  16. package/locales/ja_JP/chat.json +1 -1
  17. package/locales/ja_JP/common.json +4 -4
  18. package/locales/ja_JP/error.json +16 -14
  19. package/locales/ja_JP/migration.json +4 -4
  20. package/locales/ja_JP/plugin.json +70 -33
  21. package/locales/ja_JP/setting.json +1 -1
  22. package/locales/ko_KR/chat.json +1 -1
  23. package/locales/ko_KR/common.json +4 -4
  24. package/locales/ko_KR/error.json +16 -14
  25. package/locales/ko_KR/migration.json +4 -4
  26. package/locales/ko_KR/plugin.json +71 -34
  27. package/locales/ko_KR/setting.json +1 -1
  28. package/locales/ru_RU/chat.json +1 -1
  29. package/locales/ru_RU/common.json +4 -4
  30. package/locales/ru_RU/error.json +16 -14
  31. package/locales/ru_RU/market.json +2 -2
  32. package/locales/ru_RU/migration.json +4 -4
  33. package/locales/ru_RU/plugin.json +71 -34
  34. package/locales/ru_RU/setting.json +1 -1
  35. package/locales/zh_CN/chat.json +1 -1
  36. package/locales/zh_CN/error.json +2 -0
  37. package/locales/zh_CN/plugin.json +42 -5
  38. package/locales/zh_CN/setting.json +1 -1
  39. package/locales/zh_TW/chat.json +1 -1
  40. package/locales/zh_TW/common.json +4 -4
  41. package/locales/zh_TW/error.json +16 -14
  42. package/locales/zh_TW/market.json +2 -2
  43. package/locales/zh_TW/migration.json +4 -4
  44. package/locales/zh_TW/plugin.json +98 -61
  45. package/locales/zh_TW/setting.json +1 -1
  46. package/package.json +4 -2
  47. package/scripts/i18nWorkflow/genDiff.ts +7 -8
  48. package/scripts/i18nWorkflow/utils.ts +0 -14
  49. package/scripts/readmeWorkflow/syncPluginIndex.ts +9 -11
  50. package/src/app/api/plugins/route.ts +2 -4
  51. package/src/app/api/proxy/route.ts +10 -0
  52. package/src/app/chat/features/ChatInput/ActionBar/Tools/ToolItem.tsx +3 -8
  53. package/src/app/chat/features/ChatInput/ActionBar/Tools/index.tsx +3 -3
  54. package/src/app/chat/features/Conversation/ChatList/Error/index.tsx +6 -0
  55. package/src/app/chat/features/Conversation/ChatList/Messages/Function.tsx +1 -1
  56. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +7 -5
  57. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +2 -2
  58. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +6 -4
  59. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/index.tsx +2 -2
  60. package/src/app/chat/features/Migration/ExportConfigButton.tsx +1 -1
  61. package/src/app/market/features/AgentDetailContent/AgentInfo/index.tsx +1 -0
  62. package/src/app/market/features/AgentDetailContent/AgentInfo/style.ts +1 -9
  63. package/src/app/settings/common/Common.tsx +5 -7
  64. package/src/components/FileList/FileItem.tsx +4 -2
  65. package/src/components/HotKeys/index.tsx +5 -4
  66. package/src/const/plugin.ts +1 -0
  67. package/src/features/AgentSetting/AgentPlugin/AddPluginButton.tsx +2 -2
  68. package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +19 -53
  69. package/src/features/AgentSetting/AgentPlugin/index.tsx +5 -11
  70. package/src/features/AgentSetting/store/initialState.ts +1 -1
  71. package/src/features/PluginDetailModal/APIs.tsx +44 -0
  72. package/src/features/PluginDetailModal/Meta.tsx +59 -0
  73. package/src/features/PluginDetailModal/index.tsx +79 -0
  74. package/src/features/PluginDevModal/PluginPreview.tsx +8 -10
  75. package/src/features/PluginDevModal/UrlManifestForm.tsx +131 -0
  76. package/src/features/PluginDevModal/index.tsx +31 -32
  77. package/src/features/PluginSettings/index.tsx +1 -1
  78. package/src/features/PluginStore/AddPluginButton.tsx +47 -0
  79. package/src/features/PluginStore/InstalledPluginList.tsx +54 -0
  80. package/src/features/PluginStore/OnlineList.tsx +95 -0
  81. package/src/features/PluginStore/PluginItem/Action.tsx +118 -0
  82. package/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx +50 -0
  83. package/src/features/PluginStore/PluginItem/PluginTag.tsx +58 -0
  84. package/src/features/PluginStore/PluginItem/index.tsx +73 -0
  85. package/src/features/PluginStore/index.tsx +20 -92
  86. package/src/hooks/usePlatform.ts +15 -0
  87. package/src/locales/default/chat.ts +1 -1
  88. package/src/locales/default/error.ts +2 -0
  89. package/src/locales/default/plugin.ts +41 -4
  90. package/src/locales/default/setting.ts +1 -1
  91. package/src/services/__tests__/__snapshots__/plugin.test.ts.snap +154 -0
  92. package/src/services/__tests__/chat.test.ts +1 -1
  93. package/src/services/__tests__/global.test.ts +57 -0
  94. package/src/services/__tests__/openai/OpenAPI_V3.json +245 -0
  95. package/src/services/__tests__/openai/plugin.json +22 -0
  96. package/src/services/__tests__/plugin.test.ts +90 -1
  97. package/src/services/__tests__/share.test.ts +86 -0
  98. package/src/services/_url.ts +2 -0
  99. package/src/services/global.ts +15 -0
  100. package/src/services/plugin.ts +99 -15
  101. package/src/services/share.ts +34 -0
  102. package/src/store/chat/actions/enhance.test.ts +15 -15
  103. package/src/store/chat/actions/message.test.ts +3 -2
  104. package/src/store/chat/actions/plugin.test.ts +281 -0
  105. package/src/store/chat/actions/plugin.ts +22 -3
  106. package/src/store/chat/actions/share.ts +2 -2
  107. package/src/store/chat/actions/topic.test.ts +12 -4
  108. package/src/store/global/slices/common.ts +2 -2
  109. package/src/store/session/selectors.ts +1 -1
  110. package/src/store/session/slices/agent/selectors.ts +1 -1
  111. package/src/store/tool/helpers.ts +8 -1
  112. package/src/store/tool/slices/customPlugin/action.ts +4 -1
  113. package/src/store/tool/slices/plugin/selectors.test.ts +22 -3
  114. package/src/store/tool/slices/plugin/selectors.ts +24 -5
  115. package/src/store/tool/slices/store/selectors.test.ts +18 -1
  116. package/src/store/tool/slices/store/selectors.ts +14 -3
  117. package/src/styles/antdOverride.ts +7 -0
  118. package/src/types/openai/chat.ts +0 -1
  119. package/src/types/openai/plugin.ts +57 -0
  120. package/src/types/tool/index.ts +1 -2
  121. package/src/types/tool/plugin.ts +13 -1
  122. package/src/types/tool/tool.ts +1 -0
  123. package/src/types/translate.ts +0 -1
  124. package/src/utils/platform.ts +8 -5
  125. package/src/features/PluginDevModal/UrlModeForm.tsx +0 -98
  126. package/src/features/PluginSettingsModal/index.tsx +0 -59
  127. package/src/features/PluginStore/PluginItem.tsx +0 -82
  128. package/src/features/PluginStore/PluginSettings.tsx +0 -40
  129. package/src/services/latestVersion.ts +0 -11
  130. package/src/services/shareGPT.ts +0 -28
@@ -0,0 +1,34 @@
1
+ import { ShareGPTConversation } from '@/types/share';
2
+ import { parseMarkdown } from '@/utils/parseMarkdown';
3
+
4
+ export const SHARE_GPT_URL = 'https://sharegpt.com/api/conversations';
5
+
6
+ class ShareGPTService {
7
+ public async createShareGPTUrl(conversation: ShareGPTConversation) {
8
+ const items = [];
9
+
10
+ for (const item of conversation.items) {
11
+ items.push({
12
+ from: item.from,
13
+ value: item.from === 'gpt' ? await parseMarkdown(item.value) : item.value,
14
+ });
15
+ }
16
+
17
+ const res = await fetch(SHARE_GPT_URL, {
18
+ body: JSON.stringify({ ...conversation, items }),
19
+ headers: {
20
+ 'Content-Type': 'application/json',
21
+ },
22
+ method: 'POST',
23
+ });
24
+
25
+ const { id } = await res.json();
26
+
27
+ if (!id) throw new Error('Failed to create ShareGPT URL');
28
+
29
+ // short link to the ShareGPT post
30
+ return `https://shareg.pt/${id}`;
31
+ }
32
+ }
33
+
34
+ export const shareGPTService = new ShareGPTService();
@@ -71,23 +71,23 @@ describe('ChatEnhanceAction', () => {
71
71
  const messageContent = 'Hello World';
72
72
  const detectedLang = 'en-US';
73
73
 
74
- // 设置初始消息状态
75
- useChatStore.setState({
76
- messages: [
77
- {
78
- id: messageId,
79
- content: messageContent,
80
- createdAt: Date.now(),
81
- updatedAt: Date.now(),
82
- role: 'user',
83
- sessionId: 'test',
84
- topicId: 'test',
85
- meta: {},
86
- },
87
- ],
74
+ act(() => {
75
+ useChatStore.setState({
76
+ messages: [
77
+ {
78
+ id: messageId,
79
+ content: messageContent,
80
+ createdAt: Date.now(),
81
+ updatedAt: Date.now(),
82
+ role: 'user',
83
+ sessionId: 'test',
84
+ topicId: 'test',
85
+ meta: {},
86
+ },
87
+ ],
88
+ });
88
89
  });
89
90
 
90
- // 模拟语言检测和翻译结果
91
91
  (chatService.fetchPresetTaskResult as Mock).mockImplementation(({ params }) => {
92
92
  if (params === chainLangDetect(messageContent)) {
93
93
  return Promise.resolve(detectedLang);
@@ -383,8 +383,9 @@ describe('chatMessage actions', () => {
383
383
  const toggleChatLoadingSpy = vi.spyOn(result.current, 'toggleChatLoading');
384
384
  const abortController = new AbortController();
385
385
 
386
- // 设置模拟 abortController
387
- useChatStore.setState({ abortController });
386
+ act(() => {
387
+ useChatStore.setState({ abortController });
388
+ });
388
389
 
389
390
  await act(async () => {
390
391
  result.current.stopGenerateMessage();
@@ -0,0 +1,281 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { Md5 } from 'ts-md5';
3
+ import { Mock, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
6
+ import { chatService } from '@/services/chat';
7
+ import { messageService } from '@/services/message';
8
+ import { chatSelectors } from '@/store/chat/selectors';
9
+ import { useChatStore } from '@/store/chat/store';
10
+ import { useToolStore } from '@/store/tool';
11
+ import { pluginSelectors } from '@/store/tool/selectors';
12
+ import { LobeTool } from '@/types/tool';
13
+
14
+ // Mock messageService 和 chatSelectors
15
+ vi.mock('@/services/message', () => ({
16
+ messageService: {
17
+ updateMessageContent: vi.fn(),
18
+ updateMessage: vi.fn(),
19
+ updateMessageError: vi.fn(),
20
+ updateMessagePluginState: vi.fn(),
21
+ },
22
+ }));
23
+ vi.mock('@/services/chat', () => ({
24
+ chatService: {
25
+ runPluginApi: vi.fn(),
26
+ },
27
+ }));
28
+
29
+ vi.mock('@/store/chat/selectors', () => ({
30
+ chatSelectors: {
31
+ currentChats: vi.fn(),
32
+ getMessageById: vi.fn(),
33
+ },
34
+ }));
35
+
36
+ describe('ChatPluginAction', () => {
37
+ describe('fillPluginMessageContent', () => {
38
+ it('should update message content and process the message', async () => {
39
+ // 设置模拟函数的返回值
40
+ const mockCurrentChats: any[] = [];
41
+ (chatSelectors.currentChats as Mock).mockReturnValue(mockCurrentChats);
42
+
43
+ // 设置初始状态
44
+ const initialState = {
45
+ messages: [],
46
+ coreProcessMessage: vi.fn(),
47
+ refreshMessages: vi.fn(),
48
+ };
49
+ useChatStore.setState(initialState);
50
+
51
+ const { result } = renderHook(() => useChatStore());
52
+
53
+ const messageId = 'message-id';
54
+ const newContent = 'Updated content';
55
+
56
+ await act(async () => {
57
+ await result.current.fillPluginMessageContent(messageId, newContent);
58
+ });
59
+
60
+ // 验证 messageService.updateMessageContent 是否被正确调用
61
+ expect(messageService.updateMessageContent).toHaveBeenCalledWith(messageId, newContent);
62
+
63
+ // 验证 refreshMessages 是否被调用
64
+ expect(result.current.refreshMessages).toHaveBeenCalled();
65
+
66
+ // 验证 coreProcessMessage 是否被正确调用
67
+ expect(result.current.coreProcessMessage).toHaveBeenCalledWith(mockCurrentChats, messageId);
68
+ });
69
+ });
70
+
71
+ describe('runPluginDefaultType', () => {
72
+ it('should run the default plugin type and update message content', async () => {
73
+ const pluginPayload = { apiName: 'testApi', arguments: { key: 'value' } };
74
+ const messageId = 'message-id';
75
+ const pluginApiResponse = 'Plugin API response';
76
+
77
+ const initialState = {
78
+ refreshMessages: vi.fn(),
79
+ coreProcessMessage: vi.fn(),
80
+ toggleChatLoading: vi.fn(),
81
+ };
82
+ useChatStore.setState(initialState);
83
+
84
+ (chatService.runPluginApi as Mock).mockResolvedValue(pluginApiResponse);
85
+
86
+ const { result } = renderHook(() => useChatStore());
87
+
88
+ await act(async () => {
89
+ await result.current.runPluginDefaultType(messageId, pluginPayload);
90
+ });
91
+
92
+ expect(initialState.toggleChatLoading).toHaveBeenCalledWith(
93
+ true,
94
+ messageId,
95
+ expect.any(String),
96
+ );
97
+ expect(chatService.runPluginApi).toHaveBeenCalledWith(pluginPayload, { signal: undefined });
98
+ expect(messageService.updateMessageContent).toHaveBeenCalledWith(
99
+ messageId,
100
+ pluginApiResponse,
101
+ );
102
+ expect(initialState.refreshMessages).toHaveBeenCalled();
103
+ expect(initialState.coreProcessMessage).toHaveBeenCalled();
104
+ expect(initialState.toggleChatLoading).toHaveBeenCalledWith(false);
105
+ });
106
+
107
+ it('should handle errors when the plugin API call fails', async () => {
108
+ const pluginPayload = { apiName: 'testApi', arguments: { key: 'value' } };
109
+ const messageId = 'message-id';
110
+ const error = new Error('API call failed');
111
+
112
+ const initialState = {
113
+ refreshMessages: vi.fn(),
114
+ coreProcessMessage: vi.fn(),
115
+ toggleChatLoading: vi.fn(),
116
+ };
117
+ useChatStore.setState(initialState);
118
+
119
+ (chatService.runPluginApi as Mock).mockRejectedValue(error);
120
+
121
+ const { result } = renderHook(() => useChatStore());
122
+
123
+ await act(async () => {
124
+ await result.current.runPluginDefaultType(messageId, pluginPayload);
125
+ });
126
+
127
+ expect(initialState.toggleChatLoading).toHaveBeenCalledWith(
128
+ true,
129
+ messageId,
130
+ expect.any(String),
131
+ );
132
+ expect(chatService.runPluginApi).toHaveBeenCalledWith(pluginPayload, { signal: undefined });
133
+ expect(messageService.updateMessageError).toHaveBeenCalledWith(messageId, error);
134
+ expect(initialState.refreshMessages).toHaveBeenCalled();
135
+ expect(initialState.toggleChatLoading).toHaveBeenCalledWith(false);
136
+ expect(initialState.coreProcessMessage).not.toHaveBeenCalled(); // 确保在错误情况下不调用此方法
137
+ });
138
+ });
139
+
140
+ describe('triggerFunctionCall', () => {
141
+ it('should trigger a function call and update the plugin message accordingly', async () => {
142
+ const messageId = 'message-id';
143
+ const messageContent = `{"function_call": {"name": "pluginName${PLUGIN_SCHEMA_SEPARATOR}apiName", "arguments": {"key": "value"}}}`;
144
+ const messagePluginPayload = {
145
+ apiName: 'apiName',
146
+ identifier: 'pluginName',
147
+ type: 'default',
148
+ arguments: { key: 'value' },
149
+ };
150
+
151
+ const initialState = {
152
+ refreshMessages: vi.fn(),
153
+ runPluginDefaultType: vi.fn(),
154
+ };
155
+ useChatStore.setState(initialState);
156
+
157
+ (chatSelectors.getMessageById as Mock).mockImplementation(() => () => ({
158
+ id: messageId,
159
+ content: messageContent,
160
+ }));
161
+
162
+ const { result } = renderHook(() => useChatStore());
163
+
164
+ await act(async () => {
165
+ await result.current.triggerFunctionCall(messageId);
166
+ });
167
+
168
+ expect(chatSelectors.getMessageById).toHaveBeenCalledWith(messageId);
169
+ expect(messageService.updateMessage).toHaveBeenCalledWith(messageId, {
170
+ content: '',
171
+ plugin: messagePluginPayload,
172
+ role: 'function',
173
+ });
174
+ expect(initialState.refreshMessages).toHaveBeenCalled();
175
+ expect(initialState.runPluginDefaultType).toHaveBeenCalledWith(
176
+ messageId,
177
+ messagePluginPayload,
178
+ );
179
+ });
180
+
181
+ it('should handle function call with MD5 prefixed API name', async () => {
182
+ const messageId = 'message-id';
183
+ const apiName = 'originalApiName';
184
+ const id = 'pluginIdentifier';
185
+ const md5ApiName = PLUGIN_SCHEMA_API_MD5_PREFIX + Md5.hashStr(apiName).toString();
186
+ const messageContent = JSON.stringify({
187
+ function_call: { name: id + PLUGIN_SCHEMA_SEPARATOR + md5ApiName, arguments: {} },
188
+ });
189
+
190
+ const plugin = { identifier: id, manifest: { api: [{ name: apiName }] } } as LobeTool;
191
+
192
+ useToolStore.setState({ installedPlugins: [plugin] });
193
+
194
+ useChatStore.setState({ refreshMessages: vi.fn(), runPluginDefaultType: vi.fn() });
195
+
196
+ (chatSelectors.getMessageById as Mock).mockImplementation(() => () => ({
197
+ id: messageId,
198
+ content: messageContent,
199
+ }));
200
+
201
+ const { result } = renderHook(() => useChatStore());
202
+
203
+ await act(async () => {
204
+ await result.current.triggerFunctionCall(messageId);
205
+ });
206
+ expect(result.current.refreshMessages).toHaveBeenCalled();
207
+
208
+ expect(messageService.updateMessage).toHaveBeenCalledWith(
209
+ messageId,
210
+ expect.objectContaining({
211
+ // 确保正确的 API 名称被设置
212
+ plugin: expect.objectContaining({ apiName }),
213
+ }),
214
+ );
215
+ expect(result.current.runPluginDefaultType).toHaveBeenCalledWith(
216
+ messageId,
217
+ expect.objectContaining({
218
+ apiName: apiName,
219
+ }),
220
+ );
221
+ });
222
+
223
+ it('should handle standalone plugin type', async () => {
224
+ const messageId = 'message-id';
225
+ const messageContent = JSON.stringify({
226
+ function_call: {
227
+ name: `pluginName${PLUGIN_SCHEMA_SEPARATOR}apiName${PLUGIN_SCHEMA_SEPARATOR}standalone`,
228
+ arguments: {},
229
+ },
230
+ });
231
+
232
+ useChatStore.setState({
233
+ refreshMessages: vi.fn(),
234
+ runPluginDefaultType: vi.fn(),
235
+ });
236
+
237
+ (chatSelectors.getMessageById as Mock).mockImplementation(() => () => ({
238
+ id: messageId,
239
+ content: messageContent,
240
+ }));
241
+
242
+ const { result } = renderHook(() => useChatStore());
243
+
244
+ await act(async () => {
245
+ await result.current.triggerFunctionCall(messageId);
246
+ });
247
+
248
+ // 验证 refreshMessages 是否被调用
249
+ expect(result.current.refreshMessages).toHaveBeenCalled();
250
+
251
+ // 验证 runPluginDefaultType 是否没有被调用,因为类型是 standalone
252
+ expect(result.current.runPluginDefaultType).not.toHaveBeenCalled();
253
+ });
254
+ });
255
+
256
+ describe('updatePluginState', () => {
257
+ it('should update the plugin state for a message', async () => {
258
+ const messageId = 'message-id';
259
+ const pluginStateKey = 'key';
260
+ const pluginStateValue = 'value';
261
+
262
+ const initialState = {
263
+ refreshMessages: vi.fn(),
264
+ };
265
+ useChatStore.setState(initialState);
266
+
267
+ const { result } = renderHook(() => useChatStore());
268
+
269
+ await act(async () => {
270
+ await result.current.updatePluginState(messageId, pluginStateKey, pluginStateValue);
271
+ });
272
+
273
+ expect(messageService.updateMessagePluginState).toHaveBeenCalledWith(
274
+ messageId,
275
+ pluginStateKey,
276
+ pluginStateValue,
277
+ );
278
+ expect(initialState.refreshMessages).toHaveBeenCalled();
279
+ });
280
+ });
281
+ });
@@ -1,9 +1,12 @@
1
+ import { Md5 } from 'ts-md5';
1
2
  import { StateCreator } from 'zustand/vanilla';
2
3
 
3
- import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
4
+ import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
4
5
  import { chatService } from '@/services/chat';
5
6
  import { messageService } from '@/services/message';
6
7
  import { ChatStore } from '@/store/chat/store';
8
+ import { useToolStore } from '@/store/tool';
9
+ import { pluginSelectors } from '@/store/tool/selectors';
7
10
  import { ChatPluginPayload } from '@/types/chatMessage';
8
11
  import { OpenAIFunctionCall } from '@/types/openai/functionCall';
9
12
  import { setNamespace } from '@/utils/storeDebug';
@@ -37,12 +40,18 @@ export const chatPlugin: StateCreator<
37
40
  runPluginDefaultType: async (id, payload) => {
38
41
  const { refreshMessages, coreProcessMessage, toggleChatLoading } = get();
39
42
  let data: string;
43
+
40
44
  try {
41
45
  const abortController = toggleChatLoading(true, id, n('fetchPlugin') as string);
42
46
  data = await chatService.runPluginApi(payload, { signal: abortController?.signal });
43
47
  } catch (error) {
44
- await messageService.updateMessageError(id, error as any);
45
- await refreshMessages();
48
+ const err = error as Error;
49
+
50
+ // ignore the aborted request error
51
+ if (!err.message.includes('The user aborted a request.')) {
52
+ await messageService.updateMessageError(id, error as any);
53
+ await refreshMessages();
54
+ }
46
55
 
47
56
  data = '';
48
57
  }
@@ -79,6 +88,16 @@ export const chatPlugin: StateCreator<
79
88
  identifier,
80
89
  type: (type ?? 'default') as any,
81
90
  };
91
+
92
+ // if the apiName is md5, try to find the correct apiName in the plugins
93
+ if (apiName.startsWith(PLUGIN_SCHEMA_API_MD5_PREFIX)) {
94
+ const md5 = apiName.replace(PLUGIN_SCHEMA_API_MD5_PREFIX, '');
95
+ const manifest = pluginSelectors.getPluginManifestById(identifier)(useToolStore.getState());
96
+
97
+ const api = manifest?.api.find((api) => Md5.hashStr(api.name).toString() === md5);
98
+ if (!api) return;
99
+ payload.apiName = api.name;
100
+ }
82
101
  } else {
83
102
  if (message.plugin) payload = message.plugin;
84
103
  }
@@ -3,7 +3,7 @@ import { produce } from 'immer';
3
3
  import { StateCreator } from 'zustand/vanilla';
4
4
 
5
5
  import { DEFAULT_USER_AVATAR_URL } from '@/const/meta';
6
- import { genShareGPTUrl } from '@/services/shareGPT';
6
+ import { shareGPTService } from '@/services/share';
7
7
  import { useSessionStore } from '@/store/session';
8
8
  import { agentSelectors } from '@/store/session/selectors';
9
9
  import { ShareGPTConversation } from '@/types/share';
@@ -102,7 +102,7 @@ export const chatShare: StateCreator<ChatStore, [['zustand/devtools', never]], [
102
102
 
103
103
  set({ shareLoading: true });
104
104
 
105
- const res = await genShareGPTUrl({
105
+ const res = await shareGPTService.createShareGPTUrl({
106
106
  avatarUrl: avatar || DEFAULT_USER_AVATAR_URL,
107
107
  items: shareMsgs,
108
108
  });
@@ -85,7 +85,9 @@ describe('topic action', () => {
85
85
  describe('saveToTopic', () => {
86
86
  it('should not create a topic if there are no messages', async () => {
87
87
  const { result } = renderHook(() => useChatStore());
88
- useChatStore.setState({ messages: [] });
88
+ act(() => {
89
+ useChatStore.setState({ messages: [] });
90
+ });
89
91
 
90
92
  const createTopicSpy = vi.spyOn(topicService, 'createTopic');
91
93
 
@@ -98,7 +100,9 @@ describe('topic action', () => {
98
100
  it('should create a topic and bind messages to it', async () => {
99
101
  const { result } = renderHook(() => useChatStore());
100
102
  const messages = [{ id: 'message1' }, { id: 'message2' }] as ChatMessage[];
101
- useChatStore.setState({ messages, activeId: 'session-id' });
103
+ act(() => {
104
+ useChatStore.setState({ messages, activeId: 'session-id' });
105
+ });
102
106
 
103
107
  const createTopicSpy = vi
104
108
  .spyOn(topicService, 'createTopic')
@@ -133,8 +137,10 @@ describe('topic action', () => {
133
137
  it('should call mutate to refresh topics', async () => {
134
138
  const { result } = renderHook(() => useChatStore());
135
139
  const activeId = 'test-session-id';
136
- useChatStore.setState({ activeId });
137
140
 
141
+ act(() => {
142
+ useChatStore.setState({ activeId });
143
+ });
138
144
  // Mock the mutate function to resolve immediately
139
145
 
140
146
  await act(async () => {
@@ -148,8 +154,10 @@ describe('topic action', () => {
148
154
  it('should handle errors during refreshing topics', async () => {
149
155
  const { result } = renderHook(() => useChatStore());
150
156
  const activeId = 'test-session-id';
151
- useChatStore.setState({ activeId });
152
157
 
158
+ act(() => {
159
+ useChatStore.setState({ activeId });
160
+ });
153
161
  // Mock the mutate function to throw an error
154
162
  // 设置模拟错误
155
163
  (mutate as Mock).mockImplementation(() => {
@@ -4,7 +4,7 @@ import useSWR, { SWRResponse } from 'swr';
4
4
  import type { StateCreator } from 'zustand/vanilla';
5
5
 
6
6
  import { CURRENT_VERSION } from '@/const/version';
7
- import { featLatestVersion } from '@/services/latestVersion';
7
+ import { globalService } from '@/services/global';
8
8
  import { merge } from '@/utils/merge';
9
9
  import { setNamespace } from '@/utils/storeDebug';
10
10
 
@@ -72,7 +72,7 @@ export const createCommonSlice: StateCreator<
72
72
  );
73
73
  },
74
74
  useCheckLatestVersion: () =>
75
- useSWR('checkLatestVersion', featLatestVersion, {
75
+ useSWR('checkLatestVersion', globalService.getLatestVersion, {
76
76
  onSuccess: (data: string) => {
77
77
  if (gt(data, CURRENT_VERSION))
78
78
  set({ hasNewVersion: true, latestVersion: data }, false, n('checkLatestVersion'));
@@ -1,2 +1,2 @@
1
1
  export { agentSelectors } from './slices/agent/selectors';
2
- export { sessionExportSelectors,sessionSelectors } from './slices/session/selectors';
2
+ export { sessionExportSelectors, sessionSelectors } from './slices/session/selectors';
@@ -78,7 +78,7 @@ const currentAgentMeta = (s: SessionStore): MetaData => {
78
78
  avatar: isInbox ? DEFAULT_INBOX_AVATAR : DEFAULT_AVATAR,
79
79
  backgroundColor: DEFAULT_BACKGROUND_COLOR,
80
80
  description: isInbox ? t('inbox.desc') : currentAgentSystemRole(s) || t('noDescription'),
81
- title: isInbox ? t('inbox') : t('defaultSession'),
81
+ title: isInbox ? t('inbox.title', { ns: 'chat' }) : t('defaultSession'),
82
82
  };
83
83
 
84
84
  const session = sessionSelectors.currentSession(s);
@@ -1,4 +1,4 @@
1
- import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
1
+ import { LobeChatPluginManifest, PluginSchema } from '@lobehub/chat-plugin-sdk';
2
2
 
3
3
  import { LobeTool } from '@/types/tool';
4
4
 
@@ -6,15 +6,22 @@ const getPluginFormList = (list: LobeTool[], id: string) => list?.find((p) => p.
6
6
 
7
7
  const getPluginTitle = (meta?: LobeChatPluginManifest['meta']) => meta?.title;
8
8
  const getPluginDesc = (meta?: LobeChatPluginManifest['meta']) => meta?.description;
9
+
10
+ const getPluginTags = (meta?: LobeChatPluginManifest['meta']) => meta?.tags;
9
11
  const getPluginAvatar = (meta?: LobeChatPluginManifest['meta']) => meta?.avatar || '🧩';
10
12
 
11
13
  const isCustomPlugin = (id: string, pluginList: LobeTool[]) =>
12
14
  pluginList.some((i) => i.identifier === id && i.type === 'customPlugin');
13
15
 
16
+ const isSettingSchemaNonEmpty = (schema?: PluginSchema) =>
17
+ schema?.properties && Object.keys(schema.properties).length > 0;
18
+
14
19
  export const pluginHelpers = {
15
20
  getPluginAvatar,
16
21
  getPluginDesc,
17
22
  getPluginFormList,
23
+ getPluginTags,
18
24
  getPluginTitle,
19
25
  isCustomPlugin,
26
+ isSettingSchemaNonEmpty,
20
27
  };
@@ -44,7 +44,10 @@ export const createCustomPluginSlice: StateCreator<
44
44
  const { refreshPlugins, updateInstallLoadingState } = get();
45
45
  try {
46
46
  updateInstallLoadingState(id, true);
47
- const manifest = await pluginService.getPluginManifest(plugin.customParams?.manifestUrl);
47
+ const manifest = await pluginService.getPluginManifest(
48
+ plugin.customParams?.manifestUrl,
49
+ plugin.customParams?.useProxy,
50
+ );
48
51
  updateInstallLoadingState(id, false);
49
52
 
50
53
  await pluginService.updatePluginManifest(id, manifest);
@@ -27,7 +27,6 @@ const mockState = {
27
27
  manifest: {
28
28
  identifier: 'plugin-2',
29
29
  api: [{ name: 'api-2' }],
30
- type: 'default',
31
30
  },
32
31
  type: 'plugin',
33
32
  },
@@ -53,9 +52,29 @@ const mockState = {
53
52
  describe('pluginSelectors', () => {
54
53
  describe('enabledSchema', () => {
55
54
  it('enabledSchema should return correct ChatCompletionFunctions array', () => {
56
- const result = pluginSelectors.enabledSchema(['plugin-1'])(mockState);
57
- expect(result).toEqual([{ name: 'plugin-1____api-1____default' }]);
55
+ const result = pluginSelectors.enabledSchema(['plugin-1', 'plugin-2'])(mockState);
56
+ expect(result).toEqual([{ name: 'plugin-1____api-1' }, { name: 'plugin-2____api-2' }]);
57
+ });
58
+
59
+ it('enabledSchema should return with standalone plugin', () => {
60
+ const result = pluginSelectors.enabledSchema(['plugin-3'])({
61
+ ...mockState,
62
+ installedPlugins: [
63
+ ...mockState.installedPlugins,
64
+ {
65
+ identifier: 'plugin-3',
66
+ manifest: {
67
+ identifier: 'plugin-3',
68
+ api: [{ name: 'api-3' }],
69
+ type: 'standalone',
70
+ },
71
+ type: 'plugin',
72
+ },
73
+ ],
74
+ } as ToolStoreState);
75
+ expect(result).toEqual([{ name: 'plugin-3____api-3____standalone' }]);
58
76
  });
77
+
59
78
  it('enabledSchema should return empty', () => {
60
79
  const result = pluginSelectors.enabledSchema([])(mockState);
61
80
  expect(result).toEqual([]);
@@ -1,9 +1,10 @@
1
1
  import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
2
  import { uniq, uniqBy } from 'lodash-es';
3
+ import { Md5 } from 'ts-md5';
3
4
 
4
- import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
5
+ import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
5
6
  import { ChatCompletionFunctions } from '@/types/openai/chat';
6
- import { LobeToolCustomPlugin } from '@/types/tool/plugin';
7
+ import { InstallPluginMeta, LobeToolCustomPlugin } from '@/types/tool/plugin';
7
8
 
8
9
  import type { ToolStoreState } from '../../initialState';
9
10
 
@@ -60,11 +61,16 @@ const installedPluginManifestList = (s: ToolStoreState) =>
60
61
  .filter((i) => !!i);
61
62
 
62
63
  const installedPluginMetaList = (s: ToolStoreState) =>
63
- installedPlugins(s).map((p) => ({
64
+ installedPlugins(s).map<InstallPluginMeta>((p) => ({
65
+ author: p.manifest?.author,
66
+ createdAt: p.manifest?.createdAt || (p.manifest as any)?.createAt,
67
+ homepage: p.manifest?.homepage,
64
68
  identifier: p.identifier,
65
69
  meta: getPluginMetaById(p.identifier)(s),
66
70
  type: p.type,
67
71
  }));
72
+ const installedCustomPluginMetaList = (s: ToolStoreState) =>
73
+ installedPluginMetaList(s).filter((p) => p.type === 'customPlugin');
68
74
 
69
75
  const isPluginHasUI = (id: string) => (s: ToolStoreState) => {
70
76
  const plugin = getPluginManifestById(id)(s);
@@ -85,10 +91,22 @@ const enabledSchema =
85
91
  )
86
92
  .flatMap((manifest) =>
87
93
  manifest.api.map((m) => {
88
- const pluginType = manifest.type ? `${PLUGIN_SCHEMA_SEPARATOR + manifest.type}` : '';
94
+ const pluginType =
95
+ manifest.type && manifest.type !== 'default'
96
+ ? `${PLUGIN_SCHEMA_SEPARATOR + manifest.type}`
97
+ : '';
89
98
 
90
99
  // 将插件的 identifier 作为前缀,避免重复
91
- const apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name + pluginType;
100
+ let apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name + pluginType;
101
+
102
+ // OpenAI GPT function_call name can't be longer than 64 characters
103
+ // So we need to use md5 to shorten the name
104
+ // and then find the correct apiName in response by md5
105
+ if (apiName.length >= 64) {
106
+ const md5Content = PLUGIN_SCHEMA_API_MD5_PREFIX + Md5.hashStr(m.name).toString();
107
+
108
+ apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + md5Content + pluginType;
109
+ }
92
110
 
93
111
  return {
94
112
  ...m,
@@ -108,6 +126,7 @@ export const pluginSelectors = {
108
126
  getPluginManifestLoadingStatus,
109
127
  getPluginMetaById,
110
128
  getPluginSettingsById,
129
+ installedCustomPluginMetaList,
111
130
  installedPluginManifestList,
112
131
  installedPluginMetaList,
113
132
  installedPlugins,