@lobehub/chat 0.54.4 → 0.55.1

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 (43) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +81 -0
  3. package/package.json +7 -3
  4. package/src/components/SelectWithImg/styles.ts +8 -7
  5. package/src/const/url.ts +4 -0
  6. package/src/features/AgentSetting/AgentPlugin/index.tsx +83 -20
  7. package/src/layout/index.tsx +13 -8
  8. package/src/locales/default/plugin.ts +0 -16
  9. package/src/pages/api/openai.ts +4 -20
  10. package/src/pages/api/plugins.api.ts +7 -8
  11. package/src/pages/chat/[id]/layout.tsx +4 -0
  12. package/src/pages/chat/features/Conversation/ChatList/Plugins/CustomRender.tsx +32 -0
  13. package/src/pages/chat/features/Conversation/ChatList/Plugins/FunctionCall.tsx +4 -5
  14. package/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx +36 -27
  15. package/src/pages/chat/features/Conversation/ChatList/Plugins/dynamticLoader.ts +26 -0
  16. package/src/pages/chat/features/Header/PluginTag.tsx +13 -8
  17. package/src/services/chatModel.ts +11 -2
  18. package/src/services/plugin.ts +11 -1
  19. package/src/store/plugin/action.ts +121 -0
  20. package/src/store/plugin/hooks/useSessionHydrated.ts +21 -0
  21. package/src/store/plugin/index.ts +2 -0
  22. package/src/store/plugin/initialState.ts +19 -0
  23. package/src/store/plugin/reducers/manifest.ts +36 -0
  24. package/src/store/plugin/selectors.ts +23 -0
  25. package/src/store/plugin/store.ts +48 -0
  26. package/src/store/session/hooks/useOnFinishHydrationSession.ts +6 -3
  27. package/src/store/session/hooks/useSessionHydrated.ts +0 -1
  28. package/src/store/session/slices/agentConfig/action.ts +2 -6
  29. package/src/store/session/slices/chat/selectors/utils.test.ts +15 -1
  30. package/src/store/session/slices/chat/selectors/utils.ts +5 -3
  31. package/src/store/session/store.ts +0 -1
  32. package/src/types/plugin.ts +3 -4
  33. package/src/plugins/Render.ts +0 -11
  34. package/src/plugins/index.ts +0 -11
  35. package/src/plugins/searchEngine/Render.tsx +0 -92
  36. package/src/plugins/searchEngine/index.ts +0 -26
  37. package/src/plugins/searchEngine/type.ts +0 -82
  38. package/src/plugins/type.ts +0 -45
  39. package/src/plugins/weather/Render.tsx +0 -53
  40. package/src/plugins/weather/index.ts +0 -24
  41. package/src/plugins/weather/type.ts +0 -36
  42. package/src/plugins/webCrawler/index.ts +0 -20
  43. package/src/plugins/webCrawler/type.ts +0 -35
package/.eslintrc.js CHANGED
@@ -10,5 +10,6 @@ config.rules['unicorn/no-typeof-undefined'] = 0;
10
10
  config.rules['unicorn/explicit-length-check'] = 0;
11
11
  config.rules['unicorn/prefer-code-point'] = 0;
12
12
  config.rules['no-extra-boolean-cast'] = 0;
13
+ config.rules['unicorn/no-useless-undefined'] = 0;
13
14
 
14
15
  module.exports = config;
package/CHANGELOG.md CHANGED
@@ -2,6 +2,87 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.55.1](https://github.com/lobehub/lobe-chat/compare/v0.55.0...v0.55.1)
6
+
7
+ <sup>Released on **2023-08-22**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor plugin api with @lobehub/chat-plugins-gateway.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor plugin api with @lobehub/chat-plugins-gateway, closes [#100](https://github.com/lobehub/lobe-chat/issues/100) ([b88d0db](https://github.com/lobehub/lobe-chat/commit/b88d0db))
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
+
30
+ ## [Version 0.55.0](https://github.com/lobehub/lobe-chat/compare/v0.54.4...v0.55.0)
31
+
32
+ <sup>Released on **2023-08-22**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: 将网关实现代码集成进 Chat 本体, 抽取插件为独立 store, 重构 openai 接口调用逻辑,将插件 schema 开启关闭逻辑与接口解耦, 重构插件列表获取逻辑,进而完全移除 plugins 目录.
37
+
38
+ #### ✨ Features
39
+
40
+ - **misc**: 初步完成插件市场动态加载全链路, 实现插件组件的动态加载.
41
+
42
+ #### 🐛 Bug Fixes
43
+
44
+ - **misc**: Fix error, 修正无法正常开启插件的问题, 修正测试, 补充插件 store 的水合逻辑.
45
+
46
+ #### 💄 Styles
47
+
48
+ - **misc**: 完成插件市场 loading 态样式.
49
+
50
+ <br/>
51
+
52
+ <details>
53
+ <summary><kbd>Improvements and Fixes</kbd></summary>
54
+
55
+ #### Code refactoring
56
+
57
+ - **misc**: 将网关实现代码集成进 Chat 本体 ([17e8161](https://github.com/lobehub/lobe-chat/commit/17e8161))
58
+ - **misc**: 抽取插件为独立 store ([12b7e7d](https://github.com/lobehub/lobe-chat/commit/12b7e7d))
59
+ - **misc**: 重构 openai 接口调用逻辑,将插件 schema 开启关闭逻辑与接口解耦 ([5aa886e](https://github.com/lobehub/lobe-chat/commit/5aa886e))
60
+ - **misc**: 重构插件列表获取逻辑,进而完全移除 plugins 目录 ([10055e1](https://github.com/lobehub/lobe-chat/commit/10055e1))
61
+
62
+ #### What's improved
63
+
64
+ - **misc**: 初步完成插件市场动态加载全链路 ([bc5e40f](https://github.com/lobehub/lobe-chat/commit/bc5e40f))
65
+ - **misc**: 实现插件组件的动态加载 ([04dbab2](https://github.com/lobehub/lobe-chat/commit/04dbab2))
66
+
67
+ #### What's fixed
68
+
69
+ - **misc**: Fix error ([fbeec75](https://github.com/lobehub/lobe-chat/commit/fbeec75))
70
+ - **misc**: 修正无法正常开启插件的问题 ([b3e9090](https://github.com/lobehub/lobe-chat/commit/b3e9090))
71
+ - **misc**: 修正测试 ([001de5b](https://github.com/lobehub/lobe-chat/commit/001de5b))
72
+ - **misc**: 补充插件 store 的水合逻辑 ([bfb649b](https://github.com/lobehub/lobe-chat/commit/bfb649b))
73
+
74
+ #### Styles
75
+
76
+ - **misc**: 完成插件市场 loading 态样式 ([8009691](https://github.com/lobehub/lobe-chat/commit/8009691))
77
+
78
+ </details>
79
+
80
+ <div align="right">
81
+
82
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
83
+
84
+ </div>
85
+
5
86
  ### [Version 0.54.4](https://github.com/lobehub/lobe-chat/compare/v0.54.3...v0.54.4)
6
87
 
7
88
  <sup>Released on **2023-08-21**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.54.4",
3
+ "version": "0.55.1",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -63,10 +63,12 @@
63
63
  "dependencies": {
64
64
  "@ant-design/colors": "^7",
65
65
  "@ant-design/icons": "^5",
66
+ "@cfworker/json-schema": "^1.12.7",
66
67
  "@emoji-mart/data": "^1",
67
68
  "@emoji-mart/react": "^1",
68
69
  "@icons-pack/react-simple-icons": "^8",
69
70
  "@lobehub/chat-plugin-sdk": "latest",
71
+ "@lobehub/chat-plugins-gateway": "^1.2.2",
70
72
  "@lobehub/ui": "latest",
71
73
  "@vercel/analytics": "^1",
72
74
  "ahooks": "^3",
@@ -93,6 +95,7 @@
93
95
  "react-layout-kit": "^1.7.1",
94
96
  "serpapi": "^2",
95
97
  "swr": "^2",
98
+ "systemjs": "^6.14.2",
96
99
  "ts-md5": "^1",
97
100
  "use-merge-value": "^1",
98
101
  "uuid": "^9",
@@ -111,7 +114,8 @@
111
114
  "@types/node": "^20",
112
115
  "@types/react": "^18",
113
116
  "@types/react-dom": "^18",
114
- "@types/testing-library__jest-dom": "^5",
117
+ "@types/systemjs": "^6.13.1",
118
+ "@types/testing-library__jest-dom": "^6",
115
119
  "@types/uuid": "^9",
116
120
  "@umijs/lint": "^4",
117
121
  "@vitest/coverage-v8": "latest",
@@ -123,7 +127,7 @@
123
127
  "i18next-browser-languagedetector": "^7",
124
128
  "i18next-resources-for-ts": "^1",
125
129
  "jsdom": "^22",
126
- "lint-staged": "^13",
130
+ "lint-staged": "^14",
127
131
  "next-pwa": "^5",
128
132
  "node-fetch": "^3",
129
133
  "prettier": "^2",
@@ -8,13 +8,14 @@ export const useStyles = createStyles(({ css, token, cx }) => {
8
8
  }
9
9
  `;
10
10
 
11
- const img = cx(css`
12
- box-shadow: inset 0 0 0 2px ${token.colorSplit};
13
- transition: all 100ms ${token.motionEaseOut};
14
- border-radius: ${token.borderRadius}px;
15
-
16
- ${hover}
17
- `);
11
+ const img = cx(
12
+ css`
13
+ border-radius: ${token.borderRadius}px;
14
+ box-shadow: inset 0 0 0 2px ${token.colorSplit};
15
+ transition: all 100ms ${token.motionEaseOut};
16
+ `,
17
+ hover,
18
+ );
18
19
 
19
20
  return {
20
21
  active: css`
package/src/const/url.ts CHANGED
@@ -5,3 +5,7 @@ export const CHANGELOG = `${pkg.homepage}/blob/master/CHANGELOG.md`;
5
5
  export const ABOUT = pkg.homepage;
6
6
  export const FEEDBACK = pkg.bugs.url;
7
7
  export const DISCORD = 'https://discord.gg/AYFPHvv2jT';
8
+
9
+ export const PLUGINS_INDEX_URL =
10
+ process.env.PLUGINS_INDEX_URL ??
11
+ 'https://registry.npmmirror.com/@lobehub/lobe-chat-plugins/latest/files';
@@ -1,14 +1,33 @@
1
1
  import { Avatar, Form, ItemGroup } from '@lobehub/ui';
2
- import { Switch } from 'antd';
2
+ import { Skeleton, Switch } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import isEqual from 'fast-deep-equal';
3
5
  import { ToyBrick } from 'lucide-react';
4
6
  import { memo, useMemo } from 'react';
5
7
  import { useTranslation } from 'react-i18next';
6
8
 
7
9
  import { FORM_STYLE } from '@/const/layoutTokens';
8
- import { PluginsMap } from '@/plugins';
10
+ import { usePluginStore } from '@/store/plugin';
9
11
  import { AgentAction } from '@/store/session/slices/agentConfig';
10
12
  import { LobeAgentConfig } from '@/types/session';
11
13
 
14
+ const useStyles = createStyles(({ css }) => ({
15
+ avatar: css`
16
+ .ant-skeleton-header {
17
+ padding-right: 0;
18
+ }
19
+ `,
20
+ label: css`
21
+ li {
22
+ height: 14px !important;
23
+ }
24
+
25
+ li + li {
26
+ margin-block-start: 12px !important;
27
+ }
28
+ `,
29
+ }));
30
+
12
31
  export interface AgentPluginProps {
13
32
  config: LobeAgentConfig;
14
33
  updateConfig: AgentAction['toggleAgentPlugin'];
@@ -16,27 +35,71 @@ export interface AgentPluginProps {
16
35
 
17
36
  const AgentPlugin = memo<AgentPluginProps>(({ config, updateConfig }) => {
18
37
  const { t } = useTranslation('setting');
38
+ const { styles } = useStyles();
39
+ const [useFetchPluginList, fetchPluginManifest] = usePluginStore((s) => [
40
+ s.useFetchPluginList,
41
+ s.fetchPluginManifest,
42
+ ]);
43
+ const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
44
+ const { data } = useFetchPluginList();
45
+
46
+ const plugin: ItemGroup = useMemo(() => {
47
+ let children;
48
+
49
+ const loadingItem = {
50
+ avatar: (
51
+ <Skeleton
52
+ active
53
+ avatar={{ size: 40 }}
54
+ className={styles.avatar}
55
+ paragraph={false}
56
+ title={false}
57
+ />
58
+ ),
59
+ label: (
60
+ <Skeleton
61
+ active
62
+ avatar={false}
63
+ paragraph={{ className: styles.label, style: { marginBottom: 0 } }}
64
+ style={{ width: 300 }}
65
+ title={false}
66
+ />
67
+ ),
68
+ };
69
+
70
+ children = !data
71
+ ? [loadingItem, loadingItem, loadingItem]
72
+ : data.plugins.map((item) => ({
73
+ avatar: <Avatar avatar={item.meta.avatar} />,
74
+ children: (
75
+ <Switch
76
+ checked={
77
+ // 如果在加载中,说明激活了
78
+ pluginManifestLoading[item.name] || !config.plugins
79
+ ? false
80
+ : config.plugins.includes(item.name)
81
+ }
82
+ loading={pluginManifestLoading[item.name]}
83
+ onChange={(checked) => {
84
+ updateConfig(item.name, checked);
85
+ if (checked) {
86
+ fetchPluginManifest(item.name);
87
+ }
88
+ }}
89
+ />
90
+ ),
91
+ desc: item.meta?.description,
92
+ label: t(`plugins.${item.name}` as any, { ns: 'plugin' }),
93
+ minWidth: undefined,
94
+ tag: item.name,
95
+ }));
19
96
 
20
- const plugin: ItemGroup = useMemo(
21
- () => ({
22
- children: Object.values(PluginsMap).map((item) => ({
23
- avatar: <Avatar avatar={item.avatar} />,
24
- children: (
25
- <Switch
26
- checked={!config.plugins ? false : config.plugins.includes(item.name)}
27
- onChange={() => updateConfig(item.name)}
28
- />
29
- ),
30
- desc: item.schema.description,
31
- label: t(`plugins.${item.name}` as any, { ns: 'plugin' }),
32
- minWidth: undefined,
33
- tag: item.name,
34
- })),
97
+ return {
98
+ children,
35
99
  icon: ToyBrick,
36
100
  title: t('settingPlugin.title'),
37
- }),
38
- [config],
39
- );
101
+ };
102
+ }, [config, data?.plugins, pluginManifestLoading]);
40
103
 
41
104
  return <Form items={[plugin]} {...FORM_STYLE} />;
42
105
  });
@@ -8,7 +8,8 @@ import { PropsWithChildren, memo, useCallback, useEffect } from 'react';
8
8
 
9
9
  import { createI18nNext } from '@/locales/create';
10
10
  import { useGlobalStore, useOnFinishHydrationGlobal } from '@/store/global';
11
- import { useSessionStore } from '@/store/session';
11
+ import { usePluginStore } from '@/store/plugin';
12
+ import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
12
13
  import { GlobalStyle } from '@/styles';
13
14
 
14
15
  import { useStyles } from './style';
@@ -24,6 +25,10 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
24
25
  });
25
26
  });
26
27
 
28
+ useOnFinishHydrationSession((s) => {
29
+ usePluginStore.getState().checkLocalEnabledPlugins(s.sessions);
30
+ });
31
+
27
32
  return (
28
33
  <ConfigProvider locale={Zh_CN}>
29
34
  <App className={styles.bg}>{children}</App>
@@ -32,21 +37,21 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
32
37
  });
33
38
 
34
39
  export default memo(({ children }: PropsWithChildren) => {
40
+ useEffect(() => {
41
+ // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
42
+ useSessionStore.persist.rehydrate();
43
+ useGlobalStore.persist.rehydrate();
44
+ usePluginStore.persist.rehydrate();
45
+ }, []);
46
+
35
47
  const themeMode = useGlobalStore((s) => s.settings.themeMode);
36
48
  const [primaryColor, neutralColor] = useGlobalStore((s) => [
37
49
  s.settings.primaryColor,
38
50
  s.settings.neutralColor,
39
51
  ]);
40
-
41
52
  const { browserPrefers } = useThemeMode();
42
53
  const isDarkMode = themeMode === 'auto' ? browserPrefers === 'dark' : themeMode === 'dark';
43
54
 
44
- useEffect(() => {
45
- // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
46
- useSessionStore.persist.rehydrate();
47
- useGlobalStore.persist.rehydrate();
48
- }, []);
49
-
50
55
  const genCustomToken: any = useCallback(
51
56
  () => lobeCustomTheme({ isDarkMode, neutralColor, primaryColor }),
52
57
  [primaryColor, neutralColor, isDarkMode],
@@ -13,22 +13,6 @@ export default {
13
13
  realtimeWeather: '实时天气预报',
14
14
  searchEngine: '搜索引擎',
15
15
  undefined: '插件检测中...',
16
-
17
16
  websiteCrawler: '网页内容提取',
18
17
  },
19
- realtimeWeather: {
20
- data: {
21
- date: '日期',
22
- daytemp_float: '日间温度',
23
- dayweather: '日间天气',
24
- daywind: '日间风速',
25
- nighttemp_float: '夜间温度',
26
- nightweather: '夜间天气',
27
- nightwind: '夜间风速',
28
- week: '星期',
29
- },
30
- title: '近7日天气数据({{city}})',
31
- updateAt: '更新时间',
32
- },
33
- responseData: '返回数据',
34
18
  };
@@ -1,10 +1,8 @@
1
1
  import { OpenAIStream, OpenAIStreamCallbacks, StreamingTextResponse } from 'ai';
2
2
  import { Configuration, OpenAIApi } from 'openai-edge';
3
- import { ChatCompletionFunctions } from 'openai-edge/types/api';
4
3
 
5
4
  import { getServerConfig } from '@/config/server';
6
5
  import { createErrorResponse } from '@/pages/api/error';
7
- import { PluginsMap } from '@/plugins';
8
6
  import { ErrorType } from '@/types/fetch';
9
7
  import { OpenAIStreamPayload } from '@/types/openai';
10
8
 
@@ -38,32 +36,18 @@ export const createChatCompletion = async ({
38
36
 
39
37
  const openai = createOpenAI(OPENAI_API_KEY, endpoint);
40
38
 
41
- const { messages, plugins: enabledPlugins, ...params } = payload;
39
+ // ============ 1. 前置处理 messages ============ //
40
+ const { messages, ...params } = payload;
42
41
 
43
- // ============ 1. 前置处理 functions ============ //
44
-
45
- const filterFunctions: ChatCompletionFunctions[] = Object.values(PluginsMap)
46
- .filter((p) => {
47
- // 如果不存在 enabledPlugins,那么全部不启用
48
- if (!enabledPlugins) return false;
49
-
50
- // 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
51
- return enabledPlugins.includes(p.name);
52
- })
53
- .map((f) => f.schema);
54
-
55
- const functions = filterFunctions.length === 0 ? undefined : filterFunctions;
56
-
57
- // ============ 2. 前置处理 messages ============ //
58
42
  const formatMessages = messages.map((m) => ({
59
43
  content: m.content,
60
44
  name: m.name,
61
45
  role: m.role,
62
46
  }));
63
47
 
64
- // ============ 3. 发送请求 ============ //
48
+ // ============ 2. 发送请求 ============ //
65
49
 
66
- const requestParams = { functions, messages: formatMessages, stream: true, ...params };
50
+ const requestParams = { messages: formatMessages, stream: true, ...params };
67
51
 
68
52
  let response: Response;
69
53
 
@@ -1,10 +1,9 @@
1
- export const runtime = 'edge';
1
+ import { createLobeChatPluginGateway } from '@lobehub/chat-plugins-gateway';
2
2
 
3
- // chat plugin gateway
4
- // refs to: https://github.com/lobehub/chat-plugins-gateway
5
- const PLUGIN_GATEWAY_URL = process.env.PLUGIN_GATEWAY_URL || 'https://chat-plugins.lobehub.com';
3
+ import { PLUGINS_INDEX_URL } from '@/const/url';
6
4
 
7
- export default async function handler(req: Request) {
8
- const payload = await req.text();
9
- return fetch(`${PLUGIN_GATEWAY_URL}/api/v1/runner`, { body: payload, method: 'POST' });
10
- }
5
+ export const config = {
6
+ runtime: 'edge',
7
+ };
8
+
9
+ export default createLobeChatPluginGateway(PLUGINS_INDEX_URL);
@@ -2,11 +2,13 @@ import { useRouter } from 'next/router';
2
2
  import { PropsWithChildren, memo } from 'react';
3
3
  import { shallow } from 'zustand/shallow';
4
4
 
5
+ import { usePluginStore } from '@/store/plugin';
5
6
  import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
6
7
 
7
8
  import ChatLayout from '../layout';
8
9
 
9
10
  const Layout = memo<PropsWithChildren>(({ children }) => {
11
+ const useFetchPluginList = usePluginStore((s) => s.useFetchPluginList);
10
12
  const [activeSession, toggleTopic] = useSessionStore((s) => {
11
13
  return [s.activeSession, s.toggleTopic];
12
14
  }, shallow);
@@ -24,6 +26,8 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
24
26
  toggleTopic();
25
27
  }, [id]);
26
28
 
29
+ useFetchPluginList();
30
+
27
31
  return <ChatLayout>{children}</ChatLayout>;
28
32
  });
29
33
 
@@ -0,0 +1,32 @@
1
+ import { PluginRender, PluginRenderProps } from '@lobehub/chat-plugin-sdk';
2
+ import { Skeleton } from 'antd';
3
+ import { memo, useState } from 'react';
4
+
5
+ import { system } from './dynamticLoader';
6
+
7
+ interface CustomRenderProps extends PluginRenderProps {
8
+ url: string;
9
+ }
10
+ const CustomRender = memo<CustomRenderProps>(({ url, ...props }) => {
11
+ const [component, setComp] = useState<PluginRender | null>(null);
12
+
13
+ if (!component) {
14
+ system
15
+ .import(url)
16
+ .then((module1) => {
17
+ setComp(module1.default);
18
+ // 使用module1模块
19
+ })
20
+ .catch((error) => {
21
+ setComp(null);
22
+ console.error(error);
23
+ });
24
+
25
+ return <Skeleton active style={{ width: 300 }} />;
26
+ }
27
+
28
+ const Comp = component;
29
+
30
+ return <Comp {...props} />;
31
+ });
32
+ export default CustomRender;
@@ -6,7 +6,7 @@ import { memo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
- import { PluginsMap } from '@/plugins';
9
+ import { pluginSelectors, usePluginStore } from '@/store/plugin';
10
10
  import { OpenAIFunctionCall } from '@/types/chatMessage';
11
11
 
12
12
  import PluginResult from './PluginResultRender';
@@ -22,11 +22,10 @@ const FunctionCall = memo<FunctionCallProps>(({ function_call, loading, content
22
22
  const { t } = useTranslation('plugin');
23
23
  const { styles } = useStyles();
24
24
  const [open, setOpen] = useState(false);
25
+ const item = usePluginStore(pluginSelectors.getPluginMetaByName(function_call?.name || ''));
25
26
 
26
- const plugin = PluginsMap[function_call?.name || ''];
27
-
28
- const avatar = plugin?.avatar ? (
29
- <Avatar avatar={plugin?.avatar} size={32} />
27
+ const avatar = item?.meta.avatar ? (
28
+ <Avatar avatar={item?.meta.avatar} size={32} />
30
29
  ) : (
31
30
  <Icon icon={LucideToyBrick} />
32
31
  );
@@ -3,41 +3,50 @@ import { memo } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
- import { PluginsRender } from '@/plugins/Render';
6
+ import { usePluginStore } from '@/store/plugin';
7
7
  import { ChatMessage } from '@/types/chatMessage';
8
8
 
9
+ import CustomRender from './CustomRender';
10
+
9
11
  export interface FunctionMessageProps extends ChatMessage {
10
12
  loading?: boolean;
11
13
  }
12
14
 
13
- const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
14
- const { t } = useTranslation('plugin');
15
-
16
- const Render = PluginsRender[name || ''];
17
-
18
- let isJSON = true;
19
- try {
20
- JSON.parse(content);
21
- } catch {
22
- isJSON = false;
23
- }
15
+ const PluginMessage = memo<FunctionMessageProps>(
16
+ ({
17
+ content,
18
+ // loading,
19
+ name,
20
+ }) => {
21
+ const { t } = useTranslation('plugin');
22
+
23
+ const manifest = usePluginStore((s) => s.pluginManifestMap[name || '']);
24
+ let isJSON = true;
25
+ try {
26
+ JSON.parse(content);
27
+ } catch {
28
+ isJSON = false;
29
+ }
30
+
31
+ // if (!loading)
32
+
33
+ if (!isJSON) {
34
+ return (
35
+ <Flexbox gap={8} horizontal>
36
+ <div>
37
+ <Loading3QuartersOutlined spin />
38
+ </div>
39
+ {t('loading.content')}
40
+ </Flexbox>
41
+ );
42
+ }
43
+
44
+ if (!manifest?.ui?.url) return;
24
45
 
25
- if (!isJSON) {
26
46
  return (
27
- <Flexbox gap={8} horizontal>
28
- <div>
29
- <Loading3QuartersOutlined spin />
30
- </div>
31
- {t('loading.content')}
32
- </Flexbox>
47
+ <CustomRender content={JSON.parse(content)} name={name || 'unknown'} url={manifest.ui?.url} />
33
48
  );
34
- }
35
-
36
- if (Render) {
37
- return <Render content={JSON.parse(content)} name={name || 'unknown'} />;
38
- }
39
-
40
- return null;
41
- });
49
+ },
50
+ );
42
51
 
43
52
  export default PluginMessage;
@@ -0,0 +1,26 @@
1
+ /* eslint-disable no-undef */
2
+
3
+ /**
4
+ * 本动态加载模块使用 SystemJS 实现,在 Lobe Chat 中缓存了 React、ReactDOM、antd、antd-style 四个模块。
5
+ */
6
+ import * as antd from 'antd';
7
+ import * as AntdStyle from 'antd-style';
8
+ import * as React from 'react';
9
+ import * as ReactDOM from 'react-dom';
10
+ import 'systemjs';
11
+
12
+ System.addImportMap({
13
+ imports: {
14
+ 'React': 'app:React',
15
+ 'ReactDOM': 'app:ReactDOM',
16
+ 'antd': 'app:antd',
17
+ 'antd-style': 'app:antd-style',
18
+ },
19
+ });
20
+
21
+ System.set('app:React', { default: React, ...React });
22
+ System.set('app:ReactDOM', { __useDefault: true, ...ReactDOM });
23
+ System.set('app:antd', antd);
24
+ System.set('app:antd-style', AntdStyle);
25
+
26
+ export const system = System;
@@ -5,7 +5,7 @@ import { LucideToyBrick } from 'lucide-react';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
- import { PluginsMap } from '@/plugins';
8
+ import { usePluginStore } from '@/store/plugin';
9
9
 
10
10
  export interface PluginTagProps {
11
11
  plugins: string[];
@@ -14,15 +14,20 @@ export interface PluginTagProps {
14
14
  const PluginTag = memo<PluginTagProps>(({ plugins }) => {
15
15
  const { t } = useTranslation('plugin');
16
16
 
17
+ const list = usePluginStore((s) => s.pluginList);
18
+
17
19
  if (plugins.length === 0) return null;
18
20
 
19
- const items: MenuProps['items'] = plugins.map((id) => ({
20
- icon: (
21
- <Avatar avatar={PluginsMap[id].avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />
22
- ),
23
- key: id,
24
- label: t(`plugins.${id}` as any),
25
- }));
21
+ const items: MenuProps['items'] = plugins.map((id) => {
22
+ const item = list?.find((p) => p.name === id);
23
+ return {
24
+ icon: (
25
+ <Avatar avatar={item?.meta.avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />
26
+ ),
27
+ key: id,
28
+ label: t(`plugins.${id}` as any),
29
+ };
30
+ });
26
31
  const count = plugins.length;
27
32
 
28
33
  return (