@lobehub/chat 0.94.5 → 0.95.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.
Files changed (37) hide show
  1. package/.github/workflows/pr-welcome.yml +1 -1
  2. package/CHANGELOG.md +35 -0
  3. package/package.json +4 -4
  4. package/src/app/chat/(desktop)/features/ChatInput.tsx +0 -1
  5. package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +1 -1
  6. package/src/app/chat/features/Conversation/ChatList/Messages/Assistant.tsx +12 -15
  7. package/src/app/chat/features/Conversation/ChatList/Messages/Function.tsx +26 -15
  8. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +36 -0
  9. package/src/app/chat/features/Conversation/ChatList/Plugins/{FunctionCall.tsx → Inspector/index.tsx} +53 -25
  10. package/src/app/chat/features/Conversation/ChatList/Plugins/{IFrameRender → Render/DefaultType/IFrameRender}/index.tsx +5 -16
  11. package/src/app/chat/features/Conversation/ChatList/Plugins/{PluginMessage.tsx → Render/DefaultType/index.tsx} +3 -3
  12. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +136 -0
  13. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/index.tsx +28 -0
  14. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/index.tsx +28 -0
  15. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/iframeOnReady.ts +23 -0
  16. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/listenToPlugin.ts +65 -0
  17. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginSettings.ts +17 -0
  18. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginState.ts +20 -0
  19. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/postMessage.ts +17 -0
  20. package/src/const/plugin.ts +1 -1
  21. package/src/features/PluginSettings/index.tsx +5 -3
  22. package/src/features/PluginSettingsModal/index.tsx +45 -0
  23. package/src/store/plugin/selectors.ts +10 -4
  24. package/src/store/session/slices/chat/actions/index.ts +4 -1
  25. package/src/store/session/slices/chat/actions/message.ts +102 -117
  26. package/src/store/session/slices/chat/actions/plugin.ts +109 -0
  27. package/src/store/session/slices/chat/reducers/message.ts +31 -1
  28. package/src/store/session/slices/chat/selectors/chat.ts +17 -20
  29. package/src/store/session/slices/chat/selectors/index.ts +4 -2
  30. package/src/types/chatMessage.ts +1 -0
  31. package/src/utils/message.ts +8 -1
  32. package/src/app/chat/features/Conversation/ChatList/Plugins/IFrameRender/hooks.ts +0 -29
  33. package/src/app/chat/features/Conversation/ChatList/Plugins/IFrameRender/utils.ts +0 -17
  34. /package/src/app/chat/features/Conversation/ChatList/Plugins/{PluginResultRender.tsx → Inspector/PluginResultJSON.tsx} +0 -0
  35. /package/src/app/chat/features/Conversation/ChatList/Plugins/{style.ts → Inspector/style.ts} +0 -0
  36. /package/src/app/chat/features/Conversation/ChatList/Plugins/{SystemJsRender → Render/DefaultType/SystemJsRender}/index.tsx +0 -0
  37. /package/src/app/chat/features/Conversation/ChatList/Plugins/{SystemJsRender → Render/DefaultType/SystemJsRender}/utils.ts +0 -0
@@ -1,7 +1,7 @@
1
1
  name: PR Welcome
2
2
 
3
3
  on:
4
- pull_request:
4
+ pull_request_target:
5
5
  types:
6
6
  - closed
7
7
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,41 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.95.0](https://github.com/lobehub/lobe-chat/compare/v0.94.5...v0.95.0)
6
+
7
+ <sup>Released on **2023-10-24**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: 优化 plugin 文件夹命名以支持 standalone 类型的插件.
12
+
13
+ #### ✨ Features
14
+
15
+ - **misc**: Support function call at message end, support plugin settings modal, support plugin state and settings.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### Code refactoring
23
+
24
+ - **misc**: 优化 plugin 文件夹命名以支持 standalone 类型的插件 ([98860a8](https://github.com/lobehub/lobe-chat/commit/98860a8))
25
+
26
+ #### What's improved
27
+
28
+ - **misc**: Support function call at message end, closes [#357](https://github.com/lobehub/lobe-chat/issues/357) ([e195fdb](https://github.com/lobehub/lobe-chat/commit/e195fdb))
29
+ - **misc**: Support plugin settings modal ([f47b6fa](https://github.com/lobehub/lobe-chat/commit/f47b6fa))
30
+ - **misc**: Support plugin state and settings ([10829a4](https://github.com/lobehub/lobe-chat/commit/10829a4))
31
+
32
+ </details>
33
+
34
+ <div align="right">
35
+
36
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
37
+
38
+ </div>
39
+
5
40
  ### [Version 0.94.5](https://github.com/lobehub/lobe-chat/compare/v0.94.4...v0.94.5)
6
41
 
7
42
  <sup>Released on **2023-10-22**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.94.5",
3
+ "version": "0.95.0",
4
4
  "description": "An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -76,7 +76,7 @@
76
76
  "ahooks": "^3",
77
77
  "ai": "^2",
78
78
  "antd": "^5",
79
- "antd-style": "^3.5",
79
+ "antd-style": "^3",
80
80
  "brotli-wasm": "^1",
81
81
  "chroma-js": "^2",
82
82
  "copy-to-clipboard": "^3",
@@ -122,7 +122,7 @@
122
122
  "zustand-utils": "^1"
123
123
  },
124
124
  "devDependencies": {
125
- "@commitlint/cli": "^17",
125
+ "@commitlint/cli": "^18",
126
126
  "@lobehub/i18n-cli": "latest",
127
127
  "@lobehub/lint": "latest",
128
128
  "@next/bundle-analyzer": "^13",
@@ -141,7 +141,7 @@
141
141
  "@types/uuid": "^9",
142
142
  "@umijs/lint": "^4",
143
143
  "@vitest/coverage-v8": "latest",
144
- "commitlint": "^17",
144
+ "commitlint": "^18",
145
145
  "consola": "^3",
146
146
  "dpdm": "^3",
147
147
  "eslint": "^8",
@@ -15,7 +15,6 @@ const ChatInputDesktopLayout = memo(() => {
15
15
 
16
16
  return (
17
17
  <DraggablePanel
18
- expandable={false}
19
18
  fullscreen={expand}
20
19
  headerHeight={HEADER_HEIGHT}
21
20
  minHeight={CHAT_TEXTAREA_HEIGHT}
@@ -36,7 +36,7 @@ const PluginSettings: RenderErrorMessage = memo(({ id, plugin }) => {
36
36
  <Flexbox className={styles.desc}>{t('pluginSettings.desc')}</Flexbox>
37
37
  <Divider style={{ margin: '0 16px' }} />
38
38
  {manifest.settings && (
39
- <PluginSettingsConfig id={manifest.identifier} settings={manifest.settings} />
39
+ <PluginSettingsConfig id={manifest.identifier} schema={manifest.settings} />
40
40
  )}
41
41
  <Button
42
42
  block
@@ -3,23 +3,20 @@ import { memo } from 'react';
3
3
 
4
4
  import { useSessionStore } from '@/store/session';
5
5
  import { chatSelectors } from '@/store/session/selectors';
6
- import { isFunctionMessage } from '@/utils/message';
6
+ import { isFunctionMessageAtStart } from '@/utils/message';
7
7
 
8
- import FunctionCall from '../Plugins/FunctionCall';
8
+ import Inspector from '../Plugins/Inspector';
9
9
  import { DefaultMessage } from './Default';
10
10
 
11
- export const AssistantMessage: RenderMessage = memo(
12
- ({ id, plugin, function_call, content, ...props }) => {
13
- const genFunctionCallProps = useSessionStore(chatSelectors.getFunctionMessageParams);
11
+ export const AssistantMessage: RenderMessage = memo(({ id, plugin, content, ...props }) => {
12
+ const fcProps = useSessionStore(chatSelectors.getFunctionMessageProps({ content, id, plugin }));
14
13
 
15
- if (!isFunctionMessage(content)) return <DefaultMessage content={content} id={id} {...props} />;
14
+ if (!isFunctionMessageAtStart(content))
15
+ return <DefaultMessage content={content} id={id} {...props} />;
16
16
 
17
- const fcProps = genFunctionCallProps({ content, function_call, id, plugin });
18
-
19
- return (
20
- <div id={id}>
21
- <FunctionCall {...fcProps} />
22
- </div>
23
- );
24
- },
25
- );
17
+ return (
18
+ <div id={id}>
19
+ <Inspector {...fcProps} />
20
+ </div>
21
+ );
22
+ });
@@ -1,23 +1,34 @@
1
1
  import { RenderMessage } from '@lobehub/ui';
2
- import { memo } from 'react';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { memo, useState } from 'react';
3
4
  import { Flexbox } from 'react-layout-kit';
4
5
 
5
6
  import { useSessionStore } from '@/store/session';
6
7
  import { chatSelectors } from '@/store/session/selectors';
7
8
 
8
- import FunctionCall from '../Plugins/FunctionCall';
9
- import PluginMessage from '../Plugins/PluginMessage';
9
+ import Inspector from '../Plugins/Inspector';
10
+ import PluginRender from '../Plugins/Render';
10
11
 
11
- export const FunctionMessage: RenderMessage = memo(
12
- ({ id, content, plugin, function_call, name }) => {
13
- const genFunctionCallProps = useSessionStore(chatSelectors.getFunctionMessageParams);
14
- const fcProps = genFunctionCallProps({ content, function_call, id, plugin });
12
+ export const FunctionMessage: RenderMessage = memo(({ id, content, plugin, name }) => {
13
+ const fcProps = useSessionStore(
14
+ chatSelectors.getFunctionMessageProps({ content, id, plugin }),
15
+ isEqual,
16
+ );
17
+ const [showRender, setShow] = useState(true);
15
18
 
16
- return (
17
- <Flexbox gap={12} id={id}>
18
- <FunctionCall {...fcProps} />
19
- <PluginMessage content={content} loading={fcProps.loading} name={name} />
20
- </Flexbox>
21
- );
22
- },
23
- );
19
+ return (
20
+ <Flexbox gap={12} id={id}>
21
+ <Inspector showRender={showRender} {...fcProps} setShow={setShow} />
22
+ {showRender && (
23
+ <PluginRender
24
+ content={content}
25
+ id={id}
26
+ loading={fcProps.loading}
27
+ name={name}
28
+ payload={fcProps.command}
29
+ type={fcProps.type}
30
+ />
31
+ )}
32
+ </Flexbox>
33
+ );
34
+ });
@@ -0,0 +1,36 @@
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import { LucideSettings } from 'lucide-react';
3
+ import { memo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import PluginSettingsModal from '@/features/PluginSettingsModal';
7
+ import { pluginSelectors, usePluginStore } from '@/store/plugin';
8
+
9
+ const Settings = memo<{ id: string }>(({ id }) => {
10
+ const item = usePluginStore(pluginSelectors.getPluginManifestById(id));
11
+ const [open, setOpen] = useState(false);
12
+ const { t } = useTranslation('plugin');
13
+ return (
14
+ item?.settings && (
15
+ <>
16
+ <ActionIcon
17
+ icon={LucideSettings}
18
+ onClick={() => {
19
+ setOpen(true);
20
+ }}
21
+ title={t('setting')}
22
+ />
23
+ <PluginSettingsModal
24
+ id={id}
25
+ onClose={() => {
26
+ setOpen(false);
27
+ }}
28
+ open={open}
29
+ schema={item.settings}
30
+ />
31
+ </>
32
+ )
33
+ );
34
+ });
35
+
36
+ export default Settings;
@@ -1,26 +1,40 @@
1
1
  import { LoadingOutlined } from '@ant-design/icons';
2
- import { Avatar, Highlighter, Icon } from '@lobehub/ui';
2
+ import { LobePluginType } from '@lobehub/chat-plugin-sdk';
3
+ import { ActionIcon, Avatar, Highlighter, Icon } from '@lobehub/ui';
3
4
  import { Tabs } from 'antd';
4
- import { LucideChevronDown, LucideChevronUp, LucideToyBrick } from 'lucide-react';
5
+ import { LucideChevronDown, LucideChevronUp, LucideOrbit, LucideToyBrick } from 'lucide-react';
5
6
  import { memo, useState } from 'react';
6
7
  import { useTranslation } from 'react-i18next';
7
8
  import { Flexbox } from 'react-layout-kit';
8
9
 
9
10
  import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
10
11
 
11
- import PluginResult from './PluginResultRender';
12
+ import PluginResult from './PluginResultJSON';
13
+ import Settings from './Settings';
12
14
  import { useStyles } from './style';
13
15
 
14
- export interface FunctionCallProps {
16
+ export interface InspectorProps {
15
17
  arguments?: string;
16
18
  command?: any;
17
19
  content: string;
18
20
  id?: string;
19
21
  loading?: boolean;
22
+ setShow?: (showRender: boolean) => void;
23
+ showRender?: boolean;
24
+ type?: LobePluginType;
20
25
  }
21
26
 
22
- const FunctionCall = memo<FunctionCallProps>(
23
- ({ arguments: requestArgs = '{}', command, loading, content, id = 'unknown' }) => {
27
+ const Inspector = memo<InspectorProps>(
28
+ ({
29
+ arguments: requestArgs = '{}',
30
+ command,
31
+ showRender,
32
+ loading,
33
+ setShow,
34
+ content,
35
+ id = 'unknown',
36
+ type,
37
+ }) => {
24
38
  const { t } = useTranslation('plugin');
25
39
  const { styles } = useStyles();
26
40
  const [open, setOpen] = useState(false);
@@ -41,24 +55,38 @@ const FunctionCall = memo<FunctionCallProps>(
41
55
 
42
56
  return (
43
57
  <Flexbox gap={8}>
44
- <Flexbox
45
- align={'center'}
46
- className={styles.container}
47
- gap={8}
48
- horizontal
49
- onClick={() => {
50
- setOpen(!open);
51
- }}
52
- >
53
- {loading ? (
54
- <div>
55
- <LoadingOutlined />
56
- </div>
57
- ) : (
58
- avatar
59
- )}
60
- {pluginTitle ?? t('plugins.unknown')}
61
- <Icon icon={open ? LucideChevronUp : LucideChevronDown} />
58
+ <Flexbox align={'center'} distribution={'space-between'} gap={24} horizontal>
59
+ <Flexbox
60
+ align={'center'}
61
+ className={styles.container}
62
+ gap={8}
63
+ horizontal
64
+ onClick={() => {
65
+ setOpen(!open);
66
+ }}
67
+ >
68
+ {loading ? (
69
+ <div>
70
+ <LoadingOutlined />
71
+ </div>
72
+ ) : (
73
+ avatar
74
+ )}
75
+ {pluginTitle ?? t('plugins.unknown')}
76
+ <Icon icon={open ? LucideChevronUp : LucideChevronDown} />
77
+ </Flexbox>
78
+ <Flexbox horizontal>
79
+ {type === 'standalone' && <ActionIcon icon={LucideOrbit} />}
80
+ <Settings id={id} />
81
+ {setShow && (
82
+ <ActionIcon
83
+ icon={showRender ? LucideChevronUp : LucideChevronDown}
84
+ onClick={() => {
85
+ setShow(!showRender);
86
+ }}
87
+ />
88
+ )}
89
+ </Flexbox>
62
90
  </Flexbox>
63
91
  {open && (
64
92
  <Tabs
@@ -87,4 +115,4 @@ const FunctionCall = memo<FunctionCallProps>(
87
115
  },
88
116
  );
89
117
 
90
- export default FunctionCall;
118
+ export default Inspector;
@@ -1,9 +1,9 @@
1
1
  import { PluginRenderProps } from '@lobehub/chat-plugin-sdk/client';
2
2
  import { Skeleton } from 'antd';
3
- import { memo, useEffect, useRef, useState } from 'react';
3
+ import { memo, useRef, useState } from 'react';
4
4
 
5
- import { useOnPluginFetchMessage, useOnPluginReady } from './hooks';
6
- import { sendMessageToPlugin } from './utils';
5
+ import { useOnPluginReadyForInteraction } from '../../utils/iframeOnReady';
6
+ import { sendMessageToPlugin } from '../../utils/postMessage';
7
7
 
8
8
  interface IFrameRenderProps extends PluginRenderProps {
9
9
  height?: number;
@@ -12,24 +12,13 @@ interface IFrameRenderProps extends PluginRenderProps {
12
12
  }
13
13
 
14
14
  const IFrameRender = memo<IFrameRenderProps>(({ url, width = 800, height = 300, ...props }) => {
15
- const [loading, setLoading] = useState(true);
16
- const [readyForRender, setReady] = useState(false);
17
15
  const iframeRef = useRef<HTMLIFrameElement>(null);
18
-
19
- useOnPluginReady(() => setReady(true));
16
+ const [loading, setLoading] = useState(true);
20
17
 
21
18
  // 当 props 发生变化时,主动向 iframe 发送数据
22
- useEffect(() => {
19
+ useOnPluginReadyForInteraction(() => {
23
20
  const iframeWin = iframeRef.current?.contentWindow;
24
21
 
25
- if (iframeWin && readyForRender) {
26
- sendMessageToPlugin(iframeWin, props);
27
- }
28
- }, [readyForRender, props]);
29
-
30
- // 当接收到来自 iframe 的请求时,触发发送数据
31
- useOnPluginFetchMessage(() => {
32
- const iframeWin = iframeRef.current?.contentWindow;
33
22
  if (iframeWin) {
34
23
  sendMessageToPlugin(iframeWin, props);
35
24
  }
@@ -11,13 +11,13 @@ import IFrameRender from './IFrameRender';
11
11
 
12
12
  const SystemJsRender = dynamic(() => import('./SystemJsRender'), { ssr: false });
13
13
 
14
- export interface FunctionMessageProps {
14
+ export interface PluginDefaultTypeProps {
15
15
  content: string;
16
16
  loading?: boolean;
17
17
  name?: string;
18
18
  }
19
19
 
20
- const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
20
+ const PluginDefaultType = memo<PluginDefaultTypeProps>(({ content, name }) => {
21
21
  const { t } = useTranslation('plugin');
22
22
 
23
23
  const manifest = usePluginStore((s) => s.pluginManifestMap[name || '']);
@@ -65,4 +65,4 @@ const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
65
65
  );
66
66
  });
67
67
 
68
- export default PluginMessage;
68
+ export default PluginDefaultType;
@@ -0,0 +1,136 @@
1
+ import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2
+ import { Skeleton } from 'antd';
3
+ import { memo, useRef, useState } from 'react';
4
+
5
+ import { useOnPluginSettingsUpdate } from '@/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginSettings';
6
+ import { useOnPluginStateUpdate } from '@/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginState';
7
+ import { pluginSelectors, usePluginStore } from '@/store/plugin';
8
+ import { useSessionStore } from '@/store/session';
9
+ import { chatSelectors } from '@/store/session/selectors';
10
+
11
+ import { useOnPluginReadyForInteraction } from '../utils/iframeOnReady';
12
+ import {
13
+ useOnPluginFetchMessage,
14
+ useOnPluginFetchPluginSettings,
15
+ useOnPluginFetchPluginState,
16
+ useOnPluginFillContent,
17
+ } from '../utils/listenToPlugin';
18
+ import {
19
+ sendMessageToPlugin,
20
+ sendPayloadToPlugin,
21
+ sendPluginSettingsToPlugin,
22
+ sendPluginStateToPlugin,
23
+ } from '../utils/postMessage';
24
+
25
+ interface IFrameRenderProps {
26
+ height?: number;
27
+ id: string;
28
+ payload?: PluginRequestPayload;
29
+ url: string;
30
+ width?: number;
31
+ }
32
+
33
+ const IFrameRender = memo<IFrameRenderProps>(({ url, id, payload, width = 600, height = 300 }) => {
34
+ const [loading, setLoading] = useState(true);
35
+
36
+ const iframeRef = useRef<HTMLIFrameElement>(null);
37
+
38
+ // when payload change,send content to plugin
39
+ useOnPluginReadyForInteraction(() => {
40
+ const iframeWin = iframeRef.current?.contentWindow;
41
+ if (iframeWin && payload) {
42
+ sendPayloadToPlugin(iframeWin, payload);
43
+ }
44
+ }, [payload]);
45
+
46
+ // when plugin wants to get message content, send it to plugin
47
+ useOnPluginFetchMessage(() => {
48
+ const iframeWin = iframeRef.current?.contentWindow;
49
+
50
+ if (iframeWin) {
51
+ const message = chatSelectors.getMessageById(id)(useSessionStore.getState());
52
+ if (!message) return;
53
+ const props = { content: '' };
54
+
55
+ try {
56
+ props.content = JSON.parse(message.content || '{}');
57
+ } catch {
58
+ props.content = message.content || '';
59
+ }
60
+
61
+ sendMessageToPlugin(iframeWin, props);
62
+ }
63
+ }, []);
64
+
65
+ // when plugin try to send back message, we should fill it to the message content
66
+ const fillPluginContent = useSessionStore((s) => s.fillPluginMessageContent);
67
+ useOnPluginFillContent((content) => {
68
+ fillPluginContent(id, content);
69
+ });
70
+
71
+ // when plugin wants to get plugin state, send it to plugin
72
+ useOnPluginFetchPluginState((key) => {
73
+ const iframeWin = iframeRef.current?.contentWindow;
74
+
75
+ if (iframeWin) {
76
+ const message = chatSelectors.getMessageById(id)(useSessionStore.getState());
77
+ if (!message) return;
78
+
79
+ sendPluginStateToPlugin(iframeWin, key, message.pluginState?.[key]);
80
+ }
81
+ });
82
+
83
+ // when plugin update state, we should update it to the message pluginState key
84
+ const updatePluginState = useSessionStore((s) => s.updatePluginState);
85
+ useOnPluginStateUpdate((key, value) => {
86
+ updatePluginState(id, key, value);
87
+ });
88
+
89
+ // when plugin wants to get plugin settings, send it to plugin
90
+ useOnPluginFetchPluginSettings(() => {
91
+ const iframeWin = iframeRef.current?.contentWindow;
92
+
93
+ if (iframeWin) {
94
+ if (!payload?.identifier) return;
95
+
96
+ const settings = pluginSelectors.getPluginSettingsById(payload?.identifier)(
97
+ usePluginStore.getState(),
98
+ );
99
+
100
+ sendPluginSettingsToPlugin(iframeWin, settings);
101
+ }
102
+ });
103
+
104
+ // when plugin update settings, we should update it to the plugin settings
105
+ const updatePluginSettings = usePluginStore((s) => s.updatePluginSettings);
106
+ useOnPluginSettingsUpdate((value) => {
107
+ if (!payload?.identifier) return;
108
+
109
+ updatePluginSettings(payload?.identifier, value);
110
+ });
111
+
112
+ return (
113
+ <>
114
+ {loading && <Skeleton active style={{ width }} />}
115
+ <iframe
116
+ // @ts-ignore
117
+ allowtransparency="true"
118
+ height={height}
119
+ hidden={loading}
120
+ onLoad={() => {
121
+ setLoading(false);
122
+ }}
123
+ ref={iframeRef}
124
+ src={url}
125
+ style={{
126
+ border: 0,
127
+ // iframe 在 color-scheme:dark 模式下无法透明
128
+ // refs: https://www.jianshu.com/p/bc5a37bb6a7b
129
+ colorScheme: 'light',
130
+ }}
131
+ width={width}
132
+ />
133
+ </>
134
+ );
135
+ });
136
+ export default IFrameRender;
@@ -0,0 +1,28 @@
1
+ import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2
+ import { memo } from 'react';
3
+
4
+ import { usePluginStore } from '@/store/plugin';
5
+
6
+ import IFrameRender from './Iframe';
7
+
8
+ export interface PluginStandaloneTypeProps {
9
+ id: string;
10
+ name?: string;
11
+ payload?: PluginRequestPayload;
12
+ }
13
+
14
+ const PluginDefaultType = memo<PluginStandaloneTypeProps>(({ payload, id, name = 'unknown' }) => {
15
+ const manifest = usePluginStore((s) => s.pluginManifestMap[name]);
16
+
17
+ if (!manifest?.ui) return;
18
+
19
+ const ui = manifest.ui;
20
+
21
+ if (!ui.url) return;
22
+
23
+ return (
24
+ <IFrameRender height={ui.height} id={id} payload={payload} url={ui.url} width={ui.width} />
25
+ );
26
+ });
27
+
28
+ export default PluginDefaultType;
@@ -0,0 +1,28 @@
1
+ import { LobePluginType, PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2
+ import { memo } from 'react';
3
+
4
+ import DefaultType from './DefaultType';
5
+ import Standalone from './StandaloneType';
6
+
7
+ export interface PluginRenderProps {
8
+ content: string;
9
+ id: string;
10
+ loading?: boolean;
11
+ name?: string;
12
+ payload?: PluginRequestPayload;
13
+ type?: LobePluginType;
14
+ }
15
+
16
+ const PluginRender = memo<PluginRenderProps>(({ content, id, payload, name, type }) => {
17
+ switch (type) {
18
+ case 'standalone': {
19
+ return <Standalone id={id} name={name} payload={payload} />;
20
+ }
21
+
22
+ default: {
23
+ return <DefaultType content={content} name={name} />;
24
+ }
25
+ }
26
+ });
27
+
28
+ export default PluginRender;
@@ -0,0 +1,23 @@
1
+ import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
2
+ import { useEffect, useState } from 'react';
3
+
4
+ export const useOnPluginReadyForInteraction = (onReady: () => void, deps: any[] = []) => {
5
+ const [readyForRender, setReady] = useState(false);
6
+ useEffect(() => {
7
+ const fn = (e: MessageEvent) => {
8
+ if (e.data.type === PluginChannel.pluginReadyForRender) {
9
+ setReady(true);
10
+ }
11
+ };
12
+
13
+ window.addEventListener('message', fn);
14
+ return () => {
15
+ window.removeEventListener('message', fn);
16
+ };
17
+ }, []);
18
+ useEffect(() => {
19
+ if (readyForRender) {
20
+ onReady();
21
+ }
22
+ }, [readyForRender, ...deps]);
23
+ };