@lobehub/chat 0.71.1 → 0.72.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 +43 -0
- package/package.json +1 -1
- package/src/components/ManifestPreviewer.tsx +30 -0
- package/src/const/url.ts +9 -1
- package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +2 -2
- package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +30 -16
- package/src/features/AgentSetting/AgentPlugin/MarketSettingModal.tsx +76 -0
- package/src/features/AgentSetting/store/action.ts +3 -3
- package/src/features/AgentSetting/store/reducers/config.ts +17 -6
- package/src/features/PluginDevModal/ManifestForm.tsx +15 -26
- package/src/locales/default/common.ts +1 -1
- package/src/locales/default/plugin.ts +9 -1
- package/src/locales/default/setting.ts +1 -0
- package/src/pages/chat/features/Header/PluginTag/PluginStatus.tsx +105 -0
- package/src/pages/chat/features/Header/PluginTag/index.tsx +10 -45
- package/src/services/pluginMarket.ts +5 -2
- package/src/store/plugin/helpers.ts +6 -0
- package/src/store/plugin/selectors.ts +4 -0
- package/src/store/session/slices/agentConfig/action.ts +16 -1
- package/src/features/AgentSetting/test.json +0 -40
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 0.72.0](https://github.com/lobehub/lobe-chat/compare/v0.71.1...v0.72.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-09-09**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **misc**: Add plugin market Setting Modal, 支持快速刷新与预览 manifest, 适配插件 i18n 方案.
|
|
12
|
+
|
|
13
|
+
#### 🐛 Bug Fixes
|
|
14
|
+
|
|
15
|
+
- **misc**: 修正删除插件时错误开启的问题.
|
|
16
|
+
|
|
17
|
+
#### 💄 Styles
|
|
18
|
+
|
|
19
|
+
- **misc**: 优化 manifest 预览的尺寸.
|
|
20
|
+
|
|
21
|
+
<br/>
|
|
22
|
+
|
|
23
|
+
<details>
|
|
24
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
25
|
+
|
|
26
|
+
#### What's improved
|
|
27
|
+
|
|
28
|
+
- **misc**: Add plugin market Setting Modal ([a0603a9](https://github.com/lobehub/lobe-chat/commit/a0603a9))
|
|
29
|
+
- **misc**: 支持快速刷新与预览 manifest, closes [#150](https://github.com/lobehub/lobe-chat/issues/150) ([5bd2eb0](https://github.com/lobehub/lobe-chat/commit/5bd2eb0))
|
|
30
|
+
- **misc**: 适配插件 i18n 方案 ([8709ab3](https://github.com/lobehub/lobe-chat/commit/8709ab3))
|
|
31
|
+
|
|
32
|
+
#### What's fixed
|
|
33
|
+
|
|
34
|
+
- **misc**: 修正删除插件时错误开启的问题 ([0e35c18](https://github.com/lobehub/lobe-chat/commit/0e35c18))
|
|
35
|
+
|
|
36
|
+
#### Styles
|
|
37
|
+
|
|
38
|
+
- **misc**: 优化 manifest 预览的尺寸 ([27f8d6d](https://github.com/lobehub/lobe-chat/commit/27f8d6d))
|
|
39
|
+
|
|
40
|
+
</details>
|
|
41
|
+
|
|
42
|
+
<div align="right">
|
|
43
|
+
|
|
44
|
+
[](#readme-top)
|
|
45
|
+
|
|
46
|
+
</div>
|
|
47
|
+
|
|
5
48
|
### [Version 0.71.1](https://github.com/lobehub/lobe-chat/compare/v0.71.0...v0.71.1)
|
|
6
49
|
|
|
7
50
|
<sup>Released on **2023-09-09**</sup>
|
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Highlighter } from '@lobehub/ui';
|
|
2
|
+
import { Popover } from 'antd';
|
|
3
|
+
import { ReactNode, memo } from 'react';
|
|
4
|
+
|
|
5
|
+
interface PluginManifestPreviewerProps {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
manifest: object;
|
|
8
|
+
trigger?: 'click' | 'hover';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ManifestPreviewer = memo<PluginManifestPreviewerProps>(
|
|
12
|
+
({ manifest, children, trigger = 'click' }) => (
|
|
13
|
+
<Popover
|
|
14
|
+
arrow={false}
|
|
15
|
+
content={
|
|
16
|
+
<Highlighter language={'json'} style={{ maxHeight: 600, maxWidth: 400 }}>
|
|
17
|
+
{JSON.stringify(manifest, null, 2)}
|
|
18
|
+
</Highlighter>
|
|
19
|
+
}
|
|
20
|
+
placement={'right'}
|
|
21
|
+
style={{ width: 400 }}
|
|
22
|
+
title={'Manifest JSON'}
|
|
23
|
+
trigger={trigger}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</Popover>
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export default ManifestPreviewer;
|
package/src/const/url.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Locales } from '@/locales/resources';
|
|
2
|
+
|
|
1
3
|
import pkg from '../../package.json';
|
|
2
4
|
import { INBOX_SESSION_ID } from './session';
|
|
3
5
|
|
|
@@ -8,7 +10,13 @@ export const FEEDBACK = pkg.bugs.url;
|
|
|
8
10
|
export const DISCORD = 'https://discord.gg/AYFPHvv2jT';
|
|
9
11
|
|
|
10
12
|
export const PLUGINS_INDEX_URL =
|
|
11
|
-
process.env.PLUGINS_INDEX_URL ?? 'https://chat-plugins.lobehub.com';
|
|
13
|
+
process.env.PLUGINS_INDEX_URL ?? 'https://chat-plugins.lobehub.com/index';
|
|
14
|
+
|
|
15
|
+
export const getPluginIndexJSON = (lang: Locales = 'en-US', baseUrl = PLUGINS_INDEX_URL) => {
|
|
16
|
+
if (lang === 'en-US') return baseUrl;
|
|
17
|
+
|
|
18
|
+
return `${baseUrl}.${lang}.json`;
|
|
19
|
+
};
|
|
12
20
|
|
|
13
21
|
export const AGENTS_INDEX_URL = process.env.AGENTS_INDEX_URL ?? 'https://chat-agents.lobehub.com';
|
|
14
22
|
|
|
@@ -3,8 +3,8 @@ import isEqual from 'fast-deep-equal';
|
|
|
3
3
|
import { memo, useState } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
|
-
import DevModal from 'src/features/PluginDevModal';
|
|
7
6
|
|
|
7
|
+
import DevModal from '@/features/PluginDevModal';
|
|
8
8
|
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
9
9
|
|
|
10
10
|
import { useStore } from '../store';
|
|
@@ -36,7 +36,7 @@ const MarketList = memo<{ id: string }>(({ id }) => {
|
|
|
36
36
|
mode={'edit'}
|
|
37
37
|
onDelete={() => {
|
|
38
38
|
deleteCustomPlugin(id);
|
|
39
|
-
toggleAgentPlugin(id);
|
|
39
|
+
toggleAgentPlugin(id, false);
|
|
40
40
|
}}
|
|
41
41
|
onOpenChange={setModal}
|
|
42
42
|
onSave={(value) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Avatar, Form, Icon, Tooltip } from '@lobehub/ui';
|
|
2
|
-
import { Button, Skeleton, Switch, Tag } from 'antd';
|
|
2
|
+
import { Button, Skeleton, Space, Switch, Tag } from 'antd';
|
|
3
3
|
import { createStyles } from 'antd-style';
|
|
4
4
|
import isEqual from 'fast-deep-equal';
|
|
5
|
-
import { LucideBlocks, LucideStore, LucideTrash2 } from 'lucide-react';
|
|
5
|
+
import { LucideBlocks, LucideSettings, LucideStore, LucideTrash2 } from 'lucide-react';
|
|
6
6
|
import { memo, useState } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
import { Flexbox } from 'react-layout-kit';
|
|
@@ -13,6 +13,7 @@ import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
|
13
13
|
|
|
14
14
|
import { useStore } from '../store';
|
|
15
15
|
import LocalPluginItem from './LocalPluginItem';
|
|
16
|
+
import MarketSettingModal from './MarketSettingModal';
|
|
16
17
|
|
|
17
18
|
const useStyles = createStyles(({ css }) => ({
|
|
18
19
|
avatar: css`
|
|
@@ -36,6 +37,7 @@ const MarketList = memo(() => {
|
|
|
36
37
|
const { styles } = useStyles();
|
|
37
38
|
|
|
38
39
|
const [showModal, setModal] = useState(false);
|
|
40
|
+
const [showSettings, setShowSettings] = useState(false);
|
|
39
41
|
|
|
40
42
|
const [userEnabledPlugins, hasPlugin, toggleAgentPlugin] = useStore((s) => [
|
|
41
43
|
s.config.plugins || [],
|
|
@@ -173,12 +175,13 @@ const MarketList = memo(() => {
|
|
|
173
175
|
onValueChange={updateNewDevPlugin}
|
|
174
176
|
open={showModal}
|
|
175
177
|
/>
|
|
178
|
+
<MarketSettingModal onOpenChange={setShowSettings} open={showSettings} />
|
|
176
179
|
<Form
|
|
177
180
|
items={[
|
|
178
181
|
{
|
|
179
182
|
children: isEmpty ? loadingList : [...deprecatedList, ...customList, ...list],
|
|
180
183
|
extra: (
|
|
181
|
-
<Flexbox gap={8} horizontal>
|
|
184
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
182
185
|
{hasDeprecated ? (
|
|
183
186
|
<Tooltip title={t('settingPlugin.clearDeprecated')}>
|
|
184
187
|
<Button
|
|
@@ -191,21 +194,32 @@ const MarketList = memo(() => {
|
|
|
191
194
|
}}
|
|
192
195
|
size={'small'}
|
|
193
196
|
type={'text'}
|
|
194
|
-
|
|
197
|
+
/>
|
|
195
198
|
</Tooltip>
|
|
196
199
|
) : null}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
200
|
+
|
|
201
|
+
<Space.Compact style={{ width: 'auto' }}>
|
|
202
|
+
<Tooltip title={t('settingPlugin.addTooltip')}>
|
|
203
|
+
<Button
|
|
204
|
+
icon={<Icon icon={LucideBlocks} />}
|
|
205
|
+
onClick={(e) => {
|
|
206
|
+
e.stopPropagation();
|
|
207
|
+
setModal(true);
|
|
208
|
+
}}
|
|
209
|
+
size={'small'}
|
|
210
|
+
/>
|
|
211
|
+
</Tooltip>
|
|
212
|
+
<Tooltip title={t('settingPlugin.settings')}>
|
|
213
|
+
<Button
|
|
214
|
+
icon={<Icon icon={LucideSettings} />}
|
|
215
|
+
onClick={(e) => {
|
|
216
|
+
e.stopPropagation();
|
|
217
|
+
setShowSettings(true);
|
|
218
|
+
}}
|
|
219
|
+
size={'small'}
|
|
220
|
+
/>
|
|
221
|
+
</Tooltip>
|
|
222
|
+
</Space.Compact>
|
|
209
223
|
</Flexbox>
|
|
210
224
|
),
|
|
211
225
|
icon: LucideStore,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Icon, Input, Tooltip } from '@lobehub/ui';
|
|
2
|
+
import { ConfigProvider, Form, Modal } from 'antd';
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
|
+
import { LucideSettings } from 'lucide-react';
|
|
5
|
+
import { lighten } from 'polished';
|
|
6
|
+
import { memo } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
import { Flexbox } from 'react-layout-kit';
|
|
9
|
+
|
|
10
|
+
import { PLUGINS_INDEX_URL } from '@/const/url';
|
|
11
|
+
|
|
12
|
+
const useStyles = createStyles(({ css, token, prefixCls }) => ({
|
|
13
|
+
content: css`
|
|
14
|
+
.${prefixCls}-modal-content {
|
|
15
|
+
border: 1px solid ${token.colorSplit};
|
|
16
|
+
border-radius: 12px;
|
|
17
|
+
}
|
|
18
|
+
`,
|
|
19
|
+
root: css`
|
|
20
|
+
backdrop-filter: blur(2px);
|
|
21
|
+
`,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
interface MarketSettingModalProps {
|
|
25
|
+
onOpenChange: (open: boolean) => void;
|
|
26
|
+
open?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const MarketSettingModal = memo<MarketSettingModalProps>(({ open, onOpenChange }) => {
|
|
30
|
+
const { t } = useTranslation('plugin');
|
|
31
|
+
const { styles, theme } = useStyles();
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<ConfigProvider
|
|
35
|
+
theme={{
|
|
36
|
+
token: {
|
|
37
|
+
colorBgElevated: lighten(0.005, theme.colorBgContainer),
|
|
38
|
+
},
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<Modal
|
|
42
|
+
centered
|
|
43
|
+
closable
|
|
44
|
+
maskClosable
|
|
45
|
+
onCancel={() => {
|
|
46
|
+
onOpenChange(false);
|
|
47
|
+
}}
|
|
48
|
+
open={open}
|
|
49
|
+
title={
|
|
50
|
+
<Flexbox gap={8} horizontal style={{ marginBottom: 24 }}>
|
|
51
|
+
<Icon icon={LucideSettings} />
|
|
52
|
+
{t('settings.title')}
|
|
53
|
+
</Flexbox>
|
|
54
|
+
}
|
|
55
|
+
width={700}
|
|
56
|
+
wrapClassName={styles.root}
|
|
57
|
+
>
|
|
58
|
+
<Flexbox gap={12}>
|
|
59
|
+
<Form layout={'vertical'}>
|
|
60
|
+
<Form.Item extra={t('settings.modalDesc')} label={t('settings.indexUrl.title')}>
|
|
61
|
+
<Tooltip title={t('settings.indexUrl.tooltip')}>
|
|
62
|
+
<Input
|
|
63
|
+
defaultValue={PLUGINS_INDEX_URL}
|
|
64
|
+
disabled
|
|
65
|
+
placeholder={'https://xxxxx.com/index.json'}
|
|
66
|
+
/>
|
|
67
|
+
</Tooltip>
|
|
68
|
+
</Form.Item>
|
|
69
|
+
</Form>
|
|
70
|
+
</Flexbox>
|
|
71
|
+
</Modal>
|
|
72
|
+
</ConfigProvider>
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export default MarketSettingModal;
|
|
@@ -48,7 +48,7 @@ export interface Action {
|
|
|
48
48
|
|
|
49
49
|
setAgentMeta: (meta: Partial<MetaData>) => void;
|
|
50
50
|
streamUpdateMeta: (key: keyof MetaData) => any;
|
|
51
|
-
toggleAgentPlugin: (pluginId: string) => void;
|
|
51
|
+
toggleAgentPlugin: (pluginId: string, state?: boolean) => void;
|
|
52
52
|
/**
|
|
53
53
|
* 更新加载状态
|
|
54
54
|
* @param key - SessionLoadingState 的键
|
|
@@ -198,8 +198,8 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
|
198
198
|
};
|
|
199
199
|
},
|
|
200
200
|
|
|
201
|
-
toggleAgentPlugin: (id) => {
|
|
202
|
-
get().dispatchConfig({ pluginId: id, type: 'togglePlugin' });
|
|
201
|
+
toggleAgentPlugin: (id, state) => {
|
|
202
|
+
get().dispatchConfig({ pluginId: id, state, type: 'togglePlugin' });
|
|
203
203
|
},
|
|
204
204
|
|
|
205
205
|
updateLoadingState: (key, value) => {
|
|
@@ -6,7 +6,7 @@ import { merge } from '@/utils/merge';
|
|
|
6
6
|
|
|
7
7
|
export type ConfigDispatch =
|
|
8
8
|
| { config: Partial<any>; type: 'update' }
|
|
9
|
-
| { pluginId: string; type: 'togglePlugin' }
|
|
9
|
+
| { pluginId: string; state?: boolean; type: 'togglePlugin' }
|
|
10
10
|
| { type: 'reset' };
|
|
11
11
|
|
|
12
12
|
export const configReducer = (state: LobeAgentConfig, payload: ConfigDispatch): LobeAgentConfig => {
|
|
@@ -19,15 +19,26 @@ export const configReducer = (state: LobeAgentConfig, payload: ConfigDispatch):
|
|
|
19
19
|
|
|
20
20
|
case 'togglePlugin': {
|
|
21
21
|
return produce(state, (config) => {
|
|
22
|
-
const { pluginId: id } = payload;
|
|
22
|
+
const { pluginId: id, state } = payload;
|
|
23
23
|
if (config.plugins === undefined) {
|
|
24
|
-
config.plugins = [
|
|
25
|
-
}
|
|
24
|
+
config.plugins = [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (typeof state === 'undefined') {
|
|
26
28
|
if (config.plugins.includes(id)) {
|
|
27
29
|
config.plugins.splice(config.plugins.indexOf(id), 1);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
|
|
31
|
+
return;
|
|
30
32
|
}
|
|
33
|
+
|
|
34
|
+
config.plugins.push(id);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!state) {
|
|
39
|
+
config.plugins = config.plugins.filter((pluginId) => pluginId !== id);
|
|
40
|
+
} else {
|
|
41
|
+
config.plugins.push(id);
|
|
31
42
|
}
|
|
32
43
|
});
|
|
33
44
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { LobeChatPluginManifest, pluginManifestSchema } from '@lobehub/chat-plugin-sdk';
|
|
2
|
-
import { ActionIcon, Form, FormItemProps,
|
|
3
|
-
import { FormInstance,
|
|
2
|
+
import { ActionIcon, Form, FormItemProps, Input, Tooltip } from '@lobehub/ui';
|
|
3
|
+
import { FormInstance, Radio } from 'antd';
|
|
4
4
|
import { FileCode, RotateCwIcon } from 'lucide-react';
|
|
5
5
|
import { memo, useState } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { Flexbox } from 'react-layout-kit';
|
|
8
8
|
|
|
9
|
+
import ManifestPreviewer from '@/components/ManifestPreviewer';
|
|
10
|
+
|
|
9
11
|
const ManifestForm = memo<{ form: FormInstance; mode?: 'url' | 'local' }>(
|
|
10
12
|
({ form, mode = 'url' }) => {
|
|
11
13
|
const { t } = useTranslation('plugin');
|
|
@@ -21,17 +23,15 @@ const ManifestForm = memo<{ form: FormInstance; mode?: 'url' | 'local' }>(
|
|
|
21
23
|
<Input
|
|
22
24
|
placeholder={'http://localhost:3400/manifest-dev.json'}
|
|
23
25
|
suffix={
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
26
|
+
<ActionIcon
|
|
27
|
+
icon={RotateCwIcon}
|
|
28
|
+
onClick={(e) => {
|
|
29
|
+
e.stopPropagation();
|
|
30
|
+
form.validateFields(['manifest']);
|
|
31
|
+
}}
|
|
32
|
+
size={'small'}
|
|
33
|
+
title={t('dev.meta.manifest.refresh')}
|
|
34
|
+
/>
|
|
35
35
|
}
|
|
36
36
|
/>
|
|
37
37
|
),
|
|
@@ -39,24 +39,13 @@ const ManifestForm = memo<{ form: FormInstance; mode?: 'url' | 'local' }>(
|
|
|
39
39
|
<Flexbox horizontal justify={'space-between'} style={{ marginTop: 8 }}>
|
|
40
40
|
{t('dev.meta.manifest.desc')}
|
|
41
41
|
{manifest && (
|
|
42
|
-
<
|
|
43
|
-
arrow={false}
|
|
44
|
-
content={
|
|
45
|
-
<Highlighter language={'json'}>
|
|
46
|
-
{JSON.stringify(manifest, null, 2)}
|
|
47
|
-
</Highlighter>
|
|
48
|
-
}
|
|
49
|
-
placement={'right'}
|
|
50
|
-
style={{ width: 400 }}
|
|
51
|
-
title={'Manifest JSON'}
|
|
52
|
-
trigger={'click'}
|
|
53
|
-
>
|
|
42
|
+
<ManifestPreviewer manifest={manifest}>
|
|
54
43
|
<ActionIcon
|
|
55
44
|
icon={FileCode}
|
|
56
45
|
size={'small'}
|
|
57
46
|
title={t('dev.meta.manifest.preview')}
|
|
58
47
|
/>
|
|
59
|
-
</
|
|
48
|
+
</ManifestPreviewer>
|
|
60
49
|
)}
|
|
61
50
|
</Flexbox>
|
|
62
51
|
),
|
|
@@ -87,13 +87,21 @@ export default {
|
|
|
87
87
|
'local.title': '自定义',
|
|
88
88
|
},
|
|
89
89
|
},
|
|
90
|
-
|
|
91
90
|
loading: {
|
|
92
91
|
content: '数据获取中...',
|
|
93
92
|
plugin: '插件运行中...',
|
|
94
93
|
},
|
|
94
|
+
|
|
95
95
|
pluginList: '插件列表',
|
|
96
96
|
plugins: {
|
|
97
97
|
unknown: '插件检测中...',
|
|
98
98
|
},
|
|
99
|
+
settings: {
|
|
100
|
+
indexUrl: {
|
|
101
|
+
title: '市场索引',
|
|
102
|
+
tooltip: '暂不支持编辑',
|
|
103
|
+
},
|
|
104
|
+
modalDesc: '配置插件市场的地址后,可以使用自定义的插件市场',
|
|
105
|
+
title: '设置插件市场',
|
|
106
|
+
},
|
|
99
107
|
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ActionIcon } from '@lobehub/ui';
|
|
2
|
+
import { Badge, Button, Tag } from 'antd';
|
|
3
|
+
import { LucideRotateCw, LucideTrash2, RotateCwIcon } from 'lucide-react';
|
|
4
|
+
import { memo, useMemo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Flexbox } from 'react-layout-kit';
|
|
7
|
+
|
|
8
|
+
import ManifestPreviewer from '@/components/ManifestPreviewer';
|
|
9
|
+
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
10
|
+
import { useSessionStore } from '@/store/session';
|
|
11
|
+
|
|
12
|
+
interface PluginStatusProps {
|
|
13
|
+
deprecated?: boolean;
|
|
14
|
+
id: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
}
|
|
17
|
+
const PluginStatus = memo<PluginStatusProps>(({ title, id, deprecated }) => {
|
|
18
|
+
const { t } = useTranslation('common');
|
|
19
|
+
const [status, isCustom, fetchPluginManifest] = usePluginStore((s) => [
|
|
20
|
+
pluginSelectors.getPluginManifestLoadingStatus(id)(s),
|
|
21
|
+
pluginSelectors.isCustomPlugin(id)(s),
|
|
22
|
+
s.fetchPluginManifest,
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const manifest = usePluginStore(pluginSelectors.getPluginManifestById(id));
|
|
26
|
+
|
|
27
|
+
const removePlugin = useSessionStore((s) => s.removePlugin);
|
|
28
|
+
|
|
29
|
+
const renderStatus = useMemo(() => {
|
|
30
|
+
switch (status) {
|
|
31
|
+
case 'loading': {
|
|
32
|
+
return <Badge color={'blue'} status={'processing'} />;
|
|
33
|
+
}
|
|
34
|
+
case 'error': {
|
|
35
|
+
return (
|
|
36
|
+
<ActionIcon
|
|
37
|
+
icon={LucideRotateCw}
|
|
38
|
+
onClick={() => {
|
|
39
|
+
fetchPluginManifest(id);
|
|
40
|
+
}}
|
|
41
|
+
size={'small'}
|
|
42
|
+
title={t('retry')}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
case 'success': {
|
|
47
|
+
return <Badge status={status} />;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}, [status]);
|
|
51
|
+
|
|
52
|
+
const tag =
|
|
53
|
+
// 废弃标签
|
|
54
|
+
deprecated ? (
|
|
55
|
+
<Tag bordered={false} color={'red'} style={{ marginRight: 0 }}>
|
|
56
|
+
{t('list.item.deprecated.title', { ns: 'plugin' })}
|
|
57
|
+
</Tag>
|
|
58
|
+
) : // 自定义标签
|
|
59
|
+
isCustom ? (
|
|
60
|
+
<Tag bordered={false} color={'gold'}>
|
|
61
|
+
{t('list.item.local.title', { ns: 'plugin' })}
|
|
62
|
+
</Tag>
|
|
63
|
+
) : null;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Flexbox gap={12} horizontal justify={'space-between'}>
|
|
67
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
68
|
+
{title || id}
|
|
69
|
+
{tag}
|
|
70
|
+
</Flexbox>
|
|
71
|
+
|
|
72
|
+
{deprecated ? (
|
|
73
|
+
<ActionIcon
|
|
74
|
+
icon={LucideTrash2}
|
|
75
|
+
onClick={(e) => {
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
removePlugin(id);
|
|
78
|
+
}}
|
|
79
|
+
size={'small'}
|
|
80
|
+
title={t('settingPlugin.clearDeprecated', { ns: 'setting' })}
|
|
81
|
+
/>
|
|
82
|
+
) : (
|
|
83
|
+
<Flexbox align={'center'} horizontal>
|
|
84
|
+
{isCustom ? (
|
|
85
|
+
<ActionIcon
|
|
86
|
+
icon={RotateCwIcon}
|
|
87
|
+
onClick={(e) => {
|
|
88
|
+
e.stopPropagation();
|
|
89
|
+
fetchPluginManifest(id);
|
|
90
|
+
// form.validateFields(['manifest']);
|
|
91
|
+
}}
|
|
92
|
+
size={'small'}
|
|
93
|
+
title={t('dev.meta.manifest.refresh', { ns: 'plugin' })}
|
|
94
|
+
/>
|
|
95
|
+
) : null}
|
|
96
|
+
<ManifestPreviewer manifest={manifest || {}} trigger={'hover'}>
|
|
97
|
+
<Button icon={renderStatus} size={'small'} type={'text'} />
|
|
98
|
+
</ManifestPreviewer>
|
|
99
|
+
</Flexbox>
|
|
100
|
+
)}
|
|
101
|
+
</Flexbox>
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export default PluginStatus;
|
|
@@ -1,50 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Avatar, Icon, Tag } from '@lobehub/ui';
|
|
2
2
|
import type { MenuProps } from 'antd';
|
|
3
|
-
import {
|
|
3
|
+
import { Dropdown } from 'antd';
|
|
4
4
|
import isEqual from 'fast-deep-equal';
|
|
5
|
-
import {
|
|
6
|
-
import { memo
|
|
7
|
-
import { useTranslation } from 'react-i18next';
|
|
8
|
-
import { Flexbox } from 'react-layout-kit';
|
|
5
|
+
import { LucideToyBrick } from 'lucide-react';
|
|
6
|
+
import { memo } from 'react';
|
|
9
7
|
|
|
10
8
|
import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
const { t } = useTranslation('common');
|
|
14
|
-
const [status, fetchPluginManifest] = usePluginStore((s) => [
|
|
15
|
-
pluginSelectors.getPluginManifestLoadingStatus(id)(s),
|
|
16
|
-
s.fetchPluginManifest,
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
const renderStatus = useMemo(() => {
|
|
20
|
-
switch (status) {
|
|
21
|
-
case 'loading': {
|
|
22
|
-
return <Badge color={'blue'} status={'processing'} />;
|
|
23
|
-
}
|
|
24
|
-
case 'error': {
|
|
25
|
-
return (
|
|
26
|
-
<ActionIcon
|
|
27
|
-
icon={LucideRotateCw}
|
|
28
|
-
onClick={() => {
|
|
29
|
-
fetchPluginManifest(id);
|
|
30
|
-
}}
|
|
31
|
-
size={'small'}
|
|
32
|
-
title={t('retry')}
|
|
33
|
-
/>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
case 'success': {
|
|
37
|
-
return <Badge status={status} />;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}, [status]);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<Flexbox gap={12} horizontal justify={'space-between'}>
|
|
44
|
-
{title} {renderStatus}
|
|
45
|
-
</Flexbox>
|
|
46
|
-
);
|
|
47
|
-
});
|
|
10
|
+
import PluginStatus from './PluginStatus';
|
|
48
11
|
|
|
49
12
|
export interface PluginTagProps {
|
|
50
13
|
plugins: string[];
|
|
@@ -58,11 +21,13 @@ const PluginTag = memo<PluginTagProps>(({ plugins }) => {
|
|
|
58
21
|
|
|
59
22
|
const items: MenuProps['items'] = plugins.map((id) => {
|
|
60
23
|
const item = list.find((i) => i.identifier === id);
|
|
24
|
+
const isDeprecated = !item?.title;
|
|
25
|
+
const avatar = isDeprecated ? '♻️' : item?.avatar || '🧩';
|
|
61
26
|
|
|
62
27
|
return {
|
|
63
|
-
icon: <Avatar avatar={
|
|
28
|
+
icon: <Avatar avatar={avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />,
|
|
64
29
|
key: id,
|
|
65
|
-
label: <PluginStatus id={id} title={item?.title} />,
|
|
30
|
+
label: <PluginStatus deprecated={isDeprecated} id={id} title={item?.title} />,
|
|
66
31
|
};
|
|
67
32
|
});
|
|
68
33
|
|
|
@@ -73,7 +38,7 @@ const PluginTag = memo<PluginTagProps>(({ plugins }) => {
|
|
|
73
38
|
<div>
|
|
74
39
|
<Tag>
|
|
75
40
|
{<Icon icon={LucideToyBrick} />}
|
|
76
|
-
{pluginHelpers.getPluginTitle(displayPlugin?.meta)}
|
|
41
|
+
{pluginHelpers.getPluginTitle(displayPlugin?.meta) || plugins[0]}
|
|
77
42
|
{count > 1 && <div>({plugins.length - 1}+)</div>}
|
|
78
43
|
</Tag>
|
|
79
44
|
</div>
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getPluginIndexJSON } from '@/const/url';
|
|
2
|
+
import { useGlobalStore } from '@/store/global';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 请求插件列表
|
|
5
6
|
*/
|
|
6
7
|
export const getPluginList = async () => {
|
|
7
|
-
const
|
|
8
|
+
const url = getPluginIndexJSON(useGlobalStore.getState().settings.language);
|
|
9
|
+
|
|
10
|
+
const res = await fetch(url);
|
|
8
11
|
|
|
9
12
|
return res.json();
|
|
10
13
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
|
|
2
2
|
import i18n from 'i18next';
|
|
3
3
|
|
|
4
|
+
import { CustomPlugin } from '@/types/plugin';
|
|
5
|
+
|
|
4
6
|
const getI18nValue = (value: string | Record<string, string> | undefined) => {
|
|
5
7
|
if (!value) return;
|
|
6
8
|
|
|
@@ -18,9 +20,13 @@ const getPluginTitle = (meta?: LobeChatPluginMeta['meta']) => getI18nValue(meta?
|
|
|
18
20
|
const getPluginDesc = (meta?: LobeChatPluginMeta['meta']) => getI18nValue(meta?.description);
|
|
19
21
|
const getPluginAvatar = (meta?: LobeChatPluginMeta['meta']) => meta?.avatar;
|
|
20
22
|
|
|
23
|
+
const isCustomPlugin = (id: string, pluginList: CustomPlugin[]) =>
|
|
24
|
+
pluginList.some((i) => i.identifier === id);
|
|
25
|
+
|
|
21
26
|
export const pluginHelpers = {
|
|
22
27
|
getPluginAvatar,
|
|
23
28
|
getPluginDesc,
|
|
24
29
|
getPluginFormList,
|
|
25
30
|
getPluginTitle,
|
|
31
|
+
isCustomPlugin,
|
|
26
32
|
};
|
|
@@ -50,6 +50,9 @@ const getPluginManifestLoadingStatus = (id: string) => (s: PluginStoreState) =>
|
|
|
50
50
|
if (!!manifest) return 'success';
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
const isCustomPlugin = (id: string) => (s: PluginStoreState) =>
|
|
54
|
+
pluginHelpers.isCustomPlugin(id, s.customPluginList);
|
|
55
|
+
|
|
53
56
|
const displayPluginList = (s: PluginStoreState) =>
|
|
54
57
|
pluginList(s).map((p) => ({
|
|
55
58
|
author: p.author,
|
|
@@ -69,5 +72,6 @@ export const pluginSelectors = {
|
|
|
69
72
|
getPluginManifestLoadingStatus,
|
|
70
73
|
getPluginMetaById,
|
|
71
74
|
getPluginSettingsById,
|
|
75
|
+
isCustomPlugin,
|
|
72
76
|
pluginList,
|
|
73
77
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { produce } from 'immer';
|
|
1
2
|
import { StateCreator } from 'zustand/vanilla';
|
|
2
3
|
|
|
3
4
|
import { MetaData } from '@/types/meta';
|
|
@@ -7,9 +8,10 @@ import { SessionStore } from '../../store';
|
|
|
7
8
|
import { sessionSelectors } from '../session/selectors';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* 助手接口
|
|
11
12
|
*/
|
|
12
13
|
export interface AgentAction {
|
|
14
|
+
removePlugin: (id: string) => void;
|
|
13
15
|
/**
|
|
14
16
|
* 更新代理配置
|
|
15
17
|
* @param config - 部分 LobeAgentConfig 的配置
|
|
@@ -24,6 +26,18 @@ export const createAgentSlice: StateCreator<
|
|
|
24
26
|
[],
|
|
25
27
|
AgentAction
|
|
26
28
|
> = (set, get) => ({
|
|
29
|
+
removePlugin: (id) => {
|
|
30
|
+
const { activeId } = get();
|
|
31
|
+
const session = sessionSelectors.currentSession(get());
|
|
32
|
+
if (!activeId || !session) return;
|
|
33
|
+
|
|
34
|
+
const config = produce(session.config, (draft) => {
|
|
35
|
+
draft.plugins = draft.plugins?.filter((i) => i !== id) || [];
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
get().dispatchSession({ config, id: activeId, type: 'updateSessionConfig' });
|
|
39
|
+
},
|
|
40
|
+
|
|
27
41
|
updateAgentConfig: (config) => {
|
|
28
42
|
const { activeId } = get();
|
|
29
43
|
const session = sessionSelectors.currentSession(get());
|
|
@@ -31,6 +45,7 @@ export const createAgentSlice: StateCreator<
|
|
|
31
45
|
|
|
32
46
|
get().dispatchSession({ config, id: activeId, type: 'updateSessionConfig' });
|
|
33
47
|
},
|
|
48
|
+
|
|
34
49
|
updateAgentMeta: (meta) => {
|
|
35
50
|
const { activeId } = get();
|
|
36
51
|
const session = sessionSelectors.currentSession(get());
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"exportType": "agents",
|
|
3
|
-
"state": {
|
|
4
|
-
"sessions": {
|
|
5
|
-
"9a2def55-ff50-4f7f-ad6c-b97dac817193": {
|
|
6
|
-
"chats": {},
|
|
7
|
-
"config": {
|
|
8
|
-
"model": "gpt-3.5-turbo",
|
|
9
|
-
"params": { "temperature": 0.6 },
|
|
10
|
-
"systemRole": "你是一名专业的前端。擅长书写 Typescript JSDoc 代码,代码的示例如下:\n\n```ts\ninterface Props {\n/**\n* 尺寸\n* */\nloading: boolean;\n/**\n* 头像形状\n* @default 'square'\n* @enum [\"square\",\"circle\"]\n* @enumNames [ \"方形\", \"圆形\"]\n*/\nshape?: 'square' | 'circle';\n/**\n* 返回事件\n* @ignore\n*/\nonBack: () => void;\n/**\n* 选择路由的回调函数\n* @param key - 选中的路由\n* @ignore\n*/\nonSelect?: (key: string) => any;\n/**\n* 点击事件回调函数\n* @ignore\n*/\nonClick?: () => void;\n/**\n* 引用\n* @ignore\n*/\nref: any;\n/**\n* Tooltip 提示框位置\n* @enum ['top', 'left', 'right', 'bottom', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', 'leftTop', 'leftBottom', 'rightTop', 'rightBottom']\n* @enumNames ['上', '左', '右', '下', '左上', '右上', '左下', '右下', '左上', '左下', '右上', '右下']\n* @default 'top'\n*/\nplacement?: TooltipPlacement;\n}\n```\n\n\n接下来用户会输入一串 interface 代码,需要你补全 jsdoc。其中接口的类型不可改变"
|
|
11
|
-
},
|
|
12
|
-
"createAt": 1690184262713,
|
|
13
|
-
"id": "9a2def55-ff50-4f7f-ad6c-b97dac817193",
|
|
14
|
-
"meta": { "backgroundColor": "#ec5e41", "avatar": "🥲" },
|
|
15
|
-
"type": "agent",
|
|
16
|
-
"updateAt": 1690184262713,
|
|
17
|
-
"topics": {}
|
|
18
|
-
},
|
|
19
|
-
"744fe028-c8a2-410c-8b0a-66cfa00d647b": {
|
|
20
|
-
"chats": {},
|
|
21
|
-
"config": {
|
|
22
|
-
"model": "gpt-3.5-turbo",
|
|
23
|
-
"params": { "temperature": 0.6 },
|
|
24
|
-
"systemRole": "- dafdsafa\n- da- dafdsafa\n- da- dafdsafa\n- da- dafdsafa\n- da- dafdsafa\n- da"
|
|
25
|
-
},
|
|
26
|
-
"createAt": 1690184730185,
|
|
27
|
-
"id": "744fe028-c8a2-410c-8b0a-66cfa00d647b",
|
|
28
|
-
"meta": {
|
|
29
|
-
"backgroundColor": "#ffef5c",
|
|
30
|
-
"title": "dafaa",
|
|
31
|
-
"description": "safsafsafsafsafsafsfasfas"
|
|
32
|
-
},
|
|
33
|
-
"type": "agent",
|
|
34
|
-
"updateAt": 1690184730185,
|
|
35
|
-
"topics": {}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
"version": 1
|
|
40
|
-
}
|