Skip to content

Commit 5bd2eb0

Browse files
committed
✨ feat: 支持快速刷新与预览 manifest
close #150
1 parent 8709ab3 commit 5bd2eb0

File tree

9 files changed

+132
-26
lines changed

9 files changed

+132
-26
lines changed

src/components/ManifestPreviewer.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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;

src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import isEqual from 'fast-deep-equal';
33
import { memo, useState } from 'react';
44
import { useTranslation } from 'react-i18next';
55
import { Flexbox } from 'react-layout-kit';
6-
import DevModal from 'src/features/PluginDevModal';
76

7+
import DevModal from '@/features/PluginDevModal';
88
import { pluginSelectors, usePluginStore } from '@/store/plugin';
99

1010
import { useStore } from '../store';

src/features/PluginDevModal/ManifestForm.tsx

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { LobeChatPluginManifest, pluginManifestSchema } from '@lobehub/chat-plugin-sdk';
2-
import { ActionIcon, Form, FormItemProps, Highlighter, Input, Tooltip } from '@lobehub/ui';
3-
import { FormInstance, Popover, Radio } from 'antd';
2+
import { ActionIcon, Form, FormItemProps, Input, Tooltip } from '@lobehub/ui';
3+
import { FormInstance, Radio } from 'antd';
44
import { FileCode, RotateCwIcon } from 'lucide-react';
55
import { memo, useState } from 'react';
66
import { useTranslation } from 'react-i18next';
77
import { Flexbox } from 'react-layout-kit';
88

9+
import ManifestPreviewer from '@/components/ManifestPreviewer';
10+
911
const ManifestForm = memo<{ form: FormInstance; mode?: 'url' | 'local' }>(
1012
({ form, mode = 'url' }) => {
1113
const { t } = useTranslation('plugin');
@@ -37,24 +39,13 @@ const ManifestForm = memo<{ form: FormInstance; mode?: 'url' | 'local' }>(
3739
<Flexbox horizontal justify={'space-between'} style={{ marginTop: 8 }}>
3840
{t('dev.meta.manifest.desc')}
3941
{manifest && (
40-
<Popover
41-
arrow={false}
42-
content={
43-
<Highlighter language={'json'} style={{ maxHeight: 600, maxWidth: 400 }}>
44-
{JSON.stringify(manifest, null, 2)}
45-
</Highlighter>
46-
}
47-
placement={'right'}
48-
style={{ width: 400 }}
49-
title={'Manifest JSON'}
50-
trigger={'click'}
51-
>
42+
<ManifestPreviewer manifest={manifest}>
5243
<ActionIcon
5344
icon={FileCode}
5445
size={'small'}
5546
title={t('dev.meta.manifest.preview')}
5647
/>
57-
</Popover>
48+
</ManifestPreviewer>
5849
)}
5950
</Flexbox>
6051
),

src/locales/default/common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default {
3838
inbox: {
3939
defaultMessage:
4040
'你好,我是你的智能助手,你可以问我任何问题,我会尽力回答你。如果需要获得更加专业或定制的助手,可以点击<kbd>+</kbd>创建自定义助手',
41-
desc: '开启大脑集群,激发思维火花。你的智能助理,就在这里与你交流一切',
41+
desc: '开启大脑集群,激发思维火花。你的智能助理,在这里与你交流一切',
4242
title: '随便聊聊',
4343
},
4444
message: {

src/pages/chat/features/Header/PluginTag/PluginStatus.tsx

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
import { ActionIcon } from '@lobehub/ui';
2-
import { Badge } from 'antd';
3-
import { LucideRotateCw } from 'lucide-react';
2+
import { Badge, Button, Tag } from 'antd';
3+
import { LucideRotateCw, LucideTrash2, RotateCwIcon } from 'lucide-react';
44
import { memo, useMemo } from 'react';
55
import { useTranslation } from 'react-i18next';
66
import { Flexbox } from 'react-layout-kit';
77

8+
import ManifestPreviewer from '@/components/ManifestPreviewer';
89
import { pluginSelectors, usePluginStore } from '@/store/plugin';
10+
import { useSessionStore } from '@/store/session';
911

10-
const PluginStatus = memo<{ id: string; title?: string }>(({ title, id }) => {
12+
interface PluginStatusProps {
13+
deprecated?: boolean;
14+
id: string;
15+
title?: string;
16+
}
17+
const PluginStatus = memo<PluginStatusProps>(({ title, id, deprecated }) => {
1118
const { t } = useTranslation('common');
12-
const [status, fetchPluginManifest] = usePluginStore((s) => [
19+
const [status, isCustom, fetchPluginManifest] = usePluginStore((s) => [
1320
pluginSelectors.getPluginManifestLoadingStatus(id)(s),
21+
pluginSelectors.isCustomPlugin(id)(s),
1422
s.fetchPluginManifest,
1523
]);
1624

25+
const manifest = usePluginStore(pluginSelectors.getPluginManifestById(id));
26+
27+
const removePlugin = useSessionStore((s) => s.removePlugin);
28+
1729
const renderStatus = useMemo(() => {
1830
switch (status) {
1931
case 'loading': {
@@ -37,9 +49,55 @@ const PluginStatus = memo<{ id: string; title?: string }>(({ title, id }) => {
3749
}
3850
}, [status]);
3951

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+
4065
return (
4166
<Flexbox gap={12} horizontal justify={'space-between'}>
42-
{title} {renderStatus}
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+
)}
43101
</Flexbox>
44102
);
45103
});

src/pages/chat/features/Header/PluginTag/index.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ const PluginTag = memo<PluginTagProps>(({ plugins }) => {
2121

2222
const items: MenuProps['items'] = plugins.map((id) => {
2323
const item = list.find((i) => i.identifier === id);
24+
const isDeprecated = !item?.title;
25+
const avatar = isDeprecated ? '♻️' : item?.avatar || '🧩';
2426

2527
return {
26-
icon: <Avatar avatar={item?.avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />,
28+
icon: <Avatar avatar={avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />,
2729
key: id,
28-
label: <PluginStatus id={id} title={item?.title} />,
30+
label: <PluginStatus deprecated={isDeprecated} id={id} title={item?.title} />,
2931
};
3032
});
3133

@@ -36,7 +38,7 @@ const PluginTag = memo<PluginTagProps>(({ plugins }) => {
3638
<div>
3739
<Tag>
3840
{<Icon icon={LucideToyBrick} />}
39-
{pluginHelpers.getPluginTitle(displayPlugin?.meta)}
41+
{pluginHelpers.getPluginTitle(displayPlugin?.meta) || plugins[0]}
4042
{count > 1 && <div>({plugins.length - 1}+)</div>}
4143
</Tag>
4244
</div>

src/store/plugin/helpers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
22
import i18n from 'i18next';
33

4+
import { CustomPlugin } from '@/types/plugin';
5+
46
const getI18nValue = (value: string | Record<string, string> | undefined) => {
57
if (!value) return;
68

@@ -18,9 +20,13 @@ const getPluginTitle = (meta?: LobeChatPluginMeta['meta']) => getI18nValue(meta?
1820
const getPluginDesc = (meta?: LobeChatPluginMeta['meta']) => getI18nValue(meta?.description);
1921
const getPluginAvatar = (meta?: LobeChatPluginMeta['meta']) => meta?.avatar;
2022

23+
const isCustomPlugin = (id: string, pluginList: CustomPlugin[]) =>
24+
pluginList.some((i) => i.identifier === id);
25+
2126
export const pluginHelpers = {
2227
getPluginAvatar,
2328
getPluginDesc,
2429
getPluginFormList,
2530
getPluginTitle,
31+
isCustomPlugin,
2632
};

src/store/plugin/selectors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ const getPluginManifestLoadingStatus = (id: string) => (s: PluginStoreState) =>
5050
if (!!manifest) return 'success';
5151
};
5252

53+
const isCustomPlugin = (id: string) => (s: PluginStoreState) =>
54+
pluginHelpers.isCustomPlugin(id, s.customPluginList);
55+
5356
const displayPluginList = (s: PluginStoreState) =>
5457
pluginList(s).map((p) => ({
5558
author: p.author,
@@ -69,5 +72,6 @@ export const pluginSelectors = {
6972
getPluginManifestLoadingStatus,
7073
getPluginMetaById,
7174
getPluginSettingsById,
75+
isCustomPlugin,
7276
pluginList,
7377
};

src/store/session/slices/agentConfig/action.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { produce } from 'immer';
12
import { StateCreator } from 'zustand/vanilla';
23

34
import { MetaData } from '@/types/meta';
@@ -7,9 +8,10 @@ import { SessionStore } from '../../store';
78
import { sessionSelectors } from '../session/selectors';
89

910
/**
10-
* 代理行为接口
11+
* 助手接口
1112
*/
1213
export interface AgentAction {
14+
removePlugin: (id: string) => void;
1315
/**
1416
* 更新代理配置
1517
* @param config - 部分 LobeAgentConfig 的配置
@@ -24,13 +26,26 @@ export const createAgentSlice: StateCreator<
2426
[],
2527
AgentAction
2628
> = (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+
2741
updateAgentConfig: (config) => {
2842
const { activeId } = get();
2943
const session = sessionSelectors.currentSession(get());
3044
if (!activeId || !session) return;
3145

3246
get().dispatchSession({ config, id: activeId, type: 'updateSessionConfig' });
3347
},
48+
3449
updateAgentMeta: (meta) => {
3550
const { activeId } = get();
3651
const session = sessionSelectors.currentSession(get());

0 commit comments

Comments
 (0)