@lobehub/chat 0.67.0 → 0.68.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 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,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.68.0](https://github.com/lobehub/lobe-chat/compare/v0.67.0...v0.68.0)
6
+
7
+ <sup>Released on **2023-09-03**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Plugin default use iframe render.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **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))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 0.67.0](https://github.com/lobehub/lobe-chat/compare/v0.66.0...v0.67.0)
6
31
 
7
32
  <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.0",
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.0",
70
70
  "@lobehub/chat-plugins-gateway": "^1.5.0",
71
71
  "@lobehub/ui": "latest",
72
72
  "@vercel/analytics": "^1",
@@ -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];