@lobehub/chat 0.54.4 → 0.55.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/.eslintrc.js +1 -0
- package/CHANGELOG.md +56 -0
- package/package.json +6 -3
- package/src/components/SelectWithImg/styles.ts +8 -7
- package/src/const/url.ts +4 -0
- package/src/features/AgentSetting/AgentPlugin/index.tsx +83 -20
- package/src/layout/index.tsx +13 -8
- package/src/locales/default/plugin.ts +0 -16
- package/src/pages/api/openai.ts +4 -20
- package/src/pages/api/plugins.api.ts +161 -8
- package/src/pages/chat/[id]/layout.tsx +4 -0
- package/src/pages/chat/features/Conversation/ChatList/Plugins/CustomRender.tsx +32 -0
- package/src/pages/chat/features/Conversation/ChatList/Plugins/FunctionCall.tsx +4 -5
- package/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx +36 -27
- package/src/pages/chat/features/Conversation/ChatList/Plugins/dynamticLoader.ts +26 -0
- package/src/pages/chat/features/Header/PluginTag.tsx +13 -8
- package/src/services/chatModel.ts +11 -2
- package/src/services/plugin.ts +11 -1
- package/src/store/plugin/action.ts +121 -0
- package/src/store/plugin/hooks/useSessionHydrated.ts +21 -0
- package/src/store/plugin/index.ts +2 -0
- package/src/store/plugin/initialState.ts +19 -0
- package/src/store/plugin/reducers/manifest.ts +36 -0
- package/src/store/plugin/selectors.ts +23 -0
- package/src/store/plugin/store.ts +48 -0
- package/src/store/session/hooks/useOnFinishHydrationSession.ts +6 -3
- package/src/store/session/hooks/useSessionHydrated.ts +0 -1
- package/src/store/session/slices/agentConfig/action.ts +2 -6
- package/src/store/session/slices/chat/selectors/utils.test.ts +15 -1
- package/src/store/session/slices/chat/selectors/utils.ts +5 -3
- package/src/store/session/store.ts +0 -1
- package/src/types/plugin.ts +3 -4
- package/src/plugins/Render.ts +0 -11
- package/src/plugins/index.ts +0 -11
- package/src/plugins/searchEngine/Render.tsx +0 -92
- package/src/plugins/searchEngine/index.ts +0 -26
- package/src/plugins/searchEngine/type.ts +0 -82
- package/src/plugins/type.ts +0 -45
- package/src/plugins/weather/Render.tsx +0 -53
- package/src/plugins/weather/index.ts +0 -24
- package/src/plugins/weather/type.ts +0 -36
- package/src/plugins/webCrawler/index.ts +0 -20
- 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,62 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 0.55.0](https://github.com/lobehub/lobe-chat/compare/v0.54.4...v0.55.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-08-22**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: 将网关实现代码集成进 Chat 本体, 抽取插件为独立 store, 重构 openai 接口调用逻辑,将插件 schema 开启关闭逻辑与接口解耦, 重构插件列表获取逻辑,进而完全移除 plugins 目录.
|
|
12
|
+
|
|
13
|
+
#### ✨ Features
|
|
14
|
+
|
|
15
|
+
- **misc**: 初步完成插件市场动态加载全链路, 实现插件组件的动态加载.
|
|
16
|
+
|
|
17
|
+
#### 🐛 Bug Fixes
|
|
18
|
+
|
|
19
|
+
- **misc**: Fix error, 修正无法正常开启插件的问题, 修正测试, 补充插件 store 的水合逻辑.
|
|
20
|
+
|
|
21
|
+
#### 💄 Styles
|
|
22
|
+
|
|
23
|
+
- **misc**: 完成插件市场 loading 态样式.
|
|
24
|
+
|
|
25
|
+
<br/>
|
|
26
|
+
|
|
27
|
+
<details>
|
|
28
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
29
|
+
|
|
30
|
+
#### Code refactoring
|
|
31
|
+
|
|
32
|
+
- **misc**: 将网关实现代码集成进 Chat 本体 ([17e8161](https://github.com/lobehub/lobe-chat/commit/17e8161))
|
|
33
|
+
- **misc**: 抽取插件为独立 store ([12b7e7d](https://github.com/lobehub/lobe-chat/commit/12b7e7d))
|
|
34
|
+
- **misc**: 重构 openai 接口调用逻辑,将插件 schema 开启关闭逻辑与接口解耦 ([5aa886e](https://github.com/lobehub/lobe-chat/commit/5aa886e))
|
|
35
|
+
- **misc**: 重构插件列表获取逻辑,进而完全移除 plugins 目录 ([10055e1](https://github.com/lobehub/lobe-chat/commit/10055e1))
|
|
36
|
+
|
|
37
|
+
#### What's improved
|
|
38
|
+
|
|
39
|
+
- **misc**: 初步完成插件市场动态加载全链路 ([bc5e40f](https://github.com/lobehub/lobe-chat/commit/bc5e40f))
|
|
40
|
+
- **misc**: 实现插件组件的动态加载 ([04dbab2](https://github.com/lobehub/lobe-chat/commit/04dbab2))
|
|
41
|
+
|
|
42
|
+
#### What's fixed
|
|
43
|
+
|
|
44
|
+
- **misc**: Fix error ([fbeec75](https://github.com/lobehub/lobe-chat/commit/fbeec75))
|
|
45
|
+
- **misc**: 修正无法正常开启插件的问题 ([b3e9090](https://github.com/lobehub/lobe-chat/commit/b3e9090))
|
|
46
|
+
- **misc**: 修正测试 ([001de5b](https://github.com/lobehub/lobe-chat/commit/001de5b))
|
|
47
|
+
- **misc**: 补充插件 store 的水合逻辑 ([bfb649b](https://github.com/lobehub/lobe-chat/commit/bfb649b))
|
|
48
|
+
|
|
49
|
+
#### Styles
|
|
50
|
+
|
|
51
|
+
- **misc**: 完成插件市场 loading 态样式 ([8009691](https://github.com/lobehub/lobe-chat/commit/8009691))
|
|
52
|
+
|
|
53
|
+
</details>
|
|
54
|
+
|
|
55
|
+
<div align="right">
|
|
56
|
+
|
|
57
|
+
[](#readme-top)
|
|
58
|
+
|
|
59
|
+
</div>
|
|
60
|
+
|
|
5
61
|
### [Version 0.54.4](https://github.com/lobehub/lobe-chat/compare/v0.54.3...v0.54.4)
|
|
6
62
|
|
|
7
63
|
<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.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatbot",
|
|
@@ -63,6 +63,7 @@
|
|
|
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",
|
|
@@ -93,6 +94,7 @@
|
|
|
93
94
|
"react-layout-kit": "^1.7.1",
|
|
94
95
|
"serpapi": "^2",
|
|
95
96
|
"swr": "^2",
|
|
97
|
+
"systemjs": "^6.14.2",
|
|
96
98
|
"ts-md5": "^1",
|
|
97
99
|
"use-merge-value": "^1",
|
|
98
100
|
"uuid": "^9",
|
|
@@ -111,7 +113,8 @@
|
|
|
111
113
|
"@types/node": "^20",
|
|
112
114
|
"@types/react": "^18",
|
|
113
115
|
"@types/react-dom": "^18",
|
|
114
|
-
"@types/
|
|
116
|
+
"@types/systemjs": "^6.13.1",
|
|
117
|
+
"@types/testing-library__jest-dom": "^6",
|
|
115
118
|
"@types/uuid": "^9",
|
|
116
119
|
"@umijs/lint": "^4",
|
|
117
120
|
"@vitest/coverage-v8": "latest",
|
|
@@ -123,7 +126,7 @@
|
|
|
123
126
|
"i18next-browser-languagedetector": "^7",
|
|
124
127
|
"i18next-resources-for-ts": "^1",
|
|
125
128
|
"jsdom": "^22",
|
|
126
|
-
"lint-staged": "^
|
|
129
|
+
"lint-staged": "^14",
|
|
127
130
|
"next-pwa": "^5",
|
|
128
131
|
"node-fetch": "^3",
|
|
129
132
|
"prettier": "^2",
|
|
@@ -8,13 +8,14 @@ export const useStyles = createStyles(({ css, token, cx }) => {
|
|
|
8
8
|
}
|
|
9
9
|
`;
|
|
10
10
|
|
|
11
|
-
const img = cx(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
);
|
|
101
|
+
};
|
|
102
|
+
}, [config, data?.plugins, pluginManifestLoading]);
|
|
40
103
|
|
|
41
104
|
return <Form items={[plugin]} {...FORM_STYLE} />;
|
|
42
105
|
});
|
package/src/layout/index.tsx
CHANGED
|
@@ -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 {
|
|
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
|
};
|
package/src/pages/api/openai.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
// ============
|
|
48
|
+
// ============ 2. 发送请求 ============ //
|
|
65
49
|
|
|
66
|
-
const requestParams = {
|
|
50
|
+
const requestParams = { messages: formatMessages, stream: true, ...params };
|
|
67
51
|
|
|
68
52
|
let response: Response;
|
|
69
53
|
|
|
@@ -1,10 +1,163 @@
|
|
|
1
|
-
|
|
1
|
+
// reason to use cfworker json schema:
|
|
2
|
+
// https://github.com/vercel/next.js/discussions/47063#discussioncomment-5303951
|
|
3
|
+
import { Validator } from '@cfworker/json-schema';
|
|
4
|
+
import {
|
|
5
|
+
ErrorType,
|
|
6
|
+
LobeChatPlugin,
|
|
7
|
+
LobeChatPluginsMarketIndex,
|
|
8
|
+
PluginRequestPayload,
|
|
9
|
+
createErrorResponse,
|
|
10
|
+
marketIndexSchema,
|
|
11
|
+
pluginManifestSchema,
|
|
12
|
+
pluginMetaSchema,
|
|
13
|
+
pluginRequestPayloadSchema,
|
|
14
|
+
} from '@lobehub/chat-plugin-sdk';
|
|
2
15
|
|
|
3
|
-
|
|
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';
|
|
16
|
+
import { PLUGINS_INDEX_URL } from '@/const/url';
|
|
6
17
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
export const config = {
|
|
19
|
+
runtime: 'edge',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const DEFAULT_INDEX_URL = PLUGINS_INDEX_URL;
|
|
23
|
+
|
|
24
|
+
export default async (req: Request) => {
|
|
25
|
+
// ========== 1. 校验请求方法 ========== //
|
|
26
|
+
if (req.method !== 'POST')
|
|
27
|
+
return createErrorResponse(ErrorType.MethodNotAllowed, {
|
|
28
|
+
message: '[gateway] only allow POST method',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// ========== 2. 校验请求入参基础格式 ========== //
|
|
32
|
+
const requestPayload = (await req.json()) as PluginRequestPayload;
|
|
33
|
+
|
|
34
|
+
const payloadParseResult = pluginRequestPayloadSchema.safeParse(requestPayload);
|
|
35
|
+
|
|
36
|
+
if (!payloadParseResult.success)
|
|
37
|
+
return createErrorResponse(ErrorType.BadRequest, payloadParseResult.error);
|
|
38
|
+
|
|
39
|
+
const { name, arguments: args, indexUrl } = requestPayload;
|
|
40
|
+
|
|
41
|
+
console.info(`plugin call: ${name}`);
|
|
42
|
+
|
|
43
|
+
const marketIndexUrl = indexUrl ?? DEFAULT_INDEX_URL;
|
|
44
|
+
// ========== 3. 获取插件市场索引 ========== //
|
|
45
|
+
|
|
46
|
+
let marketIndex: LobeChatPluginsMarketIndex | undefined;
|
|
47
|
+
try {
|
|
48
|
+
const indexRes = await fetch(marketIndexUrl);
|
|
49
|
+
marketIndex = await indexRes.json();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(error);
|
|
52
|
+
marketIndex = undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 插件市场索引不存在
|
|
56
|
+
if (!marketIndex)
|
|
57
|
+
return createErrorResponse(ErrorType.PluginMarketIndexNotFound, {
|
|
58
|
+
indexUrl,
|
|
59
|
+
message: '[gateway] plugin market index not found',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 插件市场索引解析失败
|
|
63
|
+
const indexParseResult = marketIndexSchema.safeParse(marketIndex);
|
|
64
|
+
|
|
65
|
+
if (!indexParseResult.success)
|
|
66
|
+
return createErrorResponse(ErrorType.PluginMarketIndexInvalid, {
|
|
67
|
+
error: indexParseResult.error,
|
|
68
|
+
indexUrl,
|
|
69
|
+
marketIndex,
|
|
70
|
+
message: '[gateway] plugin market index is invalid',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.info('marketIndex:', marketIndex);
|
|
74
|
+
|
|
75
|
+
// ========== 4. 校验插件 meta 完备性 ========== //
|
|
76
|
+
|
|
77
|
+
const pluginMeta = marketIndex.plugins.find((i) => i.name === name);
|
|
78
|
+
|
|
79
|
+
// 一个不规范的插件示例
|
|
80
|
+
// const pluginMeta = {
|
|
81
|
+
// createAt: '2023-08-12',
|
|
82
|
+
// homepage: 'https://github.com/lobehub/chat-plugin-real-time-weather',
|
|
83
|
+
// manifest: 'https://registry.npmmirror.com/@lobehub/lobe-chat-plugins/latest/files',
|
|
84
|
+
// meta: {
|
|
85
|
+
// avatar: '☂️',
|
|
86
|
+
// tags: ['weather', 'realtime'],
|
|
87
|
+
// },
|
|
88
|
+
// name: 'realtimeWeather',
|
|
89
|
+
// schemaVersion: 'v1',
|
|
90
|
+
// };
|
|
91
|
+
|
|
92
|
+
// 校验插件是否存在
|
|
93
|
+
if (!pluginMeta)
|
|
94
|
+
return createErrorResponse(ErrorType.PluginMetaNotFound, {
|
|
95
|
+
message: `[gateway] plugin '${name}' is not found,please check the plugin list in ${indexUrl}, or create an issue to [lobe-chat-plugins](https://github.com/lobehub/lobe-chat-plugins/issues)`,
|
|
96
|
+
name,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const metaParseResult = pluginMetaSchema.safeParse(pluginMeta);
|
|
100
|
+
|
|
101
|
+
if (!metaParseResult.success)
|
|
102
|
+
return createErrorResponse(ErrorType.PluginMetaInvalid, {
|
|
103
|
+
error: metaParseResult.error,
|
|
104
|
+
message: '[plugin] plugin meta is invalid',
|
|
105
|
+
pluginMeta,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ========== 5. 校验插件 manifest 完备性 ========== //
|
|
109
|
+
|
|
110
|
+
// 获取插件的 manifest
|
|
111
|
+
let manifest: LobeChatPlugin | undefined;
|
|
112
|
+
try {
|
|
113
|
+
const pluginRes = await fetch(pluginMeta.manifest);
|
|
114
|
+
manifest = (await pluginRes.json()) as LobeChatPlugin;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error(error);
|
|
117
|
+
manifest = undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!manifest)
|
|
121
|
+
return createErrorResponse(ErrorType.PluginManifestNotFound, {
|
|
122
|
+
manifestUrl: pluginMeta.manifest,
|
|
123
|
+
message: '[plugin] plugin manifest not found',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const manifestParseResult = pluginManifestSchema.safeParse(manifest);
|
|
127
|
+
|
|
128
|
+
if (!manifestParseResult.success)
|
|
129
|
+
return createErrorResponse(ErrorType.PluginManifestInvalid, {
|
|
130
|
+
error: manifestParseResult.error,
|
|
131
|
+
manifest: manifest,
|
|
132
|
+
message: '[plugin] plugin manifest is invalid',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
console.log(`[${name}] plugin manifest:`, manifest);
|
|
136
|
+
|
|
137
|
+
// ========== 6. 校验请求入参与 manifest 要求一致性 ========== //
|
|
138
|
+
|
|
139
|
+
if (args) {
|
|
140
|
+
const v = new Validator(manifest.schema.parameters as any);
|
|
141
|
+
const validator = v.validate(JSON.parse(args!));
|
|
142
|
+
|
|
143
|
+
if (!validator.valid)
|
|
144
|
+
return createErrorResponse(ErrorType.BadRequest, {
|
|
145
|
+
error: validator.errors,
|
|
146
|
+
manifest,
|
|
147
|
+
message: '[plugin] args is invalid with plugin manifest schema',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ========== 7. 发送请求 ========== //
|
|
152
|
+
|
|
153
|
+
const response = await fetch(manifest.server.url, { body: args, method: 'post' });
|
|
154
|
+
|
|
155
|
+
// 不正常的错误,直接返回请求
|
|
156
|
+
if (!response.ok) return response;
|
|
157
|
+
|
|
158
|
+
const data = await response.text();
|
|
159
|
+
|
|
160
|
+
console.log(`[${name}]`, args, `result:`, data.slice(0, 1000));
|
|
161
|
+
|
|
162
|
+
return new Response(data);
|
|
163
|
+
};
|
|
@@ -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 {
|
|
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
|
|
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
|
);
|