@lobehub/chat 0.94.5 → 0.95.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 (37) hide show
  1. package/.github/workflows/pr-welcome.yml +1 -1
  2. package/CHANGELOG.md +35 -0
  3. package/package.json +4 -4
  4. package/src/app/chat/(desktop)/features/ChatInput.tsx +0 -1
  5. package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +1 -1
  6. package/src/app/chat/features/Conversation/ChatList/Messages/Assistant.tsx +12 -15
  7. package/src/app/chat/features/Conversation/ChatList/Messages/Function.tsx +26 -15
  8. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +36 -0
  9. package/src/app/chat/features/Conversation/ChatList/Plugins/{FunctionCall.tsx → Inspector/index.tsx} +53 -25
  10. package/src/app/chat/features/Conversation/ChatList/Plugins/{IFrameRender → Render/DefaultType/IFrameRender}/index.tsx +5 -16
  11. package/src/app/chat/features/Conversation/ChatList/Plugins/{PluginMessage.tsx → Render/DefaultType/index.tsx} +3 -3
  12. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +136 -0
  13. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/index.tsx +28 -0
  14. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/index.tsx +28 -0
  15. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/iframeOnReady.ts +23 -0
  16. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/listenToPlugin.ts +65 -0
  17. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginSettings.ts +17 -0
  18. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginState.ts +20 -0
  19. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/postMessage.ts +17 -0
  20. package/src/const/plugin.ts +1 -1
  21. package/src/features/PluginSettings/index.tsx +5 -3
  22. package/src/features/PluginSettingsModal/index.tsx +45 -0
  23. package/src/store/plugin/selectors.ts +10 -4
  24. package/src/store/session/slices/chat/actions/index.ts +4 -1
  25. package/src/store/session/slices/chat/actions/message.ts +102 -117
  26. package/src/store/session/slices/chat/actions/plugin.ts +109 -0
  27. package/src/store/session/slices/chat/reducers/message.ts +31 -1
  28. package/src/store/session/slices/chat/selectors/chat.ts +17 -20
  29. package/src/store/session/slices/chat/selectors/index.ts +4 -2
  30. package/src/types/chatMessage.ts +1 -0
  31. package/src/utils/message.ts +8 -1
  32. package/src/app/chat/features/Conversation/ChatList/Plugins/IFrameRender/hooks.ts +0 -29
  33. package/src/app/chat/features/Conversation/ChatList/Plugins/IFrameRender/utils.ts +0 -17
  34. /package/src/app/chat/features/Conversation/ChatList/Plugins/{PluginResultRender.tsx → Inspector/PluginResultJSON.tsx} +0 -0
  35. /package/src/app/chat/features/Conversation/ChatList/Plugins/{style.ts → Inspector/style.ts} +0 -0
  36. /package/src/app/chat/features/Conversation/ChatList/Plugins/{SystemJsRender → Render/DefaultType/SystemJsRender}/index.tsx +0 -0
  37. /package/src/app/chat/features/Conversation/ChatList/Plugins/{SystemJsRender → Render/DefaultType/SystemJsRender}/utils.ts +0 -0
@@ -0,0 +1,65 @@
1
+ import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
2
+ import { useEffect } from 'react';
3
+
4
+ export const useOnPluginFetchMessage = (onRequest: (data: any) => void, deps: any[] = []) => {
5
+ useEffect(() => {
6
+ const fn = (e: MessageEvent) => {
7
+ if (e.data.type === PluginChannel.fetchPluginMessage) {
8
+ onRequest(e.data);
9
+ }
10
+ };
11
+
12
+ window.addEventListener('message', fn);
13
+ return () => {
14
+ window.removeEventListener('message', fn);
15
+ };
16
+ }, deps);
17
+ };
18
+
19
+ export const useOnPluginFetchPluginState = (onRequest: (key: string) => void) => {
20
+ useEffect(() => {
21
+ const fn = (e: MessageEvent) => {
22
+ if (e.data.type === PluginChannel.fetchPluginState) {
23
+ onRequest(e.data.key);
24
+ }
25
+ };
26
+
27
+ window.addEventListener('message', fn);
28
+ return () => {
29
+ window.removeEventListener('message', fn);
30
+ };
31
+ }, []);
32
+ };
33
+
34
+ export const useOnPluginFillContent = (callback: (content: string) => void) => {
35
+ useEffect(() => {
36
+ const fn = (e: MessageEvent) => {
37
+ if (e.data.type === PluginChannel.fillStandalonePluginContent) {
38
+ const data = e.data.content;
39
+ const content = typeof data !== 'string' ? JSON.stringify(data) : data;
40
+
41
+ callback(content);
42
+ }
43
+ };
44
+
45
+ window.addEventListener('message', fn);
46
+ return () => {
47
+ window.removeEventListener('message', fn);
48
+ };
49
+ }, []);
50
+ };
51
+
52
+ export const useOnPluginFetchPluginSettings = (onRequest: () => void) => {
53
+ useEffect(() => {
54
+ const fn = (e: MessageEvent) => {
55
+ if (e.data.type === PluginChannel.fetchPluginSettings) {
56
+ onRequest();
57
+ }
58
+ };
59
+
60
+ window.addEventListener('message', fn);
61
+ return () => {
62
+ window.removeEventListener('message', fn);
63
+ };
64
+ }, []);
65
+ };
@@ -0,0 +1,17 @@
1
+ import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
2
+ import { useEffect } from 'react';
3
+
4
+ export const useOnPluginSettingsUpdate = (callback: (settings: any) => void) => {
5
+ useEffect(() => {
6
+ const fn = (e: MessageEvent) => {
7
+ if (e.data.type === PluginChannel.updatePluginSettings) {
8
+ callback(e.data.value);
9
+ }
10
+ };
11
+
12
+ window.addEventListener('message', fn);
13
+ return () => {
14
+ window.removeEventListener('message', fn);
15
+ };
16
+ }, []);
17
+ };
@@ -0,0 +1,20 @@
1
+ import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
2
+ import { useEffect } from 'react';
3
+
4
+ export const useOnPluginStateUpdate = (callback: (key: string, value: any) => void) => {
5
+ useEffect(() => {
6
+ const fn = (e: MessageEvent) => {
7
+ if (e.data.type === PluginChannel.updatePluginState) {
8
+ const key = e.data.key;
9
+ const value = e.data.value;
10
+
11
+ callback(key, value);
12
+ }
13
+ };
14
+
15
+ window.addEventListener('message', fn);
16
+ return () => {
17
+ window.removeEventListener('message', fn);
18
+ };
19
+ }, []);
20
+ };
@@ -0,0 +1,17 @@
1
+ import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
2
+
3
+ export const sendMessageToPlugin = (window: Window, props: any) => {
4
+ window.postMessage({ props, type: PluginChannel.renderPlugin }, '*');
5
+ };
6
+
7
+ export const sendPayloadToPlugin = (window: Window, props: any) => {
8
+ window.postMessage({ props, type: PluginChannel.initStandalonePlugin }, '*');
9
+ };
10
+
11
+ export const sendPluginStateToPlugin = (window: Window, key: string, value: any) => {
12
+ window.postMessage({ key, type: PluginChannel.renderPluginState, value }, '*');
13
+ };
14
+
15
+ export const sendPluginSettingsToPlugin = (window: Window, settings: any) => {
16
+ window.postMessage({ type: PluginChannel.renderPluginState, value: settings }, '*');
17
+ };
@@ -1 +1 @@
1
- export const PLUGIN_SCHEMA_SEPARATOR = '--__--';
1
+ export const PLUGIN_SCHEMA_SEPARATOR = '____';
@@ -9,6 +9,8 @@ import { usePluginStore } from '@/store/plugin';
9
9
  import PluginSettingRender from './PluginSettingRender';
10
10
 
11
11
  export const transformPluginSettings = (pluginSettings: PluginSchema) => {
12
+ if (!pluginSettings?.properties) return [];
13
+
12
14
  return Object.entries(pluginSettings.properties).map(([name, i]) => ({
13
15
  desc: i.description,
14
16
  format: i.format,
@@ -21,7 +23,7 @@ export const transformPluginSettings = (pluginSettings: PluginSchema) => {
21
23
 
22
24
  interface PluginSettingsConfigProps {
23
25
  id: string;
24
- settings: PluginSchema;
26
+ schema: PluginSchema;
25
27
  }
26
28
 
27
29
  const useStyles = createStyles(({ css, token }) => ({
@@ -32,8 +34,8 @@ const useStyles = createStyles(({ css, token }) => ({
32
34
  `,
33
35
  }));
34
36
 
35
- const PluginSettingsConfig = memo<PluginSettingsConfigProps>(({ settings, id }) => {
36
- const items = transformPluginSettings(settings);
37
+ const PluginSettingsConfig = memo<PluginSettingsConfigProps>(({ schema, id }) => {
38
+ const items = transformPluginSettings(schema);
37
39
 
38
40
  const { styles } = useStyles();
39
41
  const [updatePluginSettings] = usePluginStore((s) => [s.updatePluginSettings]);
@@ -0,0 +1,45 @@
1
+ import { Avatar, Modal } from '@lobehub/ui';
2
+ import { Divider, Typography } from 'antd';
3
+ import { useTheme } from 'antd-style';
4
+ import isEqual from 'fast-deep-equal';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Center, Flexbox } from 'react-layout-kit';
8
+
9
+ import PluginSettingsConfig from '@/features/PluginSettings';
10
+ import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
11
+
12
+ interface PluginSettingsModalProps {
13
+ id: string;
14
+ onClose: () => void;
15
+ open?: boolean;
16
+ schema: any;
17
+ }
18
+
19
+ const PluginSettingsModal = memo<PluginSettingsModalProps>(({ schema, onClose, id, open }) => {
20
+ const pluginMeta = usePluginStore(pluginSelectors.getPluginMetaById(id), isEqual);
21
+
22
+ const { t } = useTranslation('plugin');
23
+ const theme = useTheme();
24
+ return (
25
+ <Modal onCancel={onClose} open={open} title={t('setting')} width={600}>
26
+ <Center gap={16}>
27
+ <Avatar
28
+ avatar={pluginHelpers.getPluginAvatar(pluginMeta?.meta) || '⚙️'}
29
+ background={theme.colorFillContent}
30
+ gap={12}
31
+ size={64}
32
+ />
33
+
34
+ <Flexbox style={{ fontSize: 20 }}>{pluginHelpers.getPluginTitle(pluginMeta?.meta)}</Flexbox>
35
+ <Typography.Text type={'secondary'}>
36
+ {pluginHelpers.getPluginDesc(pluginMeta?.meta)}
37
+ </Typography.Text>
38
+ <Divider style={{ marginBottom: 0, marginTop: 8 }} />
39
+ {schema && <PluginSettingsConfig id={id} schema={schema} />}
40
+ </Center>
41
+ </Modal>
42
+ );
43
+ });
44
+
45
+ export default PluginSettingsModal;
@@ -18,11 +18,17 @@ const enabledSchema =
18
18
  return enabledPlugins.includes(p.identifier);
19
19
  })
20
20
  .flatMap((manifest) =>
21
- manifest.api.map((m) => ({
22
- ...m,
21
+ manifest.api.map((m) => {
22
+ const pluginType = manifest.type ? `${PLUGIN_SCHEMA_SEPARATOR + manifest.type}` : '';
23
+
23
24
  // 将插件的 identifier 作为前缀,避免重复
24
- name: manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name,
25
- })),
25
+ const apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name + pluginType;
26
+
27
+ return {
28
+ ...m,
29
+ name: apiName,
30
+ };
31
+ }),
26
32
  );
27
33
 
28
34
  return uniqBy(list, 'name');
@@ -3,6 +3,7 @@ import { StateCreator } from 'zustand/vanilla';
3
3
  import { SessionStore } from '@/store/session';
4
4
 
5
5
  import { ChatMessageAction, chatMessage } from './message';
6
+ import { ChatPluginAction, chatPlugin } from './plugin';
6
7
  import { ShareAction, chatShare } from './share';
7
8
  import { ChatTopicAction, chatTopic } from './topic';
8
9
  import { ChatTranslateAction, chatTranslate } from './translate';
@@ -14,7 +15,8 @@ export interface ChatAction
14
15
  extends ChatTopicAction,
15
16
  ChatMessageAction,
16
17
  ShareAction,
17
- ChatTranslateAction {}
18
+ ChatTranslateAction,
19
+ ChatPluginAction {}
18
20
 
19
21
  export const createChatSlice: StateCreator<
20
22
  SessionStore,
@@ -26,4 +28,5 @@ export const createChatSlice: StateCreator<
26
28
  ...chatMessage(...params),
27
29
  ...chatShare(...params),
28
30
  ...chatTranslate(...params),
31
+ ...chatPlugin(...params),
29
32
  });
@@ -1,15 +1,12 @@
1
- import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2
1
  import { template } from 'lodash-es';
3
2
  import { StateCreator } from 'zustand/vanilla';
4
3
 
5
4
  import { LOADING_FLAT } from '@/const/message';
6
- import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
7
5
  import { fetchChatModel } from '@/services/chatModel';
8
- import { fetchPlugin } from '@/services/plugin';
9
6
  import { SessionStore } from '@/store/session';
10
- import { ChatMessage, OpenAIFunctionCall } from '@/types/chatMessage';
7
+ import { ChatMessage } from '@/types/chatMessage';
11
8
  import { fetchSSE } from '@/utils/fetch';
12
- import { isFunctionMessage } from '@/utils/message';
9
+ import { isFunctionMessageAtStart, testFunctionMessageAtEnd } from '@/utils/message';
13
10
  import { setNamespace } from '@/utils/storeDebug';
14
11
  import { nanoid } from '@/utils/uuid';
15
12
 
@@ -29,6 +26,12 @@ export interface ChatMessageAction {
29
26
  * 清除消息
30
27
  */
31
28
  clearMessage: () => void;
29
+ /**
30
+ * 处理 ai 消息的核心逻辑(包含前处理与后处理)
31
+ * @param messages - 聊天消息数组
32
+ * @param parentId - 父消息 ID,可选
33
+ */
34
+ coreProcessMessage: (messages: ChatMessage[], parentId: string) => Promise<void>;
32
35
  /**
33
36
  * 删除消息
34
37
  * @param id - 消息 ID
@@ -40,21 +43,19 @@ export interface ChatMessageAction {
40
43
  */
41
44
  dispatchMessage: (payload: MessageDispatch) => void;
42
45
  /**
43
- * 生成消息
46
+ * 实际获取 AI 响应
44
47
  * @param messages - 聊天消息数组
45
48
  * @param options - 获取 SSE 选项
46
49
  */
47
- generateMessage: (
50
+ fetchAIChatMessage: (
48
51
  messages: ChatMessage[],
49
52
  assistantMessageId: string,
50
- ) => Promise<{ isFunctionCall: boolean }>;
51
- /**
52
- * 实际获取 AI 响应
53
- *
54
- * @param messages - 聊天消息数组
55
- * @param parentId - 父消息 ID,可选
56
- */
57
- realFetchAIResponse: (messages: ChatMessage[], parentId: string) => Promise<void>;
53
+ ) => Promise<{
54
+ content: string;
55
+ functionCallAtEnd: boolean;
56
+ functionCallContent: string;
57
+ isFunctionCall: boolean;
58
+ }>;
58
59
 
59
60
  /**
60
61
  * 重新发送消息
@@ -73,8 +74,6 @@ export interface ChatMessageAction {
73
74
  id?: string,
74
75
  action?: string,
75
76
  ) => AbortController | undefined;
76
-
77
- triggerFunctionCall: (id: string) => Promise<void>;
78
77
  }
79
78
 
80
79
  export const chatMessage: StateCreator<
@@ -93,10 +92,74 @@ export const chatMessage: StateCreator<
93
92
  }
94
93
  },
95
94
 
95
+ coreProcessMessage: async (messages, userMessageId) => {
96
+ const { dispatchMessage, fetchAIChatMessage, triggerFunctionCall, activeTopicId } = get();
97
+
98
+ const { model } = agentSelectors.currentAgentConfig(get());
99
+
100
+ // 添加一个空的信息用于放置 ai 响应,注意顺序不能反
101
+ // 因为如果顺序反了,messages 中将包含新增的 ai message
102
+ const mid = nanoid();
103
+
104
+ dispatchMessage({
105
+ id: mid,
106
+ message: LOADING_FLAT,
107
+ parentId: userMessageId,
108
+ role: 'assistant',
109
+ type: 'addMessage',
110
+ });
111
+
112
+ // 如果有 activeTopicId,则添加 topicId
113
+ if (activeTopicId) {
114
+ dispatchMessage({ id: mid, key: 'topicId', type: 'updateMessage', value: activeTopicId });
115
+ }
116
+
117
+ // 为模型添加 fromModel 的额外信息
118
+ dispatchMessage({ id: mid, key: 'fromModel', type: 'updateMessageExtra', value: model });
119
+
120
+ // 生成 ai message
121
+ const { isFunctionCall, content, functionCallAtEnd, functionCallContent } =
122
+ await fetchAIChatMessage(messages, mid);
123
+
124
+ // 如果是 function,则发送函数调用方法
125
+ if (isFunctionCall) {
126
+ let functionId = mid;
127
+
128
+ if (functionCallAtEnd) {
129
+ // create a new separate message and remove the function call from the prev message
130
+ dispatchMessage({
131
+ id: mid,
132
+ key: 'content',
133
+ type: 'updateMessage',
134
+ value: content.replace(functionCallContent, ''),
135
+ });
136
+
137
+ functionId = nanoid();
138
+ dispatchMessage({
139
+ id: functionId,
140
+ message: functionCallContent,
141
+ parentId: userMessageId,
142
+ role: 'assistant',
143
+ type: 'addMessage',
144
+ });
145
+
146
+ // also add activeTopicId
147
+ if (activeTopicId)
148
+ dispatchMessage({
149
+ id: functionId,
150
+ key: 'topicId',
151
+ type: 'updateMessage',
152
+ value: activeTopicId,
153
+ });
154
+ }
155
+
156
+ triggerFunctionCall(functionId);
157
+ }
158
+ },
159
+
96
160
  deleteMessage: (id) => {
97
161
  get().dispatchMessage({ id, type: 'deleteMessage' });
98
162
  },
99
-
100
163
  dispatchMessage: (payload) => {
101
164
  const { activeId } = get();
102
165
  const session = sessionSelectors.currentSession(get());
@@ -106,7 +169,8 @@ export const chatMessage: StateCreator<
106
169
 
107
170
  get().dispatchSession({ chats, id: activeId, type: 'updateSessionChat' });
108
171
  },
109
- generateMessage: async (messages, assistantId) => {
172
+
173
+ fetchAIChatMessage: async (messages, assistantId) => {
110
174
  const { dispatchMessage, toggleChatLoading } = get();
111
175
 
112
176
  const abortController = toggleChatLoading(
@@ -160,6 +224,8 @@ export const chatMessage: StateCreator<
160
224
 
161
225
  let output = '';
162
226
  let isFunctionCall = false;
227
+ let functionCallAtEnd = false;
228
+ let functionCallContent = '';
163
229
 
164
230
  await fetchSSE(fetcher, {
165
231
  onErrorHandle: (error) => {
@@ -168,15 +234,10 @@ export const chatMessage: StateCreator<
168
234
  onMessageHandle: (text) => {
169
235
  output += text;
170
236
 
171
- dispatchMessage({
172
- id: assistantId,
173
- key: 'content',
174
- type: 'updateMessage',
175
- value: output,
176
- });
237
+ dispatchMessage({ id: assistantId, key: 'content', type: 'updateMessage', value: output });
177
238
 
178
- // 如果是 function call
179
- if (isFunctionMessage(output)) {
239
+ // is this message is just a function call
240
+ if (isFunctionMessageAtStart(output)) {
180
241
  isFunctionCall = true;
181
242
  }
182
243
  },
@@ -184,41 +245,19 @@ export const chatMessage: StateCreator<
184
245
 
185
246
  toggleChatLoading(false, undefined, t('generateMessage(end)') as string);
186
247
 
187
- return { isFunctionCall };
188
- },
189
-
190
- realFetchAIResponse: async (messages, userMessageId) => {
191
- const { dispatchMessage, generateMessage, triggerFunctionCall, activeTopicId } = get();
192
-
193
- const { model } = agentSelectors.currentAgentConfig(get());
248
+ // also exist message like this: 请稍等,我帮您查询一下。{"function_call": {"name": "plugin-identifier____recommendClothes____standalone", "arguments": "{\n "mood": "",\n "gender": "man"\n}"}}
249
+ if (!isFunctionCall) {
250
+ const { content, valid } = testFunctionMessageAtEnd(output);
194
251
 
195
- // 添加一个空的信息用于放置 ai 响应,注意顺序不能反
196
- // 因为如果顺序反了,messages 中将包含新增的 ai message
197
- const mid = nanoid();
198
-
199
- dispatchMessage({
200
- id: mid,
201
- message: LOADING_FLAT,
202
- parentId: userMessageId,
203
- role: 'assistant',
204
- type: 'addMessage',
205
- });
206
-
207
- // 如果有 activeTopicId,则添加 topicId
208
- if (activeTopicId) {
209
- dispatchMessage({ id: mid, key: 'topicId', type: 'updateMessage', value: activeTopicId });
252
+ // if fc at end, replace the message
253
+ if (valid) {
254
+ isFunctionCall = true;
255
+ functionCallAtEnd = true;
256
+ functionCallContent = content;
257
+ }
210
258
  }
211
259
 
212
- // 为模型添加 fromModel 的额外信息
213
- dispatchMessage({ id: mid, key: 'fromModel', type: 'updateMessageExtra', value: model });
214
-
215
- // 生成 ai message
216
- const { isFunctionCall } = await generateMessage(messages, mid);
217
-
218
- // 如果是 function,则发送函数调用方法
219
- if (isFunctionCall) {
220
- triggerFunctionCall(mid);
221
- }
260
+ return { content: output, functionCallAtEnd, functionCallContent, isFunctionCall };
222
261
  },
223
262
 
224
263
  resendMessage: async (messageId) => {
@@ -254,17 +293,17 @@ export const chatMessage: StateCreator<
254
293
 
255
294
  if (contextMessages.length <= 0) return;
256
295
 
257
- const { realFetchAIResponse } = get();
296
+ const { coreProcessMessage } = get();
258
297
 
259
298
  const latestMsg = contextMessages.filter((s) => s.role === 'user').at(-1);
260
299
 
261
300
  if (!latestMsg) return;
262
301
 
263
- await realFetchAIResponse(contextMessages, latestMsg.id);
302
+ await coreProcessMessage(contextMessages, latestMsg.id);
264
303
  },
265
304
 
266
305
  sendMessage: async (message) => {
267
- const { dispatchMessage, realFetchAIResponse, activeTopicId } = get();
306
+ const { dispatchMessage, coreProcessMessage, activeTopicId } = get();
268
307
  const session = sessionSelectors.currentSession(get());
269
308
  if (!session || !message) return;
270
309
 
@@ -279,7 +318,7 @@ export const chatMessage: StateCreator<
279
318
  // Get the current messages to generate AI response
280
319
  const messages = chatSelectors.currentChats(get());
281
320
 
282
- await realFetchAIResponse(messages, userId);
321
+ await coreProcessMessage(messages, userId);
283
322
 
284
323
  // check activeTopic and then auto create topic
285
324
  const chats = chatSelectors.currentChats(get());
@@ -308,58 +347,4 @@ export const chatMessage: StateCreator<
308
347
  set({ abortController: undefined, chatLoadingId: undefined }, false, action);
309
348
  }
310
349
  },
311
-
312
- triggerFunctionCall: async (id) => {
313
- const { dispatchMessage, realFetchAIResponse, toggleChatLoading } = get();
314
- const session = sessionSelectors.currentSession(get());
315
-
316
- if (!session) return;
317
-
318
- const message = session.chats[id];
319
- if (!message) return;
320
-
321
- let payload: PluginRequestPayload = { apiName: '', identifier: '' };
322
- // 识别到内容是 function_call 的情况下
323
- // 将 function_call 转换为 plugin request payload
324
- if (message.content) {
325
- const { function_call } = JSON.parse(message.content) as {
326
- function_call: OpenAIFunctionCall;
327
- };
328
-
329
- const [identifier, apiName] = function_call.name.split(PLUGIN_SCHEMA_SEPARATOR);
330
- payload = { apiName, arguments: function_call.arguments, identifier };
331
-
332
- dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
333
- dispatchMessage({ id, key: 'content', type: 'updateMessage', value: '' });
334
- } else {
335
- if (message.plugin) {
336
- payload = message.plugin;
337
- }
338
- }
339
-
340
- if (!payload.apiName) return;
341
-
342
- dispatchMessage({ id, key: 'role', type: 'updateMessage', value: 'function' });
343
- dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.identifier });
344
- dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
345
-
346
- let data: string;
347
- try {
348
- const abortController = toggleChatLoading(true, id);
349
- data = await fetchPlugin(payload, { signal: abortController?.signal });
350
- } catch (error) {
351
- dispatchMessage({ id, key: 'error', type: 'updateMessage', value: error });
352
-
353
- data = '';
354
- }
355
- toggleChatLoading(false);
356
- // 如果报错则结束了
357
- if (!data) return;
358
-
359
- dispatchMessage({ id, key: 'content', type: 'updateMessage', value: data });
360
-
361
- const chats = chatSelectors.currentChats(get());
362
-
363
- await realFetchAIResponse(chats, message.id);
364
- },
365
350
  });
@@ -0,0 +1,109 @@
1
+ import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2
+ import { StateCreator } from 'zustand/vanilla';
3
+
4
+ import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
5
+ import { fetchPlugin } from '@/services/plugin';
6
+ import { SessionStore } from '@/store/session';
7
+ import { OpenAIFunctionCall } from '@/types/chatMessage';
8
+ import { setNamespace } from '@/utils/storeDebug';
9
+
10
+ import { sessionSelectors } from '../../session/selectors';
11
+ import { chatSelectors } from '../selectors';
12
+
13
+ const t = setNamespace('chat/plugin');
14
+
15
+ /**
16
+ * 插件方法
17
+ */
18
+ export interface ChatPluginAction {
19
+ fillPluginMessageContent: (id: string, content: string) => Promise<void>;
20
+ runPluginDefaultType: (id: string, payload: any) => Promise<void>;
21
+ triggerFunctionCall: (id: string) => Promise<void>;
22
+ updatePluginState: (id: string, key: string, value: any) => void;
23
+ }
24
+
25
+ export const chatPlugin: StateCreator<
26
+ SessionStore,
27
+ [['zustand/devtools', never]],
28
+ [],
29
+ ChatPluginAction
30
+ > = (set, get) => ({
31
+ fillPluginMessageContent: async (id, content) => {
32
+ const { dispatchMessage, coreProcessMessage } = get();
33
+
34
+ dispatchMessage({ id, key: 'content', type: 'updateMessage', value: content });
35
+
36
+ const chats = chatSelectors.currentChats(get());
37
+ await coreProcessMessage(chats, id);
38
+ },
39
+ runPluginDefaultType: async (id, payload) => {
40
+ const { dispatchMessage, coreProcessMessage, toggleChatLoading } = get();
41
+ let data: string;
42
+ try {
43
+ const abortController = toggleChatLoading(true, id, t('fetchPlugin') as string);
44
+ data = await fetchPlugin(payload, { signal: abortController?.signal });
45
+ } catch (error) {
46
+ dispatchMessage({ id, key: 'error', type: 'updateMessage', value: error });
47
+
48
+ data = '';
49
+ }
50
+ toggleChatLoading(false);
51
+ // 如果报错则结束了
52
+ if (!data) return;
53
+
54
+ dispatchMessage({ id, key: 'content', type: 'updateMessage', value: data });
55
+
56
+ const chats = chatSelectors.currentChats(get());
57
+ await coreProcessMessage(chats, id);
58
+ },
59
+ triggerFunctionCall: async (id) => {
60
+ const { dispatchMessage, runPluginDefaultType } = get();
61
+ const session = sessionSelectors.currentSession(get());
62
+
63
+ if (!session) return;
64
+
65
+ const message = session.chats[id];
66
+ if (!message) return;
67
+
68
+ let payload: PluginRequestPayload = { apiName: '', identifier: '' };
69
+
70
+ // 识别到内容是 function_call 的情况下
71
+ // 将 function_call 转换为 plugin request payload
72
+ if (message.content) {
73
+ const { function_call } = JSON.parse(message.content) as {
74
+ function_call: OpenAIFunctionCall;
75
+ };
76
+
77
+ const [identifier, apiName, type] = function_call.name.split(PLUGIN_SCHEMA_SEPARATOR);
78
+
79
+ payload = {
80
+ apiName,
81
+ arguments: function_call.arguments,
82
+ identifier,
83
+ type: type ?? 'default',
84
+ };
85
+
86
+ dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
87
+ dispatchMessage({ id, key: 'content', type: 'updateMessage', value: '' });
88
+ } else {
89
+ if (message.plugin) {
90
+ payload = message.plugin;
91
+ }
92
+ }
93
+
94
+ if (!payload.apiName) return;
95
+
96
+ dispatchMessage({ id, key: 'role', type: 'updateMessage', value: 'function' });
97
+ dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.identifier });
98
+ dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
99
+
100
+ if (payload.type === 'standalone') {
101
+ // nothing to do
102
+ } else runPluginDefaultType(id, payload);
103
+ },
104
+ updatePluginState: (id, key, value) => {
105
+ const { dispatchMessage } = get();
106
+
107
+ dispatchMessage({ id, key, type: 'updatePluginState', value });
108
+ },
109
+ });