@lobehub/chat 0.26.0 → 0.27.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 (40) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/package.json +1 -1
  3. package/src/const/message.ts +2 -0
  4. package/src/locales/default/common.ts +3 -0
  5. package/src/locales/default/plugin.ts +19 -1
  6. package/src/pages/api/openai.ts +7 -4
  7. package/src/pages/api/plugins.api.ts +16 -38
  8. package/src/pages/chat/[id]/Conversation/ChatList/MessageExtra.tsx +28 -26
  9. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/FunctionCall.tsx +60 -0
  10. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginMessage.tsx +45 -0
  11. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginResultRender.tsx +51 -0
  12. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/style.ts +25 -0
  13. package/src/pages/chat/[id]/Conversation/ChatList/index.tsx +34 -9
  14. package/src/pages/chat/[id]/edit/AgentPlugin/index.tsx +2 -2
  15. package/src/pages/setting/index.page.tsx +11 -28
  16. package/src/pages/setting/layout.tsx +35 -0
  17. package/src/plugins/Render.ts +11 -0
  18. package/src/plugins/index.ts +7 -3
  19. package/src/plugins/searchEngine/Render.tsx +85 -0
  20. package/src/plugins/searchEngine/index.ts +4 -1
  21. package/src/plugins/searchEngine/runner.ts +7 -73
  22. package/src/plugins/searchEngine/type.ts +82 -0
  23. package/src/plugins/type.ts +17 -0
  24. package/src/plugins/weather/Render.tsx +53 -0
  25. package/src/plugins/weather/index.ts +9 -1
  26. package/src/plugins/weather/runner.ts +12 -7
  27. package/src/plugins/weather/type.ts +36 -0
  28. package/src/plugins/webCrawler/index.ts +4 -1
  29. package/src/plugins/webCrawler/runner.ts +3 -27
  30. package/src/plugins/webCrawler/type.ts +32 -0
  31. package/src/services/chatModel.ts +1 -2
  32. package/src/services/plugin.ts +30 -0
  33. package/src/store/session/slices/chat/actions/message.ts +59 -40
  34. package/src/store/session/slices/chat/selectors/utils.test.ts +3 -2
  35. package/src/store/session/slices/chat/selectors/utils.ts +4 -3
  36. package/src/types/chatMessage.ts +5 -1
  37. package/src/types/openai.ts +3 -0
  38. package/src/types/plugin.ts +4 -0
  39. package/src/utils/message.ts +16 -0
  40. package/src/pages/chat/[id]/Conversation/ChatList/FunctionMessage.tsx +0 -31
@@ -1,8 +1,9 @@
1
1
  import { StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { fetchChatModel } from '@/services/chatModel';
4
+ import { fetchPlugin } from '@/services/plugin';
4
5
  import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
5
- import { ChatMessage } from '@/types/chatMessage';
6
+ import { ChatMessage, OpenAIFunctionCall } from '@/types/chatMessage';
6
7
  import { fetchSSE } from '@/utils/fetch';
7
8
  import { isFunctionMessage } from '@/utils/message';
8
9
  import { nanoid } from '@/utils/uuid';
@@ -42,8 +43,7 @@ export interface ChatMessageAction {
42
43
  generateMessage: (
43
44
  messages: ChatMessage[],
44
45
  assistantMessageId: string,
45
- withPlugin?: boolean,
46
- ) => Promise<{ isFunctionCall: boolean; output: string }>;
46
+ ) => Promise<{ isFunctionCall: boolean }>;
47
47
 
48
48
  /**
49
49
  * 实际获取 AI 响应
@@ -63,6 +63,7 @@ export interface ChatMessageAction {
63
63
  * @param text - 消息文本
64
64
  */
65
65
  sendMessage: (text: string) => Promise<void>;
66
+ triggerFunctionCall: (id: string) => Promise<void>;
66
67
  }
67
68
 
68
69
  export const chatMessage: StateCreator<
@@ -108,16 +109,13 @@ export const chatMessage: StateCreator<
108
109
  get().dispatchSession({ chats, id: activeId, type: 'updateSessionChat' });
109
110
  },
110
111
 
111
- generateMessage: async (messages, assistantId, withPlugin) => {
112
+ generateMessage: async (messages, assistantId) => {
112
113
  const { dispatchMessage } = get();
113
114
  set({ chatLoadingId: assistantId });
114
115
  const config = agentSelectors.currentAgentConfigSafe(get());
115
116
 
116
117
  const fetcher = () =>
117
- fetchChatModel(
118
- { messages, model: config.model, ...config.params, plugins: config.plugins },
119
- { withPlugin },
120
- );
118
+ fetchChatModel({ messages, model: config.model, ...config.params, plugins: config.plugins });
121
119
 
122
120
  let output = '';
123
121
  let isFunctionCall = false;
@@ -139,13 +137,6 @@ export const chatMessage: StateCreator<
139
137
  // 如果是 function call
140
138
  if (isFunctionMessage(output)) {
141
139
  isFunctionCall = true;
142
- // 设为 function
143
- dispatchMessage({
144
- id: assistantId,
145
- key: 'role',
146
- type: 'updateMessage',
147
- value: 'function',
148
- });
149
140
  }
150
141
 
151
142
  // 滚动到最后一条消息
@@ -158,11 +149,11 @@ export const chatMessage: StateCreator<
158
149
 
159
150
  set({ chatLoadingId: undefined });
160
151
 
161
- return { isFunctionCall, output };
152
+ return { isFunctionCall };
162
153
  },
163
154
 
164
155
  realFetchAIResponse: async (messages, userMessageId) => {
165
- const { dispatchMessage, generateMessage, activeTopicId } = get();
156
+ const { dispatchMessage, generateMessage, triggerFunctionCall, activeTopicId } = get();
166
157
 
167
158
  // 添加 systemRole
168
159
  const { systemRole, model } = agentSelectors.currentAgentConfigSafe(get());
@@ -191,31 +182,11 @@ export const chatMessage: StateCreator<
191
182
  dispatchMessage({ id: mid, key: 'fromModel', type: 'updateMessageExtra', value: model });
192
183
 
193
184
  // 生成 ai message
194
- const { output, isFunctionCall } = await generateMessage(messages, mid);
185
+ const { isFunctionCall } = await generateMessage(messages, mid);
195
186
 
196
187
  // 如果是 function,则发送函数调用方法
197
188
  if (isFunctionCall) {
198
- const { function_call } = JSON.parse(output);
199
-
200
- dispatchMessage({
201
- id: mid,
202
- key: 'function_call',
203
- type: 'updateMessage',
204
- value: function_call,
205
- });
206
-
207
- await generateMessage(
208
- [...messages, { content: '', function_call, id: mid, role: 'assistant' } as ChatMessage],
209
- mid,
210
- true,
211
- );
212
-
213
- dispatchMessage({
214
- id: mid,
215
- key: 'role',
216
- type: 'updateMessage',
217
- value: 'assistant',
218
- });
189
+ triggerFunctionCall(mid);
219
190
  }
220
191
  },
221
192
 
@@ -245,7 +216,6 @@ export const chatMessage: StateCreator<
245
216
 
246
217
  await realFetchAIResponse(histories, latestMsg.id);
247
218
  },
248
-
249
219
  sendMessage: async (message) => {
250
220
  const { dispatchMessage, realFetchAIResponse, autocompleteSessionAgentMeta, activeTopicId } =
251
221
  get();
@@ -270,4 +240,53 @@ export const chatMessage: StateCreator<
270
240
  autocompleteSessionAgentMeta(session.id);
271
241
  }
272
242
  },
243
+
244
+ triggerFunctionCall: async (id) => {
245
+ const { dispatchMessage, generateMessage } = get();
246
+ const session = sessionSelectors.currentSession(get());
247
+
248
+ if (!session) return;
249
+
250
+ const message = session.chats[id];
251
+ if (!message) return;
252
+
253
+ let payload: OpenAIFunctionCall = { name: '' };
254
+ if (message.content) {
255
+ const { function_call } = JSON.parse(message.content);
256
+ dispatchMessage({ id, key: 'function_call', type: 'updateMessage', value: function_call });
257
+ dispatchMessage({ id, key: 'content', type: 'updateMessage', value: '' });
258
+ payload = function_call;
259
+ } else {
260
+ if (message.function_call) {
261
+ payload = message.function_call;
262
+ }
263
+ }
264
+
265
+ if (!payload.name) return;
266
+
267
+ // const fid = nanoid();
268
+ dispatchMessage({ id, key: 'role', type: 'updateMessage', value: 'function' });
269
+ dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.name });
270
+ dispatchMessage({ id, key: 'function_call', type: 'updateMessage', value: payload });
271
+
272
+ // dispatchMessage({
273
+ // id: id,
274
+ // message: FUNCTION_LOADING,
275
+ // parentId: message.,
276
+ // role: 'function',
277
+ // type: 'addMessage',
278
+ // });
279
+
280
+ const data = await fetchPlugin(payload);
281
+
282
+ dispatchMessage({ id, key: 'content', type: 'updateMessage', value: data });
283
+
284
+ const mid = nanoid();
285
+
286
+ dispatchMessage({ id: mid, message: LOADING_FLAT, role: 'assistant', type: 'addMessage' });
287
+
288
+ const chats = chatSelectors.currentChats(get());
289
+
290
+ await generateMessage(chats, mid);
291
+ },
273
292
  });
@@ -295,7 +295,7 @@ describe('organizeChats', () => {
295
295
  const result = organizeChats(session, avatar);
296
296
  const meta = result[0].meta;
297
297
 
298
- expect(meta.avatar).toBe(avatar.assistant);
298
+ expect(meta.avatar).toBe('assistant-avatar');
299
299
  expect(meta.title).toBeUndefined();
300
300
  });
301
301
 
@@ -329,6 +329,7 @@ describe('organizeChats', () => {
329
329
  function_call: {
330
330
  name: 'realtimeWeather',
331
331
  },
332
+ name: 'realtimeWeather',
332
333
  } as ChatMessage;
333
334
 
334
335
  session.chats[message.id] = message;
@@ -336,7 +337,7 @@ describe('organizeChats', () => {
336
337
  const result = organizeChats(session, avatar);
337
338
  const meta = result[3].meta;
338
339
 
339
- expect(meta.avatar).toBe('☂️');
340
+ expect(meta.avatar).toBe('🧩');
340
341
  expect(meta.title).toBe('realtimeWeather');
341
342
  });
342
343
  });
@@ -1,4 +1,4 @@
1
- import pluginList from '@/plugins';
1
+ import { PluginsMap } from '@/plugins';
2
2
  import { ChatMessage } from '@/types/chatMessage';
3
3
  import { LobeAgentSession } from '@/types/session';
4
4
 
@@ -27,9 +27,10 @@ export const organizeChats = (
27
27
  }
28
28
 
29
29
  case 'function': {
30
- const plugin = pluginList.find((p) => p.name === message.function_call?.name);
30
+ const plugin = PluginsMap[message.name || ''];
31
+
31
32
  return {
32
- avatar: plugin?.avatar || '🧩',
33
+ avatar: '🧩',
33
34
  title: plugin?.name || 'plugin-unknown',
34
35
  };
35
36
  }
@@ -11,6 +11,10 @@ export interface ChatMessageError {
11
11
  message: string;
12
12
  type: ErrorType;
13
13
  }
14
+ export interface OpenAIFunctionCall {
15
+ arguments?: string;
16
+ name: string;
17
+ }
14
18
 
15
19
  export interface ChatMessage extends BaseDataModel {
16
20
  /**
@@ -29,7 +33,7 @@ export interface ChatMessage extends BaseDataModel {
29
33
  };
30
34
  } & Record<string, any>;
31
35
 
32
- function_call?: { arguments?: string; name: string };
36
+ function_call?: OpenAIFunctionCall;
33
37
  name?: string;
34
38
 
35
39
  parentId?: string;
@@ -1,3 +1,4 @@
1
+ import { OpenAIFunctionCall } from '@/types/chatMessage';
1
2
  import { LLMRoleType } from '@/types/llm';
2
3
 
3
4
  export interface OpenAIChatMessage {
@@ -7,6 +8,8 @@ export interface OpenAIChatMessage {
7
8
  */
8
9
  content: string;
9
10
 
11
+ function_call?: OpenAIFunctionCall;
12
+ name?: string;
10
13
  /**
11
14
  * 角色
12
15
  * @description 消息发送者的角色
@@ -0,0 +1,4 @@
1
+ export interface OpenAIPluginPayload {
2
+ arguments: string;
3
+ name: string;
4
+ }
@@ -3,3 +3,19 @@ import { FUNCTION_MESSAGE_FLAG } from '@/const/message';
3
3
  export const isFunctionMessage = (content: string) => {
4
4
  return content.startsWith(FUNCTION_MESSAGE_FLAG);
5
5
  };
6
+
7
+ // export const createFunctionCallMessage = () => {
8
+ // return [
9
+ // {
10
+ // content: '',
11
+ // function_call: { arguments: '{\n "city": "杭州"\n}', name: 'realtimeWeather' },
12
+ // role: 'assistant',
13
+ // },
14
+ // {
15
+ // content:
16
+ // '{"status":"1","count":"1","info":"OK","infocode":"10000","forecasts":[{"city":"杭州市","adcode":"300","province":"浙江","reporttime":"2023-07-28 20:32:48","casts":[{"date":"2023-07-28","week":"5","dayweather"小雨","nightweather":"小雨-中雨","daytemp":"32","nighttemp":"26","daywind":"东","nightwind":"东","daypower":"6power":"6","daytemp_float":"32.0","nighttemp_float":"26.0"},{"date":"2023-07-29","week":"6","dayweather":"小雨雨","nightweather":"小雨","daytemp":"30","nighttemp":"25","daywind":"东南","nightwind":"东南","daypower":"4","wer":"4","daytemp_float":"30.0","nighttemp_float":"25.0"},{"date":"2023-07-30","week":"7","dayweather":"小雨",ightweather":"雷阵雨","daytemp":"31","nighttemp":"25","daywind":"东南","nightwind":"东南","daypower":"4","nigh:"4","daytemp_float":"31.0","nighttemp_float":"25.0"},{"date":"2023-07-31","week":"1","dayweather":"雷阵雨","ntweather":"雷阵雨","daytemp":"33","nighttemp":"25","daywind":"东","nightwind":"东","daypower":"4","nightpower""daytemp_float":"33.0","nighttemp_float":"25.0"}]}]}',
17
+ // name: 'realtimeWeather',
18
+ // role: 'function',
19
+ // },
20
+ // ];
21
+ // };
@@ -1,31 +0,0 @@
1
- import { LoadingOutlined } from '@ant-design/icons';
2
- import { createStyles } from 'antd-style';
3
- import { memo } from 'react';
4
- import { useTranslation } from 'react-i18next';
5
- import { Flexbox } from 'react-layout-kit';
6
-
7
- const useStyles = createStyles(({ css, token }) => ({
8
- container: css`
9
- padding: 4px 8px;
10
-
11
- color: ${token.colorTextSecondary};
12
-
13
- background: ${token.colorFillTertiary};
14
- border: 1px solid ${token.colorBorder};
15
- border-radius: 6px;
16
- `,
17
- }));
18
- const FunctionMessage = memo(() => {
19
- const { t } = useTranslation('plugin');
20
- const { styles } = useStyles();
21
- return (
22
- <Flexbox className={styles.container} gap={8} horizontal>
23
- <div>
24
- <LoadingOutlined />
25
- </div>
26
- {t('pluginLoading')}
27
- </Flexbox>
28
- );
29
- });
30
-
31
- export default FunctionMessage;