@lobehub/chat 0.63.2 → 0.64.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,68 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.64.0](https://github.com/lobehub/lobe-chat/compare/v0.63.3...v0.64.0)
6
+
7
+ <sup>Released on **2023-08-29**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Remove no need i18n.
12
+
13
+ #### ✨ Features
14
+
15
+ - **misc**: 增加自定义插件的增删改配置功能, 完善自定义插件表单的校验逻辑, 支持本地插件侧的请求与错误呈现, 新增插件配置 Dev 弹窗, 绑定本地插件的增删改逻辑.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### Code refactoring
23
+
24
+ - **misc**: Remove no need i18n ([808a86a](https://github.com/lobehub/lobe-chat/commit/808a86a))
25
+
26
+ #### What's improved
27
+
28
+ - **misc**: 增加自定义插件的增删改配置功能 ([faba081](https://github.com/lobehub/lobe-chat/commit/faba081))
29
+ - **misc**: 完善自定义插件表单的校验逻辑 ([4e1fd28](https://github.com/lobehub/lobe-chat/commit/4e1fd28))
30
+ - **misc**: 支持本地插件侧的请求与错误呈现 ([7e2b39a](https://github.com/lobehub/lobe-chat/commit/7e2b39a))
31
+ - **misc**: 新增插件配置 Dev 弹窗 ([20269b7](https://github.com/lobehub/lobe-chat/commit/20269b7))
32
+ - **misc**: 绑定本地插件的增删改逻辑 ([902e7ed](https://github.com/lobehub/lobe-chat/commit/902e7ed))
33
+
34
+ </details>
35
+
36
+ <div align="right">
37
+
38
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
39
+
40
+ </div>
41
+
42
+ ### [Version 0.63.3](https://github.com/lobehub/lobe-chat/compare/v0.63.2...v0.63.3)
43
+
44
+ <sup>Released on **2023-08-28**</sup>
45
+
46
+ #### ♻ Code Refactoring
47
+
48
+ - **misc**: Refactor with new market url.
49
+
50
+ <br/>
51
+
52
+ <details>
53
+ <summary><kbd>Improvements and Fixes</kbd></summary>
54
+
55
+ #### Code refactoring
56
+
57
+ - **misc**: Refactor with new market url, closes [#123](https://github.com/lobehub/lobe-chat/issues/123) ([34a88f8](https://github.com/lobehub/lobe-chat/commit/34a88f8))
58
+
59
+ </details>
60
+
61
+ <div align="right">
62
+
63
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
64
+
65
+ </div>
66
+
5
67
  ### [Version 0.63.2](https://github.com/lobehub/lobe-chat/compare/v0.63.1...v0.63.2)
6
68
 
7
69
  <sup>Released on **2023-08-27**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.63.2",
3
+ "version": "0.64.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -4,21 +4,29 @@ import Picker from '@emoji-mart/react';
4
4
  import { Avatar } from '@lobehub/ui';
5
5
  import { Popover } from 'antd';
6
6
  import { memo } from 'react';
7
+ import useMergeState from 'use-merge-value';
7
8
 
8
9
  import { DEFAULT_AVATAR, DEFAULT_BACKGROUND_COLOR } from '@/const/meta';
9
10
 
10
11
  import { useStyles } from './style';
11
12
 
12
13
  export interface EmojiPickerProps {
13
- avatar?: string;
14
14
  backgroundColor?: string;
15
- onChange: (emoji: string) => void;
15
+ defaultAvatar?: string;
16
+ onChange?: (emoji: string) => void;
17
+ value?: string;
16
18
  }
17
19
 
18
20
  const EmojiPicker = memo<EmojiPickerProps>(
19
- ({ avatar = DEFAULT_AVATAR, backgroundColor = DEFAULT_BACKGROUND_COLOR, onChange }) => {
21
+ ({ value, defaultAvatar, backgroundColor = DEFAULT_BACKGROUND_COLOR, onChange }) => {
20
22
  const { styles } = useStyles();
21
23
 
24
+ const [ava, setAva] = useMergeState(DEFAULT_AVATAR, {
25
+ defaultValue: defaultAvatar,
26
+ onChange,
27
+ value,
28
+ });
29
+
22
30
  return (
23
31
  <Popover
24
32
  content={
@@ -27,7 +35,7 @@ const EmojiPicker = memo<EmojiPickerProps>(
27
35
  data={data}
28
36
  i18n={i18n}
29
37
  locale={'zh'}
30
- onEmojiSelect={(e: any) => onChange(e.native)}
38
+ onEmojiSelect={(e: any) => setAva(e.native)}
31
39
  skinTonePosition={'none'}
32
40
  theme={'auto'}
33
41
  />
@@ -38,7 +46,7 @@ const EmojiPicker = memo<EmojiPickerProps>(
38
46
  trigger={'click'}
39
47
  >
40
48
  <div className={styles.avatar} style={{ width: 'fit-content' }}>
41
- <Avatar avatar={avatar} background={backgroundColor} size={44} />
49
+ <Avatar avatar={ava} background={backgroundColor} size={44} />
42
50
  </div>
43
51
  </Popover>
44
52
  );
package/src/const/url.ts CHANGED
@@ -7,4 +7,4 @@ export const FEEDBACK = pkg.bugs.url;
7
7
  export const DISCORD = 'https://discord.gg/AYFPHvv2jT';
8
8
 
9
9
  export const PLUGINS_INDEX_URL =
10
- process.env.PLUGINS_INDEX_URL ?? 'https://chat-plugins-index.lobehub.com';
10
+ process.env.PLUGINS_INDEX_URL ?? 'https://chat-plugins.lobehub.com';
@@ -63,9 +63,9 @@ const AgentMeta = memo(() => {
63
63
  {
64
64
  children: (
65
65
  <EmojiPicker
66
- avatar={meta.avatar}
67
66
  backgroundColor={meta.backgroundColor}
68
67
  onChange={(avatar) => updateMeta({ avatar })}
68
+ value={meta.avatar}
69
69
  />
70
70
  ),
71
71
  label: t('settingAgent.avatar.title'),
@@ -0,0 +1,72 @@
1
+ import { Button, Switch } from 'antd';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { memo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+ import DevModal from 'src/features/PluginDevModal';
7
+
8
+ import { pluginSelectors, usePluginStore } from '@/store/plugin';
9
+
10
+ import { useStore } from '../store';
11
+
12
+ const MarketList = memo<{ id: string }>(({ id }) => {
13
+ const { t } = useTranslation('plugin');
14
+
15
+ const [showModal, setModal] = useState(false);
16
+
17
+ const updateConfig = useStore((s) => s.toggleAgentPlugin);
18
+ const [plugins, hasPlugin] = useStore((s) => [s.config.plugins || [], !!s.config.plugins]);
19
+
20
+ const [useFetchPluginList, fetchPluginManifest, dispatchDevPluginList] = usePluginStore((s) => [
21
+ s.useFetchPluginList,
22
+ s.fetchPluginManifest,
23
+ s.dispatchDevPluginList,
24
+ ]);
25
+
26
+ const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
27
+ const devPlugin = usePluginStore(pluginSelectors.getDevPluginById(id), isEqual);
28
+
29
+ useFetchPluginList();
30
+
31
+ return (
32
+ <>
33
+ <DevModal
34
+ onDelete={() => {
35
+ dispatchDevPluginList({ id, type: 'deleteItem' });
36
+ }}
37
+ onOpenChange={setModal}
38
+ onSave={(value) => {
39
+ dispatchDevPluginList({ id, plugin: value, type: 'updateItem' });
40
+ }}
41
+ open={showModal}
42
+ showDelete
43
+ value={devPlugin}
44
+ />
45
+
46
+ <Flexbox align={'center'} gap={8} horizontal>
47
+ <Switch
48
+ checked={
49
+ // 如果在加载中,说明激活了
50
+ pluginManifestLoading[id] || !hasPlugin ? false : plugins.includes(id)
51
+ }
52
+ loading={pluginManifestLoading[id]}
53
+ onChange={(checked) => {
54
+ updateConfig(id);
55
+ if (checked) {
56
+ fetchPluginManifest(id);
57
+ }
58
+ }}
59
+ />
60
+ <Button
61
+ onClick={() => {
62
+ setModal(true);
63
+ }}
64
+ >
65
+ {t('list.item.local.config')}
66
+ </Button>
67
+ </Flexbox>
68
+ </>
69
+ );
70
+ });
71
+
72
+ export default MarketList;
@@ -0,0 +1,160 @@
1
+ import { Avatar, Form, Icon, Tooltip } from '@lobehub/ui';
2
+ import { Button, Skeleton, Switch, Tag } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import isEqual from 'fast-deep-equal';
5
+ import { LucideBlocks, LucideStore } from 'lucide-react';
6
+ import { memo, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+ import DevModal from 'src/features/PluginDevModal';
10
+
11
+ import { FORM_STYLE } from '@/const/layoutTokens';
12
+ import { pluginHelpers, usePluginStore } from '@/store/plugin';
13
+
14
+ import { useStore } from '../store';
15
+ import LocalPluginItem from './LocalPluginItem';
16
+
17
+ const useStyles = createStyles(({ css }) => ({
18
+ avatar: css`
19
+ .ant-skeleton-header {
20
+ padding-right: 0;
21
+ }
22
+ `,
23
+ label: css`
24
+ li {
25
+ height: 14px !important;
26
+ }
27
+
28
+ li + li {
29
+ margin-block-start: 12px !important;
30
+ }
31
+ `,
32
+ }));
33
+
34
+ const MarketList = memo(() => {
35
+ const { t } = useTranslation('setting');
36
+ const { styles } = useStyles();
37
+
38
+ const [showModal, setModal] = useState(false);
39
+
40
+ // useEffect(() => {
41
+ // setModal(true);
42
+ // }, []);
43
+
44
+ const updateConfig = useStore((s) => s.toggleAgentPlugin);
45
+ const [plugins, hasPlugin] = useStore((s) => [s.config.plugins || [], !!s.config.plugins]);
46
+
47
+ const [useFetchPluginList, fetchPluginManifest, saveToDevList, updateNewDevPlugin] =
48
+ usePluginStore((s) => [
49
+ s.useFetchPluginList,
50
+ s.fetchPluginManifest,
51
+ s.saveToDevList,
52
+ s.updateNewDevPlugin,
53
+ ]);
54
+ const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
55
+ const pluginList = usePluginStore((s) => s.pluginList, isEqual);
56
+ const devPluginList = usePluginStore((s) => s.devPluginList, isEqual);
57
+
58
+ useFetchPluginList();
59
+
60
+ const loadingItem = {
61
+ avatar: (
62
+ <Skeleton
63
+ active
64
+ avatar={{ size: 40 }}
65
+ className={styles.avatar}
66
+ paragraph={false}
67
+ title={false}
68
+ />
69
+ ),
70
+ label: (
71
+ <Skeleton
72
+ active
73
+ avatar={false}
74
+ paragraph={{ className: styles.label, style: { marginBottom: 0 } }}
75
+ style={{ width: 300 }}
76
+ title={false}
77
+ />
78
+ ),
79
+ };
80
+
81
+ const loadingList = [loadingItem, loadingItem, loadingItem];
82
+
83
+ const isEmpty = pluginList.length === 0;
84
+
85
+ const list = pluginList.map(({ identifier, meta }) => ({
86
+ avatar: <Avatar avatar={meta.avatar} />,
87
+ children: (
88
+ <Switch
89
+ checked={
90
+ // 如果在加载中,说明激活了
91
+ pluginManifestLoading[identifier] || !hasPlugin ? false : plugins.includes(identifier)
92
+ }
93
+ loading={pluginManifestLoading[identifier]}
94
+ onChange={(checked) => {
95
+ updateConfig(identifier);
96
+ if (checked) {
97
+ fetchPluginManifest(identifier);
98
+ }
99
+ }}
100
+ />
101
+ ),
102
+ desc: pluginHelpers.getPluginDesc(meta),
103
+ label: pluginHelpers.getPluginTitle(meta),
104
+ minWidth: undefined,
105
+ tag: identifier,
106
+ }));
107
+
108
+ const devList = devPluginList.map(({ identifier, meta }) => ({
109
+ avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta) || '🧩'} />,
110
+ children: <LocalPluginItem id={identifier} />,
111
+ desc: pluginHelpers.getPluginDesc(meta),
112
+ label: (
113
+ <Flexbox align={'center'} gap={8} horizontal>
114
+ {pluginHelpers.getPluginTitle(meta)}
115
+ <Tag bordered={false} color={'gold'}>
116
+ {t('list.item.local.title', { ns: 'plugin' })}
117
+ </Tag>
118
+ </Flexbox>
119
+ ),
120
+ minWidth: undefined,
121
+ tag: identifier,
122
+ }));
123
+
124
+ return (
125
+ <>
126
+ <DevModal
127
+ onOpenChange={setModal}
128
+ onSave={saveToDevList}
129
+ onValueChange={updateNewDevPlugin}
130
+ open={showModal}
131
+ />
132
+ <Form
133
+ items={[
134
+ {
135
+ children: isEmpty ? loadingList : [...devList, ...list],
136
+ extra: (
137
+ <Tooltip title={t('settingPlugin.addTooltip')}>
138
+ <Button
139
+ icon={<Icon icon={LucideBlocks} />}
140
+ onClick={(e) => {
141
+ e.stopPropagation();
142
+ setModal(true);
143
+ }}
144
+ size={'small'}
145
+ >
146
+ {t('settingPlugin.add')}
147
+ </Button>
148
+ </Tooltip>
149
+ ),
150
+ icon: LucideStore,
151
+ title: t('settingPlugin.title'),
152
+ },
153
+ ]}
154
+ {...FORM_STYLE}
155
+ />{' '}
156
+ </>
157
+ );
158
+ });
159
+
160
+ export default MarketList;
@@ -0,0 +1,76 @@
1
+ import { Form, ItemGroup, Markdown } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { ToyBrick } from 'lucide-react';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import { FORM_STYLE } from '@/const/layoutTokens';
9
+ import { transformPluginSettings } from '@/features/PluginSettings';
10
+ import PluginSettingRender from '@/features/PluginSettings/PluginSettingRender';
11
+ import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
12
+
13
+ import { useStore } from '../store';
14
+
15
+ const useStyles = createStyles(({ css, token }) => ({
16
+ md: css`
17
+ p {
18
+ color: ${token.colorTextDescription};
19
+ }
20
+ `,
21
+ }));
22
+
23
+ const PluginSettings = memo(() => {
24
+ const { t } = useTranslation('setting');
25
+ const { styles } = useStyles();
26
+
27
+ const [plugins] = useStore((s) => [s.config.plugins || [], !!s.config.plugins]);
28
+
29
+ const [useFetchPluginList, updatePluginSettings] = usePluginStore((s) => [
30
+ s.useFetchPluginList,
31
+ s.updatePluginSettings,
32
+ ]);
33
+ const pluginList = usePluginStore(pluginSelectors.pluginList);
34
+
35
+ const pluginManifestMap = usePluginStore((s) => s.pluginManifestMap, isEqual);
36
+ const pluginsSettings = usePluginStore((s) => s.pluginsSettings, isEqual);
37
+ useFetchPluginList();
38
+
39
+ const isEmpty = pluginList.length === 0;
40
+
41
+ const pluginsConfig = isEmpty
42
+ ? []
43
+ : (plugins
44
+ .map((identifier) => {
45
+ const item = pluginList.find((i) => i.identifier === identifier);
46
+
47
+ if (!item) return null;
48
+ const manifest = pluginManifestMap[identifier];
49
+
50
+ if (!manifest?.settings) return null;
51
+
52
+ return {
53
+ children: transformPluginSettings(manifest.settings).map((item) => ({
54
+ ...item,
55
+ children: (
56
+ <PluginSettingRender
57
+ defaultValue={pluginsSettings[identifier]?.[item.name]}
58
+ format={item.format}
59
+ onChange={(value) => {
60
+ updatePluginSettings(identifier, { [item.name]: value });
61
+ }}
62
+ type={item.type as any}
63
+ />
64
+ ),
65
+ desc: item.desc && <Markdown className={styles.md}>{item.desc}</Markdown>,
66
+ })),
67
+ icon: ToyBrick,
68
+ title: t('settingPlugin.config', { id: pluginHelpers.getPluginTitle(item.meta) }),
69
+ };
70
+ })
71
+ .filter(Boolean) as unknown as ItemGroup[]);
72
+
73
+ return <Form items={pluginsConfig} {...FORM_STYLE} />;
74
+ });
75
+
76
+ export default PluginSettings;
@@ -1,148 +1,13 @@
1
- import { Avatar, Form, ItemGroup, Markdown } from '@lobehub/ui';
2
- import { Skeleton, Switch } from 'antd';
3
- import { createStyles } from 'antd-style';
4
- import isEqual from 'fast-deep-equal';
5
- import { LucideStore, ToyBrick } from 'lucide-react';
6
1
  import { memo } from 'react';
7
- import { useTranslation } from 'react-i18next';
8
2
 
9
- import { FORM_STYLE } from '@/const/layoutTokens';
10
- import { transformPluginSettings } from '@/features/PluginSettings';
11
- import PluginSettingRender from '@/features/PluginSettings/PluginSettingRender';
12
- import { pluginHelpers, usePluginStore } from '@/store/plugin';
3
+ import MarketList from './MarketList';
4
+ import PluginSettings from './PluginSettings';
13
5
 
14
- import { useStore } from '../store';
15
-
16
- const useStyles = createStyles(({ css, token }) => ({
17
- avatar: css`
18
- .ant-skeleton-header {
19
- padding-right: 0;
20
- }
21
- `,
22
- label: css`
23
- li {
24
- height: 14px !important;
25
- }
26
-
27
- li + li {
28
- margin-block-start: 12px !important;
29
- }
30
- `,
31
- md: css`
32
- p {
33
- color: ${token.colorTextDescription};
34
- }
35
- `,
36
- }));
37
-
38
- const AgentPlugin = memo(() => {
39
- const { t } = useTranslation('setting');
40
- const { styles } = useStyles();
41
-
42
- const updateConfig = useStore((s) => s.toggleAgentPlugin);
43
- const [plugins, hasPlugin] = useStore((s) => [s.config.plugins || [], !!s.config.plugins]);
44
-
45
- const [useFetchPluginList, fetchPluginManifest, updatePluginSettings] = usePluginStore((s) => [
46
- s.useFetchPluginList,
47
- s.fetchPluginManifest,
48
- s.updatePluginSettings,
49
- ]);
50
- const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
51
- const pluginManifestMap = usePluginStore((s) => s.pluginManifestMap, isEqual);
52
- const pluginsSettings = usePluginStore((s) => s.pluginsSettings, isEqual);
53
- const { data } = useFetchPluginList();
54
-
55
- const loadingItem = {
56
- avatar: (
57
- <Skeleton
58
- active
59
- avatar={{ size: 40 }}
60
- className={styles.avatar}
61
- paragraph={false}
62
- title={false}
63
- />
64
- ),
65
- label: (
66
- <Skeleton
67
- active
68
- avatar={false}
69
- paragraph={{ className: styles.label, style: { marginBottom: 0 } }}
70
- style={{ width: 300 }}
71
- title={false}
72
- />
73
- ),
74
- };
75
-
76
- const children = !data
77
- ? [loadingItem, loadingItem, loadingItem]
78
- : data.plugins.map(({ identifier, meta }) => ({
79
- avatar: <Avatar avatar={meta.avatar} />,
80
- children: (
81
- <Switch
82
- checked={
83
- // 如果在加载中,说明激活了
84
- pluginManifestLoading[identifier] || !hasPlugin ? false : plugins.includes(identifier)
85
- }
86
- loading={pluginManifestLoading[identifier]}
87
- onChange={(checked) => {
88
- updateConfig(identifier);
89
- if (checked) {
90
- fetchPluginManifest(identifier);
91
- }
92
- }}
93
- />
94
- ),
95
- desc: pluginHelpers.getPluginDesc(meta),
96
- label: pluginHelpers.getPluginTitle(meta),
97
- minWidth: undefined,
98
- tag: identifier,
99
- }));
100
-
101
- const pluginsConfig = !data
102
- ? []
103
- : (plugins
104
- .map((identifier) => {
105
- const item = data.plugins.find((i) => i.identifier === identifier);
106
-
107
- if (!item) return null;
108
- const manifest = pluginManifestMap[identifier];
109
-
110
- if (!manifest?.settings) return null;
111
-
112
- return {
113
- children: transformPluginSettings(manifest.settings).map((item) => ({
114
- ...item,
115
- children: (
116
- <PluginSettingRender
117
- defaultValue={pluginsSettings[identifier]?.[item.name]}
118
- format={item.format}
119
- onChange={(value) => {
120
- updatePluginSettings(identifier, { [item.name]: value });
121
- }}
122
- type={item.type as any}
123
- />
124
- ),
125
- desc: item.desc && <Markdown className={styles.md}>{item.desc}</Markdown>,
126
- })),
127
- icon: ToyBrick,
128
- title: t('settingPlugin.config', { id: pluginHelpers.getPluginTitle(item.meta) }),
129
- };
130
- })
131
- .filter(Boolean) as unknown as ItemGroup[]);
132
-
133
- return (
134
- <Form
135
- items={[
136
- {
137
- children,
138
- icon: LucideStore,
139
- title: t('settingPlugin.title'),
140
- },
141
- ...pluginsConfig,
142
- ]}
143
- {...FORM_STYLE}
144
- />
145
- );
146
- });
6
+ const AgentPlugin = memo(() => (
7
+ <>
8
+ <MarketList />
9
+ <PluginSettings />
10
+ </>
11
+ ));
147
12
 
148
13
  export default AgentPlugin;