@lobehub/chat 0.67.0 → 0.68.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.
package/.eslintrc.js CHANGED
@@ -11,5 +11,6 @@ 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
13
  config.rules['unicorn/no-useless-undefined'] = 0;
14
+ config.rules['react/no-unknown-property'] = 0;
14
15
 
15
16
  module.exports = config;
package/CHANGELOG.md CHANGED
@@ -2,6 +2,57 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.68.1](https://github.com/lobehub/lobe-chat/compare/v0.68.0...v0.68.1)
6
+
7
+ <sup>Released on **2023-09-03**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: 修正数组合并逻辑, 修正被移除插件无法看到的问题.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: 修正数组合并逻辑 ([e36e621](https://github.com/lobehub/lobe-chat/commit/e36e621))
21
+ - **misc**: 修正被移除插件无法看到的问题 ([c17eb56](https://github.com/lobehub/lobe-chat/commit/c17eb56))
22
+
23
+ </details>
24
+
25
+ <div align="right">
26
+
27
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
28
+
29
+ </div>
30
+
31
+ ## [Version 0.68.0](https://github.com/lobehub/lobe-chat/compare/v0.67.0...v0.68.0)
32
+
33
+ <sup>Released on **2023-09-03**</sup>
34
+
35
+ #### ✨ Features
36
+
37
+ - **misc**: Plugin default use iframe render.
38
+
39
+ <br/>
40
+
41
+ <details>
42
+ <summary><kbd>Improvements and Fixes</kbd></summary>
43
+
44
+ #### What's improved
45
+
46
+ - **misc**: Plugin default use iframe render, closes [#141](https://github.com/lobehub/lobe-chat/issues/141) ([35a3a16](https://github.com/lobehub/lobe-chat/commit/35a3a16))
47
+
48
+ </details>
49
+
50
+ <div align="right">
51
+
52
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
53
+
54
+ </div>
55
+
5
56
  ## [Version 0.67.0](https://github.com/lobehub/lobe-chat/compare/v0.66.0...v0.67.0)
6
57
 
7
58
  <sup>Released on **2023-09-02**</sup>
package/README-zh_CN.md CHANGED
@@ -168,7 +168,7 @@ $ pnpm dev
168
168
 
169
169
  <!-- CONTRIBUTION GROUP -->
170
170
 
171
- > 📊 Total: <kbd>**4**</kbd>
171
+ > 📊 Total: <kbd>**5**</kbd>
172
172
 
173
173
  <a href="https://github.com/arvinxx" title="arvinxx">
174
174
  <img src="https://avatars.githubusercontent.com/u/28616219?v=4" width="50" />
@@ -182,6 +182,9 @@ $ pnpm dev
182
182
  <a href="https://github.com/actions-user" title="actions-user">
183
183
  <img src="https://avatars.githubusercontent.com/u/65916846?v=4" width="50" />
184
184
  </a>
185
+ <a href="https://github.com/bropines" title="bropines">
186
+ <img src="https://avatars.githubusercontent.com/u/57861007?v=4" width="50" />
187
+ </a>
185
188
 
186
189
  <!-- CONTRIBUTION END -->
187
190
 
package/README.md CHANGED
@@ -168,7 +168,7 @@ Contributions of all types are more than welcome, if you are interested in contr
168
168
 
169
169
  <!-- CONTRIBUTION GROUP -->
170
170
 
171
- > 📊 Total: <kbd>**4**</kbd>
171
+ > 📊 Total: <kbd>**5**</kbd>
172
172
 
173
173
  <a href="https://github.com/arvinxx" title="arvinxx">
174
174
  <img src="https://avatars.githubusercontent.com/u/28616219?v=4" width="50" />
@@ -182,6 +182,9 @@ Contributions of all types are more than welcome, if you are interested in contr
182
182
  <a href="https://github.com/actions-user" title="actions-user">
183
183
  <img src="https://avatars.githubusercontent.com/u/65916846?v=4" width="50" />
184
184
  </a>
185
+ <a href="https://github.com/bropines" title="bropines">
186
+ <img src="https://avatars.githubusercontent.com/u/57861007?v=4" width="50" />
187
+ </a>
185
188
 
186
189
  <!-- CONTRIBUTION END -->
187
190
 
@@ -52,7 +52,11 @@
52
52
  "desc": "LobeChat will install the plugin using this link",
53
53
  "invalid": "The input manifest link is invalid or does not comply with the specification",
54
54
  "label": "Plugin Manifest URL",
55
- "urlError": "Please enter a valid URL"
55
+ "urlError": "Please enter a valid URL",
56
+ "jsonInvalid": "The manifest is not valid, validation result: \n\n {{error}}",
57
+ "preview": "Preview Manifest",
58
+ "refresh": "Refresh",
59
+ "requestError": "Failed to request the link, please enter a valid link and check if the link allows cross-origin access"
56
60
  },
57
61
  "title": {
58
62
  "desc": "The title of the plugin",
@@ -73,7 +77,9 @@
73
77
  "manifest": "Function Description Manifest (Manifest)",
74
78
  "meta": "Plugin Metadata"
75
79
  },
76
- "title": "Add Custom Plugin"
80
+ "title": "Add Custom Plugin",
81
+ "update": "Update",
82
+ "updateSuccess": "Plugin settings updated successfully"
77
83
  },
78
84
  "list": {
79
85
  "item": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.67.0",
3
+ "version": "0.68.1",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -66,7 +66,7 @@
66
66
  "@emoji-mart/data": "^1",
67
67
  "@emoji-mart/react": "^1",
68
68
  "@icons-pack/react-simple-icons": "^9",
69
- "@lobehub/chat-plugin-sdk": "^1.13.1",
69
+ "@lobehub/chat-plugin-sdk": "^1.15.1",
70
70
  "@lobehub/chat-plugins-gateway": "^1.5.0",
71
71
  "@lobehub/ui": "latest",
72
72
  "@vercel/analytics": "^1",
@@ -14,8 +14,8 @@ const MarketList = memo<{ id: string }>(({ id }) => {
14
14
 
15
15
  const [showModal, setModal] = useState(false);
16
16
 
17
- const updateConfig = useStore((s) => s.toggleAgentPlugin);
18
- const [plugins, hasPlugin] = useStore((s) => [s.config.plugins || [], !!s.config.plugins]);
17
+ const [toggleAgentPlugin, hasPlugin] = useStore((s) => [s.toggleAgentPlugin, !!s.config.plugins]);
18
+ const plugins = useStore((s) => s.config.plugins || []);
19
19
 
20
20
  const [useFetchPluginList, fetchPluginManifest, deleteCustomPlugin, updateCustomPlugin] =
21
21
  usePluginStore((s) => [
@@ -36,6 +36,7 @@ const MarketList = memo<{ id: string }>(({ id }) => {
36
36
  mode={'edit'}
37
37
  onDelete={() => {
38
38
  deleteCustomPlugin(id);
39
+ toggleAgentPlugin(id);
39
40
  }}
40
41
  onOpenChange={setModal}
41
42
  onSave={(value) => {
@@ -53,7 +54,7 @@ const MarketList = memo<{ id: string }>(({ id }) => {
53
54
  }
54
55
  loading={pluginManifestLoading[id]}
55
56
  onChange={(checked) => {
56
- updateConfig(id);
57
+ toggleAgentPlugin(id);
57
58
  if (checked) {
58
59
  fetchPluginManifest(id);
59
60
  }
@@ -2,14 +2,14 @@ import { Avatar, Form, Icon, Tooltip } from '@lobehub/ui';
2
2
  import { Button, Skeleton, Switch, Tag } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
4
  import isEqual from 'fast-deep-equal';
5
- import { LucideBlocks, LucideStore } from 'lucide-react';
5
+ import { LucideBlocks, 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';
9
9
  import DevModal from 'src/features/PluginDevModal';
10
10
 
11
11
  import { FORM_STYLE } from '@/const/layoutTokens';
12
- import { pluginHelpers, usePluginStore } from '@/store/plugin';
12
+ import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
13
13
 
14
14
  import { useStore } from '../store';
15
15
  import LocalPluginItem from './LocalPluginItem';
@@ -37,11 +37,7 @@ const MarketList = memo(() => {
37
37
 
38
38
  const [showModal, setModal] = useState(false);
39
39
 
40
- // useEffect(() => {
41
- // setModal(true);
42
- // }, []);
43
-
44
- const [plugins, hasPlugin, toggleAgentPlugin] = useStore((s) => [
40
+ const [userEnabledPlugins, hasPlugin, toggleAgentPlugin] = useStore((s) => [
45
41
  s.config.plugins || [],
46
42
  !!s.config.plugins,
47
43
  s.toggleAgentPlugin,
@@ -57,6 +53,7 @@ const MarketList = memo(() => {
57
53
  const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
58
54
  const pluginList = usePluginStore((s) => s.pluginList, isEqual);
59
55
  const customPluginList = usePluginStore((s) => s.customPluginList, isEqual);
56
+ const totalList = usePluginStore(pluginSelectors.pluginList, isEqual);
60
57
 
61
58
  useFetchPluginList();
62
59
 
@@ -100,7 +97,9 @@ const MarketList = memo(() => {
100
97
  <Switch
101
98
  checked={
102
99
  // 如果在加载中,说明激活了
103
- pluginManifestLoading[identifier] || !hasPlugin ? false : plugins.includes(identifier)
100
+ pluginManifestLoading[identifier] || !hasPlugin
101
+ ? false
102
+ : userEnabledPlugins.includes(identifier)
104
103
  }
105
104
  loading={pluginManifestLoading[identifier]}
106
105
  onChange={(checked) => {
@@ -132,6 +131,35 @@ const MarketList = memo(() => {
132
131
  tag: identifier,
133
132
  }));
134
133
 
134
+ // =========== Deprecated Plugin List =========== //
135
+
136
+ // 基于 userEnabledPlugins 和完整的 totalList,检查出不在 totalList 中的插件
137
+ const deprecatedList = userEnabledPlugins
138
+ .filter((pluginId) => totalList.findIndex((p) => p.identifier === pluginId) < 0)
139
+ .map((id) => ({
140
+ avatar: <Avatar avatar={'♻️'} />,
141
+ children: (
142
+ <Switch
143
+ checked={true}
144
+ onChange={(checked) => {
145
+ togglePlugin(id, checked);
146
+ }}
147
+ />
148
+ ),
149
+ label: (
150
+ <Flexbox align={'center'} gap={8} horizontal>
151
+ {id}
152
+ <Tag bordered={false} color={'red'}>
153
+ {t('list.item.deprecated.title', { ns: 'plugin' })}
154
+ </Tag>
155
+ </Flexbox>
156
+ ),
157
+ minWidth: undefined,
158
+ tag: id,
159
+ }));
160
+
161
+ const hasDeprecated = deprecatedList.length > 0;
162
+
135
163
  return (
136
164
  <>
137
165
  <DevModal
@@ -148,27 +176,44 @@ const MarketList = memo(() => {
148
176
  <Form
149
177
  items={[
150
178
  {
151
- children: isEmpty ? loadingList : [...customList, ...list],
179
+ children: isEmpty ? loadingList : [...deprecatedList, ...customList, ...list],
152
180
  extra: (
153
- <Tooltip title={t('settingPlugin.addTooltip')}>
154
- <Button
155
- icon={<Icon icon={LucideBlocks} />}
156
- onClick={(e) => {
157
- e.stopPropagation();
158
- setModal(true);
159
- }}
160
- size={'small'}
161
- >
162
- {t('settingPlugin.add')}
163
- </Button>
164
- </Tooltip>
181
+ <Flexbox gap={8} horizontal>
182
+ {hasDeprecated ? (
183
+ <Tooltip title={t('settingPlugin.clearDeprecated')}>
184
+ <Button
185
+ icon={<Icon icon={LucideTrash2} />}
186
+ onClick={(e) => {
187
+ e.stopPropagation();
188
+ for (const i of deprecatedList) {
189
+ togglePlugin(i.tag as string, false);
190
+ }
191
+ }}
192
+ size={'small'}
193
+ type={'text'}
194
+ ></Button>
195
+ </Tooltip>
196
+ ) : null}
197
+ <Tooltip title={t('settingPlugin.addTooltip')}>
198
+ <Button
199
+ icon={<Icon icon={LucideBlocks} />}
200
+ onClick={(e) => {
201
+ e.stopPropagation();
202
+ setModal(true);
203
+ }}
204
+ size={'small'}
205
+ >
206
+ {t('settingPlugin.add')}
207
+ </Button>
208
+ </Tooltip>
209
+ </Flexbox>
165
210
  ),
166
211
  icon: LucideStore,
167
212
  title: t('settingPlugin.title'),
168
213
  },
169
214
  ]}
170
215
  {...FORM_STYLE}
171
- />{' '}
216
+ />
172
217
  </>
173
218
  );
174
219
  });
@@ -1,8 +1,8 @@
1
1
  import { produce } from 'immer';
2
- import { merge } from 'lodash-es';
3
2
 
4
3
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
5
4
  import { LobeAgentConfig } from '@/types/session';
5
+ import { merge } from '@/utils/merge';
6
6
 
7
7
  export type ConfigDispatch =
8
8
  | { config: Partial<any>; type: 'update' }
@@ -13,7 +13,7 @@ export const configReducer = (state: LobeAgentConfig, payload: ConfigDispatch):
13
13
  switch (payload.type) {
14
14
  case 'update': {
15
15
  return produce(state, (draftState) => {
16
- return merge({}, draftState, payload.config);
16
+ return merge(draftState, payload.config);
17
17
  });
18
18
  }
19
19
 
@@ -1,8 +1,8 @@
1
1
  import { produce } from 'immer';
2
- import { merge } from 'lodash-es';
3
2
 
4
3
  import { DEFAULT_AGENT_META } from '@/const/meta';
5
4
  import { MetaData } from '@/types/meta';
5
+ import { merge } from '@/utils/merge';
6
6
 
7
7
  export type MetaDataDispatch = { type: 'update'; value: Partial<MetaData> } | { type: 'reset' };
8
8
 
@@ -10,7 +10,7 @@ export const metaDataReducer = (state: MetaData, payload: MetaDataDispatch): Met
10
10
  switch (payload.type) {
11
11
  case 'update': {
12
12
  return produce(state, (draftState) => {
13
- return merge({}, draftState, payload.value);
13
+ return merge(draftState, payload.value);
14
14
  });
15
15
  }
16
16
 
@@ -82,6 +82,7 @@ export default {
82
82
  },
83
83
  list: {
84
84
  item: {
85
+ 'deprecated.title': '已删除',
85
86
  'local.config': '配置',
86
87
  'local.title': '自定义',
87
88
  },
@@ -1,4 +1,3 @@
1
-
2
1
  export default {
3
2
  danger: {
4
3
  clear: {
@@ -131,6 +130,7 @@ export default {
131
130
  settingPlugin: {
132
131
  add: '添加',
133
132
  addTooltip: '添加自定义插件',
133
+ clearDeprecated: '移除无效插件',
134
134
  config: '{{id}} 插件配置',
135
135
  title: '插件列表',
136
136
  },
@@ -0,0 +1,29 @@
1
+ import { useEffect } from 'react';
2
+
3
+ import { onPluginFetchMessage, onPluginReady } from './utils';
4
+
5
+ export const useOnPluginReady = (onReady: () => void) => {
6
+ useEffect(() => {
7
+ const fn = (e: MessageEvent) => {
8
+ onPluginReady(e, onReady);
9
+ };
10
+
11
+ window.addEventListener('message', fn);
12
+ return () => {
13
+ window.removeEventListener('message', fn);
14
+ };
15
+ }, []);
16
+ };
17
+
18
+ export const useOnPluginFetchMessage = (onRequest: (data: any) => void, deps: any[] = []) => {
19
+ useEffect(() => {
20
+ const fn = (e: MessageEvent) => {
21
+ onPluginFetchMessage(e, onRequest);
22
+ };
23
+
24
+ window.addEventListener('message', fn);
25
+ return () => {
26
+ window.removeEventListener('message', fn);
27
+ };
28
+ }, deps);
29
+ };
@@ -0,0 +1,62 @@
1
+ import { PluginRenderProps } from '@lobehub/chat-plugin-sdk';
2
+ import { Skeleton } from 'antd';
3
+ import { memo, useEffect, useRef, useState } from 'react';
4
+
5
+ import { useOnPluginFetchMessage, useOnPluginReady } from './hooks';
6
+ import { sendMessageToPlugin } from './utils';
7
+
8
+ interface IFrameRenderProps extends PluginRenderProps {
9
+ height?: number;
10
+ url: string;
11
+ width?: number;
12
+ }
13
+
14
+ const IFrameRender = memo<IFrameRenderProps>(({ url, width = 800, height = 300, ...props }) => {
15
+ const [loading, setLoading] = useState(true);
16
+ const [readyForRender, setReady] = useState(false);
17
+ const iframeRef = useRef<HTMLIFrameElement>(null);
18
+
19
+ useOnPluginReady(() => setReady(true));
20
+
21
+ // 当 props 发生变化时,主动向 iframe 发送数据
22
+ useEffect(() => {
23
+ const iframeWin = iframeRef.current?.contentWindow;
24
+
25
+ if (iframeWin && readyForRender) {
26
+ sendMessageToPlugin(iframeWin, props);
27
+ }
28
+ }, [readyForRender, props]);
29
+
30
+ // 当接收到来自 iframe 的请求时,触发发送数据
31
+ useOnPluginFetchMessage(() => {
32
+ const iframeWin = iframeRef.current?.contentWindow;
33
+ if (iframeWin) {
34
+ sendMessageToPlugin(iframeWin, props);
35
+ }
36
+ }, [props]);
37
+
38
+ return (
39
+ <>
40
+ {loading && <Skeleton active style={{ width }} />}
41
+ <iframe
42
+ // @ts-ignore
43
+ allowtransparency="true"
44
+ height={height}
45
+ hidden={loading}
46
+ onLoad={() => {
47
+ setLoading(false);
48
+ }}
49
+ ref={iframeRef}
50
+ src={url}
51
+ style={{
52
+ border: 0,
53
+ // iframe 在 color-scheme:dark 模式下无法透明
54
+ // refs: https://www.jianshu.com/p/bc5a37bb6a7b
55
+ colorScheme: 'light',
56
+ }}
57
+ width={width}
58
+ />
59
+ </>
60
+ );
61
+ });
62
+ export default IFrameRender;
@@ -0,0 +1,17 @@
1
+ import { PluginChannel } from '@lobehub/chat-plugin-sdk';
2
+
3
+ export const onPluginReady = (e: MessageEvent, onReady: () => void) => {
4
+ if (e.data.type === PluginChannel.pluginReadyForRender) {
5
+ onReady();
6
+ }
7
+ };
8
+
9
+ export const onPluginFetchMessage = (e: MessageEvent, onRequest: (data: any) => void) => {
10
+ if (e.data.type === PluginChannel.fetchPluginMessage) {
11
+ onRequest(e.data);
12
+ }
13
+ };
14
+
15
+ export const sendMessageToPlugin = (window: Window, props: any) => {
16
+ window.postMessage({ props, type: PluginChannel.renderPlugin }, '*');
17
+ };
@@ -1,12 +1,13 @@
1
1
  import { Loading3QuartersOutlined } from '@ant-design/icons';
2
- import { memo } from 'react';
2
+ import { memo, useMemo } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import { usePluginStore } from '@/store/plugin';
7
7
  import { ChatMessage } from '@/types/chatMessage';
8
8
 
9
- import CustomRender from './CustomRender';
9
+ import IFrameRender from './IFrameRender';
10
+ import SystemJsRender from './SystemJsRender';
10
11
 
11
12
  export interface FunctionMessageProps extends ChatMessage {
12
13
  loading?: boolean;
@@ -23,6 +24,8 @@ const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
23
24
  isJSON = false;
24
25
  }
25
26
 
27
+ const contentObj = useMemo(() => (isJSON ? JSON.parse(content) : content), [content]);
28
+
26
29
  // if (!loading)
27
30
 
28
31
  if (!isJSON) {
@@ -36,10 +39,23 @@ const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
36
39
  );
37
40
  }
38
41
 
39
- if (!manifest?.ui?.url) return;
42
+ if (!manifest?.ui) return;
43
+
44
+ const ui = manifest.ui;
45
+
46
+ if (!ui.url) return;
47
+
48
+ if (ui.mode === 'module')
49
+ return <SystemJsRender content={contentObj} name={name || 'unknown'} url={ui.url} />;
40
50
 
41
51
  return (
42
- <CustomRender content={JSON.parse(content)} name={name || 'unknown'} url={manifest.ui?.url} />
52
+ <IFrameRender
53
+ content={contentObj}
54
+ height={ui.height}
55
+ name={name || 'unknown'}
56
+ url={ui.url}
57
+ width={ui.width}
58
+ />
43
59
  );
44
60
  });
45
61
 
@@ -2,12 +2,13 @@ import { PluginRender, PluginRenderProps } from '@lobehub/chat-plugin-sdk';
2
2
  import { Skeleton } from 'antd';
3
3
  import { memo, useEffect, useState } from 'react';
4
4
 
5
- import { system } from './dynamticLoader';
5
+ import { system } from './utils';
6
6
 
7
- interface CustomRenderProps extends PluginRenderProps {
7
+ interface SystemJsRenderProps extends PluginRenderProps {
8
8
  url: string;
9
9
  }
10
- const CustomRender = memo<CustomRenderProps>(({ url, ...props }) => {
10
+
11
+ const SystemJsRender = memo<SystemJsRenderProps>(({ url, ...props }) => {
11
12
  const [component, setComp] = useState<PluginRender | null>(null);
12
13
 
13
14
  useEffect(() => {
@@ -31,4 +32,4 @@ const CustomRender = memo<CustomRenderProps>(({ url, ...props }) => {
31
32
 
32
33
  return <Render {...props} />;
33
34
  });
34
- export default CustomRender;
35
+ export default SystemJsRender;
@@ -1,3 +1,6 @@
1
+ import { uniqBy } from 'lodash-es';
2
+ import { ChatCompletionFunctions } from 'openai-edge/types/api';
3
+
1
4
  import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
2
5
  import { pluginHelpers } from '@/store/plugin/helpers';
3
6
 
@@ -5,12 +8,12 @@ import { PluginStoreState } from './initialState';
5
8
 
6
9
  const enabledSchema =
7
10
  (enabledPlugins: string[] = []) =>
8
- (s: PluginStoreState) => {
9
- return Object.values(s.pluginManifestMap)
10
- .filter((p) => {
11
- // 如果不存在 enabledPlugins,那么全部不启用
12
- if (!enabledPlugins) return false;
11
+ (s: PluginStoreState): ChatCompletionFunctions[] => {
12
+ // 如果不存在 enabledPlugins,那么全部不启用
13
+ if (!enabledPlugins) return [];
13
14
 
15
+ const list = Object.values(s.pluginManifestMap)
16
+ .filter((p) => {
14
17
  // 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
15
18
  return enabledPlugins.includes(p.identifier);
16
19
  })
@@ -21,6 +24,8 @@ const enabledSchema =
21
24
  name: manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name,
22
25
  })),
23
26
  );
27
+
28
+ return uniqBy(list, 'name');
24
29
  };
25
30
 
26
31
  const pluginList = (s: PluginStoreState) => [...s.pluginList, ...s.customPluginList];
@@ -1,10 +1,10 @@
1
1
  import { t } from 'i18next';
2
- import { merge } from 'lodash-es';
3
2
 
4
3
  import { DEFAULT_AVATAR, DEFAULT_BACKGROUND_COLOR } from '@/const/meta';
5
4
  import { SessionStore } from '@/store/session';
6
5
  import { LanguageModel } from '@/types/llm';
7
6
  import { MetaData } from '@/types/meta';
7
+ import { merge } from '@/utils/merge';
8
8
 
9
9
  import { sessionSelectors } from '../session/selectors';
10
10
  import { initialLobeAgentConfig } from './initialState';
@@ -19,7 +19,7 @@ const currentAgentTitle = (s: SessionStore) => currentAgentMeta(s)?.title || t('
19
19
 
20
20
  const currentAgentConfig = (s: SessionStore) => {
21
21
  const session = sessionSelectors.currentSession(s);
22
- return merge({}, initialLobeAgentConfig, session?.config);
22
+ return merge(initialLobeAgentConfig, session?.config);
23
23
  };
24
24
 
25
25
  const currentAgentSystemRole = (s: SessionStore) => {
@@ -0,0 +1,11 @@
1
+ import { merge as _merge, mergeWith } from 'lodash-es';
2
+
3
+ /**
4
+ * 用于合并对象,如果是数组则直接替换
5
+ * @param target
6
+ * @param source
7
+ */
8
+ export const merge: typeof _merge = <T = object>(target: T, source: T) =>
9
+ mergeWith({}, target, source, (obj, src) => {
10
+ if (Array.isArray(obj)) return src;
11
+ });