@lobehub/chat 0.55.1 → 0.56.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 CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.56.0](https://github.com/lobehub/lobe-chat/compare/v0.55.1...v0.56.0)
6
+
7
+ <sup>Released on **2023-08-24**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Use new plugin manifest to support plugin’s multi api.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Use new plugin manifest to support plugin’s multi api, closes [#101](https://github.com/lobehub/lobe-chat/issues/101) ([4534598](https://github.com/lobehub/lobe-chat/commit/4534598))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 0.55.1](https://github.com/lobehub/lobe-chat/compare/v0.55.0...v0.55.1)
6
31
 
7
32
  <sup>Released on **2023-08-22**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.55.1",
3
+ "version": "0.56.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -68,7 +68,7 @@
68
68
  "@emoji-mart/react": "^1",
69
69
  "@icons-pack/react-simple-icons": "^8",
70
70
  "@lobehub/chat-plugin-sdk": "latest",
71
- "@lobehub/chat-plugins-gateway": "^1.2.2",
71
+ "@lobehub/chat-plugins-gateway": "latest",
72
72
  "@lobehub/ui": "latest",
73
73
  "@vercel/analytics": "^1",
74
74
  "ahooks": "^3",
@@ -0,0 +1 @@
1
+ export const PLUGIN_SCHEMA_SEPARATOR = '--__--';
@@ -75,23 +75,23 @@ const AgentPlugin = memo<AgentPluginProps>(({ config, updateConfig }) => {
75
75
  <Switch
76
76
  checked={
77
77
  // 如果在加载中,说明激活了
78
- pluginManifestLoading[item.name] || !config.plugins
78
+ pluginManifestLoading[item.identifier] || !config.plugins
79
79
  ? false
80
- : config.plugins.includes(item.name)
80
+ : config.plugins.includes(item.identifier)
81
81
  }
82
- loading={pluginManifestLoading[item.name]}
82
+ loading={pluginManifestLoading[item.identifier]}
83
83
  onChange={(checked) => {
84
- updateConfig(item.name, checked);
84
+ updateConfig(item.identifier, checked);
85
85
  if (checked) {
86
- fetchPluginManifest(item.name);
86
+ fetchPluginManifest(item.identifier);
87
87
  }
88
88
  }}
89
89
  />
90
90
  ),
91
91
  desc: item.meta?.description,
92
- label: t(`plugins.${item.name}` as any, { ns: 'plugin' }),
92
+ label: t(`plugins.${item.identifier}` as any, { ns: 'plugin' }),
93
93
  minWidth: undefined,
94
- tag: item.name,
94
+ tag: item.identifier,
95
95
  }));
96
96
 
97
97
  return {
@@ -1,12 +1,14 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
3
  import { useGlobalStore } from '@/store/global';
4
+ import { usePluginStore } from '@/store/plugin';
4
5
  import { useSessionStore } from '@/store/session';
5
6
  import { importConfigFile } from '@/utils/config';
6
7
 
7
8
  export const useImportConfig = () => {
8
9
  const importSessions = useSessionStore((s) => s.importSessions);
9
10
  const importAppSettings = useGlobalStore((s) => s.importAppSettings);
11
+ const checkLocalEnabledPlugins = usePluginStore((s) => s.checkLocalEnabledPlugins);
10
12
 
11
13
  const importConfig = (info: any) => {
12
14
  importConfigFile(info, (config) => {
@@ -19,6 +21,9 @@ export const useImportConfig = () => {
19
21
  case 'sessions':
20
22
  case 'agents': {
21
23
  importSessions(config.state.sessions);
24
+
25
+ // 检查一下插件开启情况
26
+ checkLocalEnabledPlugins(config.state.sessions);
22
27
  break;
23
28
  }
24
29
 
@@ -26,6 +31,8 @@ export const useImportConfig = () => {
26
31
  importSessions(config.state.sessions);
27
32
  importAppSettings(config.state.settings);
28
33
 
34
+ // 检查一下插件开启情况
35
+ checkLocalEnabledPlugins(config.state.sessions);
29
36
  break;
30
37
  }
31
38
  }
@@ -12,7 +12,7 @@ export default {
12
12
  plugins: {
13
13
  realtimeWeather: '实时天气预报',
14
14
  searchEngine: '搜索引擎',
15
- undefined: '插件检测中...',
15
+ unknown: '插件检测中...',
16
16
  websiteCrawler: '网页内容提取',
17
17
  },
18
18
  };
@@ -7,77 +7,81 @@ import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import { pluginSelectors, usePluginStore } from '@/store/plugin';
10
- import { OpenAIFunctionCall } from '@/types/chatMessage';
11
10
 
12
11
  import PluginResult from './PluginResultRender';
13
12
  import { useStyles } from './style';
14
13
 
15
14
  export interface FunctionCallProps {
15
+ arguments?: string;
16
+ command?: any;
16
17
  content: string;
17
- function_call?: OpenAIFunctionCall;
18
+ id?: string;
18
19
  loading?: boolean;
19
20
  }
20
21
 
21
- const FunctionCall = memo<FunctionCallProps>(({ function_call, loading, content }) => {
22
- const { t } = useTranslation('plugin');
23
- const { styles } = useStyles();
24
- const [open, setOpen] = useState(false);
25
- const item = usePluginStore(pluginSelectors.getPluginMetaByName(function_call?.name || ''));
22
+ const FunctionCall = memo<FunctionCallProps>(
23
+ ({ arguments: requestArgs = '{}', command, loading, content, id = 'unknown' }) => {
24
+ const { t } = useTranslation('plugin');
25
+ const { styles } = useStyles();
26
+ const [open, setOpen] = useState(false);
26
27
 
27
- const avatar = item?.meta.avatar ? (
28
- <Avatar avatar={item?.meta.avatar} size={32} />
29
- ) : (
30
- <Icon icon={LucideToyBrick} />
31
- );
28
+ const item = usePluginStore(pluginSelectors.getPluginMetaById(id));
32
29
 
33
- const args = JSON.stringify(function_call, null, 2);
34
- const params = JSON.stringify(JSON.parse(function_call?.arguments || '{}'), null, 2);
30
+ const avatar = item?.meta.avatar ? (
31
+ <Avatar avatar={item?.meta.avatar} size={32} />
32
+ ) : (
33
+ <Icon icon={LucideToyBrick} />
34
+ );
35
35
 
36
- return (
37
- <Flexbox gap={8}>
38
- <Flexbox
39
- align={'center'}
40
- className={styles.container}
41
- gap={8}
42
- horizontal
43
- onClick={() => {
44
- setOpen(!open);
45
- }}
46
- >
47
- {loading ? (
48
- <div>
49
- <LoadingOutlined />
50
- </div>
51
- ) : (
52
- avatar
36
+ const args = JSON.stringify(command, null, 2);
37
+ const params = JSON.stringify(JSON.parse(requestArgs), null, 2);
38
+
39
+ return (
40
+ <Flexbox gap={8}>
41
+ <Flexbox
42
+ align={'center'}
43
+ className={styles.container}
44
+ gap={8}
45
+ horizontal
46
+ onClick={() => {
47
+ setOpen(!open);
48
+ }}
49
+ >
50
+ {loading ? (
51
+ <div>
52
+ <LoadingOutlined />
53
+ </div>
54
+ ) : (
55
+ avatar
56
+ )}
57
+ {t(`plugins.${id}` as any, { ns: 'plugin' })}
58
+ <Icon icon={open ? LucideChevronUp : LucideChevronDown} />
59
+ </Flexbox>
60
+ {open && (
61
+ <Tabs
62
+ items={[
63
+ {
64
+ children: <Highlighter language={'json'}>{args}</Highlighter>,
65
+ key: 'function_call',
66
+ label: t('debug.function_call'),
67
+ },
68
+ {
69
+ children: <Highlighter language={'json'}>{params}</Highlighter>,
70
+ key: 'arguments',
71
+ label: t('debug.arguments'),
72
+ },
73
+ {
74
+ children: <PluginResult content={content} />,
75
+ key: 'response',
76
+ label: t('debug.response'),
77
+ },
78
+ ]}
79
+ style={{ maxWidth: 800 }}
80
+ />
53
81
  )}
54
- {t(`plugins.${function_call?.name}` as any, { ns: 'plugin' })}
55
- <Icon icon={open ? LucideChevronUp : LucideChevronDown} />
56
82
  </Flexbox>
57
- {open && (
58
- <Tabs
59
- items={[
60
- {
61
- children: <Highlighter language={'json'}>{args}</Highlighter>,
62
- key: 'function_call',
63
- label: t('debug.function_call'),
64
- },
65
- {
66
- children: <Highlighter language={'json'}>{params}</Highlighter>,
67
- key: 'arguments',
68
- label: t('debug.arguments'),
69
- },
70
- {
71
- children: <PluginResult content={content} />,
72
- key: 'response',
73
- label: t('debug.response'),
74
- },
75
- ]}
76
- style={{ maxWidth: 800 }}
77
- />
78
- )}
79
- </Flexbox>
80
- );
81
- });
83
+ );
84
+ },
85
+ );
82
86
 
83
87
  export default FunctionCall;
@@ -49,25 +49,28 @@ const List = () => {
49
49
 
50
50
  const renderMessage: RenderMessage = useCallback(
51
51
  (content, message: ChatMessage) => {
52
+ const id = message.plugin?.identifier || message.function_call?.name;
53
+ const command = message.plugin ?? message.function_call;
54
+ const args = command?.arguments;
55
+ const fcProps = {
56
+ arguments: args,
57
+ command,
58
+ content: message.content,
59
+ id,
60
+ loading: message.id === chatLoadingId,
61
+ };
62
+
52
63
  if (message.role === 'function')
53
64
  return (
54
65
  <Flexbox gap={12}>
55
- <FunctionCall
56
- content={message.content}
57
- function_call={message.function_call}
58
- loading={message.id === chatLoadingId}
59
- />
66
+ <FunctionCall {...fcProps} />
60
67
  <PluginMessage loading={message.id === chatLoadingId} {...message} />
61
68
  </Flexbox>
62
69
  );
63
70
 
64
71
  if (message.role === 'assistant') {
65
72
  return isFunctionMessage(message.content) || !!message.function_call ? (
66
- <FunctionCall
67
- content={message.content}
68
- function_call={message.function_call}
69
- loading={message.id === chatLoadingId}
70
- />
73
+ <FunctionCall {...fcProps} />
71
74
  ) : (
72
75
  content
73
76
  );
@@ -19,7 +19,7 @@ const PluginTag = memo<PluginTagProps>(({ plugins }) => {
19
19
  if (plugins.length === 0) return null;
20
20
 
21
21
  const items: MenuProps['items'] = plugins.map((id) => {
22
- const item = list?.find((p) => p.name === id);
22
+ const item = list?.find((p) => p.identifier === id);
23
23
  return {
24
24
  icon: (
25
25
  <Avatar avatar={item?.meta.avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />
@@ -1,5 +1,5 @@
1
1
  import {
2
- LobeChatPlugin,
2
+ LobeChatPluginManifest,
3
3
  LobeChatPluginsMarketIndex,
4
4
  pluginManifestSchema,
5
5
  } from '@lobehub/chat-plugin-sdk';
@@ -10,6 +10,7 @@ import useSWR, { SWRResponse } from 'swr';
10
10
  import { StateCreator } from 'zustand/vanilla';
11
11
 
12
12
  import { getPluginList } from '@/services/plugin';
13
+ import { pluginSelectors } from '@/store/plugin/selectors';
13
14
  import { LobeSessions } from '@/types/session';
14
15
  import { setNamespace } from '@/utils/storeDebug';
15
16
 
@@ -51,7 +52,6 @@ export const createPluginSlice: StateCreator<
51
52
 
52
53
  await Promise.all(plugins.map((name) => fetchPluginManifest(name)));
53
54
 
54
- console.log('fetched');
55
55
  set({ manifestPrepared: true }, false, t('checkLocalEnabledPlugins'));
56
56
  },
57
57
  dispatchPluginManifest: (payload) => {
@@ -62,7 +62,7 @@ export const createPluginSlice: StateCreator<
62
62
  },
63
63
 
64
64
  fetchPluginManifest: async (name) => {
65
- const plugin = get().pluginList.find((plugin) => plugin.name === name);
65
+ const plugin = pluginSelectors.getPluginMetaById(name)(get());
66
66
  // 1. 校验文件
67
67
 
68
68
  if (!plugin) return;
@@ -74,7 +74,7 @@ export const createPluginSlice: StateCreator<
74
74
 
75
75
  // 2. 发送请求
76
76
  get().updateManifestLoadingState(name, true);
77
- let data: LobeChatPlugin | null;
77
+ let data: LobeChatPluginManifest | null;
78
78
 
79
79
  try {
80
80
  const res = await fetch(plugin.manifest);
@@ -100,7 +100,7 @@ export const createPluginSlice: StateCreator<
100
100
  }
101
101
 
102
102
  // 4. 存储 manifest 信息
103
- get().dispatchPluginManifest({ id: plugin.name, plugin: data, type: 'addManifest' });
103
+ get().dispatchPluginManifest({ id: plugin.identifier, plugin: data, type: 'addManifest' });
104
104
  },
105
105
 
106
106
  updateManifestLoadingState: (key, value) => {
@@ -1,9 +1,9 @@
1
- import { LobeChatPlugin } from '@lobehub/chat-plugin-sdk';
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
2
  import { produce } from 'immer';
3
3
 
4
4
  import { PluginManifestMap } from '@/types/plugin';
5
5
 
6
- type AddManifestDispatch = { id: string; plugin: LobeChatPlugin; type: 'addManifest' };
6
+ type AddManifestDispatch = { id: string; plugin: LobeChatPluginManifest; type: 'addManifest' };
7
7
  type DeleteManifestDispatch = { id: string; type: 'deleteManifest' };
8
8
  // type UpdateManifestDispatch = {
9
9
  // id: string;
@@ -1,3 +1,5 @@
1
+ import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
2
+
1
3
  import { PluginStoreState } from './initialState';
2
4
 
3
5
  const enabledSchema =
@@ -9,15 +11,21 @@ const enabledSchema =
9
11
  if (!enabledPlugins) return false;
10
12
 
11
13
  // 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
12
- return enabledPlugins.includes(p.name);
14
+ return enabledPlugins.includes(p.identifier);
13
15
  })
14
- .map((i) => i.schema);
16
+ .flatMap((manifest) =>
17
+ manifest.api.map((m) => ({
18
+ ...m,
19
+ // 将插件的 identifier 作为前缀,避免重复
20
+ name: manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name,
21
+ })),
22
+ );
15
23
  };
16
24
 
17
- const getPluginMetaByName = (name: string) => (s: PluginStoreState) =>
18
- s.pluginList?.find((p) => p.name === name);
25
+ const getPluginMetaById = (id: string) => (s: PluginStoreState) =>
26
+ s.pluginList?.find((p) => p.identifier === id);
19
27
 
20
28
  export const pluginSelectors = {
21
29
  enabledSchema,
22
- getPluginMetaByName,
30
+ getPluginMetaById,
23
31
  };
@@ -19,7 +19,7 @@ const createStore: StateCreator<PluginStore, [['zustand/devtools', never]]> = (.
19
19
 
20
20
  // =============== persist 本地缓存中间件配置 ============ //
21
21
 
22
- type SessionPersist = Pick<PluginStore, 'pluginList'>;
22
+ type SessionPersist = Pick<PluginStore, 'pluginList' | 'pluginManifestMap'>;
23
23
 
24
24
  const storeName = 'LOBE_PLUGIN';
25
25
 
@@ -28,6 +28,7 @@ const persistOptions: PersistOptions<PluginStore, SessionPersist> = {
28
28
 
29
29
  partialize: (s) => ({
30
30
  pluginList: s.pluginList,
31
+ pluginManifestMap: s.pluginManifestMap,
31
32
  }),
32
33
 
33
34
  // 手动控制 Hydration ,避免 ssr 报错
@@ -1,7 +1,9 @@
1
+ import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
1
2
  import { template } from 'lodash-es';
2
3
  import { StateCreator } from 'zustand/vanilla';
3
4
 
4
5
  import { LOADING_FLAT } from '@/const/message';
6
+ import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
5
7
  import { fetchChatModel } from '@/services/chatModel';
6
8
  import { fetchPlugin } from '@/services/plugin';
7
9
  import { SessionStore } from '@/store/session';
@@ -319,32 +321,30 @@ export const chatMessage: StateCreator<
319
321
  const message = session.chats[id];
320
322
  if (!message) return;
321
323
 
322
- let payload: OpenAIFunctionCall = { name: '' };
324
+ let payload: PluginRequestPayload = { apiName: '', identifier: '' };
325
+ // 识别到内容是 function_call 的情况下
326
+ // 将 function_call 转换为 plugin request payload
323
327
  if (message.content) {
324
- const { function_call } = JSON.parse(message.content);
325
- dispatchMessage({ id, key: 'function_call', type: 'updateMessage', value: function_call });
328
+ const { function_call } = JSON.parse(message.content) as {
329
+ function_call: OpenAIFunctionCall;
330
+ };
331
+
332
+ const [identifier, apiName] = function_call.name.split(PLUGIN_SCHEMA_SEPARATOR);
333
+ payload = { apiName, arguments: function_call.arguments, identifier };
334
+
335
+ dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
326
336
  dispatchMessage({ id, key: 'content', type: 'updateMessage', value: '' });
327
- payload = function_call;
328
337
  } else {
329
- if (message.function_call) {
330
- payload = message.function_call;
338
+ if (message.plugin) {
339
+ payload = message.plugin;
331
340
  }
332
341
  }
333
342
 
334
- if (!payload.name) return;
343
+ if (!payload.apiName) return;
335
344
 
336
- // const fid = nanoid();
337
345
  dispatchMessage({ id, key: 'role', type: 'updateMessage', value: 'function' });
338
- dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.name });
339
- dispatchMessage({ id, key: 'function_call', type: 'updateMessage', value: payload });
340
-
341
- // dispatchMessage({
342
- // id: id,
343
- // message: FUNCTION_LOADING,
344
- // parentId: message.,
345
- // role: 'function',
346
- // type: 'addMessage',
347
- // });
346
+ dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.identifier });
347
+ dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
348
348
 
349
349
  const abortController = toggleChatLoading(true, id);
350
350
  const data = await fetchPlugin(payload, { signal: abortController?.signal });
@@ -342,14 +342,16 @@ describe('organizeChats', () => {
342
342
  ...params,
343
343
  pluginList: [
344
344
  {
345
- name: 'realtimeWeather',
345
+ identifier: 'realtimeWeather',
346
+ author: '123',
346
347
  meta: {
347
348
  avatar: '🧩',
349
+ title: '天气预报',
348
350
  },
349
351
  createAt: 'abc',
350
352
  manifest: '',
351
353
  homepage: '',
352
- schemaVersion: 'v1',
354
+ schemaVersion: 1,
353
355
  },
354
356
  ],
355
357
  });
@@ -38,11 +38,11 @@ export const organizeChats = (
38
38
  }
39
39
 
40
40
  case 'function': {
41
- const plugin = (pluginList || []).find((m) => m.name === message.name);
41
+ const plugin = (pluginList || []).find((m) => m.identifier === message.name);
42
42
 
43
43
  return {
44
44
  avatar: '🧩',
45
- title: plugin?.name || 'plugin-unknown',
45
+ title: plugin?.identifier || 'plugin-unknown',
46
46
  };
47
47
  }
48
48
  }
@@ -1,3 +1,5 @@
1
+ import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2
+
1
3
  import { ErrorType } from '@/types/fetch';
2
4
 
3
5
  import { LLMRoleType } from './llm';
@@ -33,10 +35,17 @@ export interface ChatMessage extends BaseDataModel {
33
35
  };
34
36
  } & Record<string, any>;
35
37
 
38
+ /**
39
+ * replace with plugin
40
+ * @deprecated
41
+ */
36
42
  function_call?: OpenAIFunctionCall;
37
43
  name?: string;
38
44
 
39
45
  parentId?: string;
46
+
47
+ plugin?: PluginRequestPayload;
48
+
40
49
  // 引用
41
50
  quotaId?: string;
42
51
  /**
@@ -1,3 +1,3 @@
1
- import { LobeChatPlugin } from '@lobehub/chat-plugin-sdk';
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
2
 
3
- export type PluginManifestMap = Record<string, LobeChatPlugin>;
3
+ export type PluginManifestMap = Record<string, LobeChatPluginManifest>;